Build Fellow

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 meeting notes pipeline whose hard problems are chunking long audio without losing words at the seams, extracting decisions and actions that are anchored to what was actually said, and being honest about speaker labels it cannot reliably produce.

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

RuntimePython 3.12 with FastAPI, one process on your own machineAudioFFmpeg to sixteen kilohertz mono, checked against the model's current requirementTranscriptionA local speech model through whisper.cpp, no audio leaving the machineStorageSQLite with immutable transcript segmentsExtractionAnchored extraction only, with a local model or an optional key

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 interface.

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

    Verify python3 --version prints 3.12 or higher

  2. installfree

    Why Every container and codec a colleague sends you arrives differently, and the conversion to the model's documented input format is the step that decides whether the transcript is right.

    Get it Your package manager, or ffmpeg.org. open ↗

    Verify ffmpeg -version prints a version and converts a sample to the target format

  3. have readyfree, a download of a few hundred megabytes upward

    Why The model file is the transcription quality, and its documented sample rate and channel count are what the conversion step has to hit exactly.

    Get it Download a model for your chosen local runtime and read its current input requirements.

    Verify A one minute sample transcribes and the documented format is written into your configuration

  4. have readyfree

    Why Chunking and seam handling only fail on long files, and a three minute sample will pass every test while a ninety minute one finds the bug.

    Get it A recording of your own, or a public domain long-form recording.

    Verify It is over an hour and contains speech across every chunk boundary

  5. decidefree

    Why Both are decisions about other people's voices, and deciding them after the first awkward request means deciding them badly.

    Get it Write down what you confirm at import, how long audio is kept, and what deletion removes.

    Verify All three appear in the configuration and the README

Data model

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

- recordings(id, filename, sha256, duration_s, imported_at, consent_confirmed_at, consent_note, delete_after)
- chunks(recording_id, ordinal, start_ms, end_ms, overlap_ms, transcribed_at, model_name) · the seams are recorded because that is where words go missing
- segments(id, recording_id, start_ms, end_ms, text, confidence, speaker_label) · immutable, and speaker_label is usually null and honest about it
- meetings(id, recording_id, title, held_at, attendees_json, agenda_md) · what you knew before the recording existed
- items(id, meeting_id, kind, text, segment_id, start_ms, quote, owner, due_on, status) · kind is decision, action or question, and segment_id is not optional
- searches · a full-text index over segments, rebuilt from segments and therefore disposable
- Every item in the notes points at a segment. An extraction that cannot name the moment it came from does not become a row.

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
DATABASE_PATHrequired./data/meetings.dbRecordings, segments, items. The transcript is the evidence.
AUDIO_DIRrequired./data/audioConverted audio. Subject to the retention period below.
MODEL_PATHrequired./models/ggml-medium.binThe local speech model. Its documented input format drives the conversion.
TARGET_SAMPLE_RATErequired16000From the model's documentation, not from memory. Asserted in a test.
CHUNK_SECONDSrequired600Chunk length before overlap. Long enough to keep context, short enough to fail cheaply.
CHUNK_OVERLAP_SECONDSrequired10The seam insurance. Deduplicated by text match, not by clock.
AUDIO_RETENTION_DAYSrequired60Audio expires; transcripts and items do not. Stated in the README.
EXTRACTION_API_KEYsecretoptionalsk-...redactedOptional. Without it a local model does the extraction and the tool still works.

The build, in order

  1. Audio in, normalised, with consent recorded

    A file is accepted, converted correctly, and stored with a record of why you were allowed to have it.

    1. The model's documented input format is a requirement, not a suggestion.

      Files src/audio.pytests/test_convert.py

    2. A confirmation stored with the recording, and a README that does not pretend to be a lawyer.

      Files src/ingest.pysrc/templates/import.html

    3. Re-importing the same meeting twice is the most common user error in this category.

      Files src/ingest.py

    done when · tick each as it passes
    watch out
    • Feeding a forty four kilohertz stereo file straight to a model that documents sixteen kilohertz mono produces a transcript that looks plausible and is quietly wrong, which is the worst failure mode available here because nobody notices. Convert explicitly with FFmpeg, assert the output format in a test, and re-read the model's current requirement rather than trusting this sentence.
  2. Long audio without losing the seams

    A ninety minute recording transcribes in chunks that overlap and rejoin without dropping words.

    1. The seam is where the words go, so put the seam somewhere quiet.

      Files src/chunk.pytests/test_seams.py

    2. Clocks drift across chunk boundaries and text does not.

      Files src/merge.py

    3. A gap you can see beats a sentence that was never there.

      Files src/transcribe.py

    done when · tick each as it passes
    watch out
    • Cutting a long recording at exact intervals slices a word in half at every boundary, and the two halves come back as two wrong words that read perfectly well, so the error is invisible in the output and shows up months later in a quoted decision. Overlap the chunks, split at silence where you can find one, and reconcile the overlap by matching text rather than by trusting the clock.
  3. Decisions and actions that point at the tape

    Every extracted item carries a timestamp and the verbatim words it came from.

    1. The check is trivial and it is the difference between notes and fiction.

      Files src/extract.pytests/test_anchoring.py

    2. A visible discard count tells you when the extraction is going wrong.

      Files src/extract.py

    3. An item you can hear is an item somebody can disagree with.

      Files src/templates/meeting.html

    done when · tick each as it passes
    watch out
    • Asking a model for action items and storing whatever comes back produces a tidy list containing one item nobody said, assigned to somebody who was not there, and it looks exactly like the four correct ones beside it. Require a verbatim quote and a segment identifier for every item, check the quote actually occurs in the segment, and drop the ones that do not rather than showing them with a warning.
  4. Finding the meeting where somebody said it

    Search spans every meeting and lands on the moment, not the file.

    1. The unsummarised half is the half you will search for.

      Files src/search.py

    2. Then it is disposable, and a schema change stops being frightening.

      Files src/search.pytests/test_reindex.py

    3. Reading the words is half of it; hearing the tone is the other half.

      Files src/templates/search.html

    done when · tick each as it passes
    watch out
    • Indexing the generated notes instead of the transcript makes the half of every meeting that nobody summarised unsearchable, which is precisely the half you go looking for six months later. Index the segments, treat the notes as a view, and make the index rebuildable so a schema change is a reindex rather than a migration.
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.