Build Jamie

KINDAback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A local transcription and notes pipeline whose hard problems are consent, getting long audio through a model without losing an hour's work to a crash, and producing notes where every claim can be traced back to the words that produced it.

estimated effort weekendthe files for this build are in the project pack

RuntimePython 3.12 with FastAPI, one process, bound to localhostAudioFFmpeg for normalisation, always to the format the model requiresTranscriptionwhisper.cpp locally, chunked and resumableNotesAn extract-then-merge pass with verbatim spans requiredStorageSQLite for meetings and segments, audio as files with a retention

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the pipeline and the local web interface.

    Get it python.org, or your package manager. open ↗

    Verify python3 --version prints 3.12 or higher

  2. installfree

    Why Every input is normalised to the format the transcriber documents, and this is the tool that does it.

    Get it Your package manager, or the official builds. open ↗

    Verify ffmpeg -version prints a version and the conversion command runs

  3. installfree, after the model download

    Why Local transcription is the whole reason this is private, and the model file is a separate download from the binary.

    Get it Build from the repository and fetch a model with the provided script. open ↗

    Verify The command line transcribes a short 16 kHz mono WAV

  4. decidefree

    Why Whether you may record with one party's consent or need everyone's changes what this app is allowed to do, and it is a question with an actual answer.

    Get it Read your jurisdiction's wiretapping or interception statute, or its regulator's guidance, and write a paragraph.

    Verify The paragraph is in the README and the import gate reflects it

  5. have readyfree

    Why Resume, chunk stitching and retention are only testable on something over an hour, and a two minute clip proves nothing.

    Get it Record yourself reading for ninety minutes, or use a public-domain recording with a known transcript.

    Verify It exceeds an hour and you know what it says

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- meetings(id, title, occurred_at, participants_json, consent_recorded_by, consent_recorded_at, source_filename, duration_s)
- media(meeting_id, original_path, original_sha256, normalised_path, sample_rate, channels, codec, converted_at)
- chunks(meeting_id, ordinal, start_s, end_s, overlap_s, state, attempts, transcript_path, finished_at) · state is pending, running, done or failed
- segments(meeting_id, id, start_s, end_s, speaker_label, text) · speaker_label is anonymous until a human maps it
- speakers(meeting_id, label, mapped_name, mapped_by, mapped_at) · empty until somebody says who is who
- notes(meeting_id, kind, text, start_s, end_s, quote, confidence) · kind is decision, action or context, and a row without a quote is never written
- retention(meeting_id, audio_delete_after, deleted_at) · the audio goes, the notes stay
- Every notes row points at a span of segments, which is what makes the output checkable rather than plausible.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATA_DIRrequired./dataDatabase, audio, normalised copies and chunk transcripts. All of it sensitive.
WHISPER_MODEL_PATHrequired./models/ggml-base.en.binThe downloaded model file. Larger is slower and more accurate.
CHUNK_SECONDSrequired600Chunk length. Smaller means less lost to a crash and more seams to stitch.
CHUNK_OVERLAP_SECONDSrequired5Overlap used to remove duplicated words at boundaries.
AUDIO_RETENTION_DAYSrequired30After this the recording is deleted and the deletion is logged. Notes survive.
NOTES_API_KEYsecretoptionalsk-...redactedOnly if you use a hosted model for extraction. Unset means local extraction and no audio or transcript leaves the machine.

The build, in order

  1. Consent, then audio in the one format that works

    A file is accepted only with a consent record, and is converted to exactly what the transcriber wants.

    1. The import path refuses without it, and the test proves the refusal.

      Files src/import.pytests/test_consent_gate.py

    2. Sample rate, channels and codec, then probe the result.

      Files src/audio.pytests/test_normalise.py

    3. So a bad transcript can be traced to a bad input.

      Files src/media.py

    done when · tick each as it passes
    watch out
    • Handing a 48 kHz stereo recording from a phone straight to the transcriber produces either an outright error or, worse, a transcript that is subtly wrong throughout with no indication why, and you spend an evening blaming the model. Normalise every input to the documented format first, store the original untouched, and log the conversion parameters against the meeting.
  2. Long audio that survives a crash

    A two hour recording transcribes in resumable pieces, and a failure costs one piece.

    1. The difference between a resumable job and a gamble.

      Files src/transcribe.pytests/test_resume.py

    2. A sentence cut in half is a sentence transcribed twice.

      Files src/stitch.py

    3. Ninety-five percent of a transcript is useful; nothing is not.

      Files src/transcribe.py

    done when · tick each as it passes
    watch out
    • Transcribing a two hour file in a single call means a crash, an out-of-memory kill or a closed laptop at the ninety minute mark throws away ninety minutes of compute, and the obvious retry throws away another ninety. Chunk it, persist each chunk's result before moving on, and make restart resume rather than start.
  3. Speakers, without guessing who anyone is

    Turns are separated and labelled anonymously until a person says who each label is.

    1. Names enter only at render, from the human mapping.

      Files src/speakers.pytests/test_no_name_inference.py

    2. First lines per label beats a waveform nobody can read.

      Files src/templates/speakers.html

    3. So fixing a mistaken mapping is one update, not a migration.

      Files src/render.py

    done when · tick each as it passes
    watch out
    • Letting the summariser work out who is speaking from context puts a name against a commitment nobody made, and because the sentence reads naturally, the wrong person is chased for the wrong task a week later. Keep labels anonymous through the pipeline and substitute names only from the human mapping, at render time.
  4. Notes that point at the words

    Every decision and action item carries the timestamped quote it came from.

    1. And verify it against the transcript before writing the row.

      Files src/notes.pytests/test_quote_grounding.py

    2. Long-context summarisation is where invented commitments come from.

      Files src/notes.py

    3. A note you can hear is a note you can settle an argument with.

      Files src/templates/meeting.html

    done when · tick each as it passes
    watch out
    • Feeding a thirty thousand word transcript in one request and asking for a summary produces smooth, confident output containing action items nobody assigned, and there is no way to tell which ones by reading it. Extract per chunk with a required verbatim quote, verify the quote exists in the transcript, and discard the item if it does not.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more meeting notes.