KnowledgeVault AI module-spec M-04
M-04 delivers the live voice capture session: the core expert-facing interaction where an authenticated expert answers AI-generated questions by voice and sees extracted knowledge objects populate a structuring panel in real time. The module provides three API routes (POST /api/sessions/start, POST /api/sessions/:id/audio, POST /api/sessions/:id/complete), the capture_sessions Postgres table, a SharedExpertContext in-process service that holds live session state, Deepgram Nova-2 WebSocket integr
No items captured.
M-04 delivers the live voice capture session: the core expert-facing interaction where an authenticated expert answers AI-generated questions by voice and sees extracted knowledge objects populate a structuring panel in real time. The module provides three API routes (POST /api/sessions/start, POST /api/sessions/:id/audio, POST /api/sessions/:id/complete), the capture_sessions Postgres table, a SharedExpertContext in-process service that holds live session state, Deepgram Nova-2 WebSocket integr
No explicit evidence field yet. Require tests, screenshots, linked PRs, or reviewed outputs before marking complete.
No section body captured.
Machine-readable source fields
M-04 delivers the live voice capture session: the core expert-facing interaction where an authenticated expert answers AI-generated questions by voice and sees extracted knowledge objects populate a structuring panel in real time. The module provides three API routes (POST /api/sessions/start, POST /api/sessions/:id/audio, POST /api/sessions/:id/complete), the capture_sessions Postgres table, a SharedExpertContext in-process service that holds live session state, Deepgram Nova-2 WebSocket integration for streaming transcription with per-word confidence filtering, GPT-4o integration for question generation and micro-object extraction (sole real-time model), Supabase Realtime push for the structuring panel, and two mobile screens (S-03-a Active Session, S-03-b Session Complete). On /complete the session is frozen and handed off to M-05 for o3 evaluation and payment creation. This module does not handle evaluation, ground-truth gating, or payments.
M-04
| auth | path | errors | method | request | response |
|---|---|---|---|---|---|
| JWT required (role=expert claim) | /api/sessions/start | - 400 { error: 'product_id required', code: 'MISSING_PRODUCT_ID', status: 400 } - 401 { error: 'Unauthorized', code: 'UNAUTHORIZED', status: 401 } - 422 { error: 'product_not_found', code: 'PRODUCT_NOT_FOUND', status: 422 } - 503 { error: 'Product not ready yet — try again in a moment', code: 'PRE_SEED_FAILED', status: 503 } | POST | Body JSON: { product_id: string (uuid) } | 201: { session_id: string (uuid), first_question: string, product_context: { sku: string, name: string, category: string, manufacturer: string }, earnings_estimate_range: { min: number, max: number } } |
| JWT required (role=expert claim) | /api/sessions/:id/audio | - 400 { error: 'Audio chunk required', code: 'MISSING_AUDIO', status: 400 } - 401 { error: 'Unauthorized', code: 'UNAUTHORIZED', status: 401 } - 404 { error: 'Session not found', code: 'SESSION_NOT_FOUND', status: 404 } - 409 { error: 'Session already closed', code: 'SESSION_CLOSED', status: 409 } - 429 { error: 'Too many requests', code: 'RATE_LIMITED', status: 429, Retry-After: '1' } - 503 { error: 'Transcription unavailable', code: 'TRANSCRIPTION_FAILED', status: 503 } | POST | Path param: id (session uuid). Body: raw binary audio (Content-Type: audio/webm or audio/opus), max 60 seconds per chunk. | 200: { transcript_segment: string, wer_compensation_applied: boolean, micro_objects_extracted: [ { chunk_type: string, content: string } ], next_question: string|null, earnings_ticking: number, session_status: 'in_progress' | 'session_complete_ready' } |
| JWT required (role=expert claim) | /api/sessions/:id/complete | - 400 { error: 'At least one answer required', code: 'MIN_ANSWERS_REQUIRED', status: 400 } - 401 { error: 'Unauthorized', code: 'UNAUTHORIZED', status: 401 } - 404 { error: 'Session not found', code: 'SESSION_NOT_FOUND', status: 404 } - 409 { error: 'Session already closed', code: 'SESSION_CLOSED', status: 409 } | POST | Path param: id (session uuid). Body: {} (empty). | 200: { chunks_captured: integer, earnings_estimate: { min: number, max: number }, session_duration_s: integer } |
Enum type capture_session_status must be created before capture_sessions: CREATE TYPE capture_session_status AS ENUM ('in_progress', 'complete', 'abandoned'). Enum type knowledge_chunk_type must be created before knowledge_chunks: CREATE TYPE knowledge_chunk_type AS ENUM ('failure_mode', 'symptom', 'fix', 'psi_range', 'safety_warning', 'field_tip', 'tool_required', 'part_number_reference'). IMPORTANT: knowledge_chunks table may already exist from an earlier module — if so, use ALTER TABLE to add any missing columns (wer_compensation_applied, novelty_score, ground_truth_verified) rather than re-creating. session_context JSONB structure: { answered_question_count: integer, question_sequence: string[], knowledge_surface_coverage_pct: float, wer_compensation_applied: boolean, last_updated: ISO8601 string }. cost_breakdown JSONB structure (M-04 writes): { deepgram_usd: float, gpt4o_usd: float } — M-05 appends o3_usd and embedding_usd. score_object JSONB is null at end of M-04; written by M-05.
| rls | name | indexes | purpose | key columns |
|---|---|---|---|---|
| RLS enabled. Policy 'expert_own_sessions': SELECT and UPDATE allowed when auth.uid() = expert_id. INSERT allowed for authenticated role=expert. Service role bypasses all RLS. | capture_sessions | - CREATE INDEX idx_capture_sessions_expert_id ON capture_sessions(expert_id) - CREATE INDEX idx_capture_sessions_product_id ON capture_sessions(product_id) - CREATE INDEX idx_capture_sessions_status ON capture_sessions(status) WHERE status = 'in_progress' | Tracks one live voice capture session per expert per product attempt, from start through completion or abandonment. | - id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY - expert_id uuid NOT NULL REFERENCES experts(id) ON DELETE CASCADE - product_id uuid NOT NULL REFERENCES products(id) ON DELETE RESTRICT - status capture_session_status NOT NULL DEFAULT 'in_progress' - session_context JSONB NOT NULL DEFAULT '{}' - score_object JSONB - session_duration_s integer - cost_breakdown JSONB NOT NULL DEFAULT '{}' - created_at timestamptz NOT NULL DEFAULT now() - completed_at timestamptz |
| RLS enabled. Policy 'expert_own_chunks': SELECT allowed when auth.uid() = expert_id. Policy 'company_published_chunks': SELECT allowed when auth.jwt()->> 'role' = 'company' AND is_published = true. INSERT via service role only. UPDATE via service role only (M-05 sets novelty_score, ground_truth_verified, is_published). | knowledge_chunks | - CREATE INDEX idx_knowledge_chunks_session_id ON knowledge_chunks(session_id) - CREATE INDEX idx_knowledge_chunks_expert_id ON knowledge_chunks(expert_id) - CREATE INDEX idx_knowledge_chunks_product_id ON knowledge_chunks(product_id) - CREATE INDEX idx_knowledge_chunks_published ON knowledge_chunks(is_published) WHERE is_published = true | Stores individual micro-objects (knowledge units) extracted by GPT-4o during a capture session, pending publication after M-05 evaluation. | - id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY - session_id uuid NOT NULL REFERENCES capture_sessions(id) ON DELETE CASCADE - expert_id uuid NOT NULL REFERENCES experts(id) - product_id uuid NOT NULL REFERENCES products(id) - sku text NOT NULL - chunk_type knowledge_chunk_type NOT NULL - content text NOT NULL - is_published boolean NOT NULL DEFAULT false - novelty_score float - ground_truth_verified boolean NOT NULL DEFAULT false - wer_compensation_applied boolean NOT NULL DEFAULT false - created_at timestamptz NOT NULL DEFAULT now() |
| No RLS — accessed via service role only by M-05 evaluation worker. | evaluation_queue | - CREATE INDEX idx_evaluation_queue_unclaimed ON evaluation_queue(created_at) WHERE claimed_at IS NULL | Simple queue table for M-05 to pick up completed or abandoned sessions for o3 evaluation and payment creation. | - id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY - session_id uuid NOT NULL REFERENCES capture_sessions(id) - created_at timestamptz NOT NULL DEFAULT now() - claimed_at timestamptz - claimed_by text |
Capture Orchestrator
| id | name | type | inputs | outputs | description |
|---|---|---|---|---|---|
| D-04-01 | 001_capture_sessions_and_chunks.sql | migration | - Postgres connection with migration runner (Supabase CLI supabase db push or equivalent) | - Enum type capture_session_status: ('in_progress', 'complete', 'abandoned') - Enum type knowledge_chunk_type: ('failure_mode', 'symptom', 'fix', 'psi_range', 'safety_warning', 'field_tip', 'tool_required', 'part_number_reference') - Table capture_sessions with all columns, RLS, and indexes - Table knowledge_chunks with all columns, RLS, and indexes - Table evaluation_queue with session_id and created_at | Creates the capture_session_status enum type, the capture_sessions table tracking live sessions from start to completion, and the knowledge_chunk_type enum plus knowledge_chunks table for extracted micro-objects. Also creates an evaluation_queue table for M-05 handoff. Includes RLS policies and indexes. |
| D-04-02 | POST /api/sessions/start | api_route | - Authorization: Bearer <expert-JWT> (role=expert claim required) - Body JSON: { product_id: string (uuid) } | - 201: { session_id: string (uuid), first_question: string, product_context: { sku: string, name: string, category: string, manufacturer: string }, earnings_estimate_range: { min: number, max: number } } - CaptureSession row inserted with status='in_progress', expert_id from JWT sub, product_id from body - SharedExpertContext initialised and retrievable for subsequent /audio calls | Creates a new CaptureSession row, loads product_pre_seeds for the requested product, initialises SharedExpertContext in memory keyed by session_id, and returns session_id, first question, product context, and earnings estimate range. If product_pre_seeds is absent or older than 24 hours, calls Gemini synchronously (timeout 10s) to regenerate before responding. Cache hit response time target: ≤3s. |
| D-04-03 | POST /api/sessions/:id/audio | api_route | - Authorization: Bearer <expert-JWT> - Path param: id (session uuid, must be owned by JWT expert_id) - Body: raw binary audio (Content-Type: audio/webm or audio/opus), max 60 seconds per chunk | - 200: { transcript_segment: string, wer_compensation_applied: boolean, micro_objects_extracted: [ { chunk_type: string, content: string } ], next_question: string|null, earnings_ticking: number, session_status: 'in_progress' | 'session_complete_ready' } - KnowledgeChunk rows inserted for each micro_object with is_published=false - capture_sessions.session_context updated with answered_question_count, knowledge_surface_coverage_pct, wer_compensation_applied, last_updated - capture_sessions.cost_breakdown.deepgram_usd and cost_breakdown.gpt4o_usd incremented - Realtime broadcast on channel sess | Accepts a binary audio chunk, opens a Deepgram Nova-2 WebSocket connection to transcribe it, applies per-word confidence filtering (words with confidence < 0.75 flagged; wer_compensation_applied=true on response and session_context), passes transcript to GPT-4o with the current SharedExpertContext as system prompt to extract micro_objects and determine next_question. Inserts one KnowledgeChunk row per extracted micro-object (is_published=false), updates capture_sessions.session_context JSONB immediately, broadcasts each new chunk to Supabase Realtime channel session:<id>:chunks, and returns th |
| D-04-04 | POST /api/sessions/:id/complete | api_route | - Authorization: Bearer <expert-JWT> - Path param: id (session uuid, must be owned by JWT expert_id) - Body: {} (empty — all context from session state) | - 200: { chunks_captured: integer, earnings_estimate: { min: number, max: number }, session_duration_s: integer } - capture_sessions.status='complete', completed_at=now(), session_duration_s computed - SharedExpertContext for session_id removed from memory - Row inserted into evaluation_queue: { session_id, created_at=now() } | Closes an in-progress capture session: validates at least 1 answered question, sets status='complete', completed_at=now(), computes session_duration_s, freezes SharedExpertContext (removes from memory), and enqueues the session for M-05 evaluation by inserting a row into evaluation_queue. Returns a summary of captured knowledge and earnings estimate. |
| D-04-05 | SharedExpertContext | service | - init(session_id: string, pre_seed: { base_questions: string[], context_json: object }) — called by POST /api/sessions/start - update(session_id: string, transcript: string, micro_objects: object[], next_question: string|null) — called by POST /api/sessions/:id/audio - freeze(session_id: string) — called by POST /api/sessions/:id/complete - get(session_id: string) → SharedExpertContextObject | null — called by POST /api/sessions/:id/audio to build GPT-4o prompt | - get() returns: { session_id, question_sequence: string[], answered_question_count: integer, knowledge_surface_coverage_pct: float, context_summary: string, wer_compensation_applied: boolean } or null if evicted - Auto-abandon side-effect: capture_sessions.status='abandoned' + evaluation_queue row + memory eviction after 5-minute idle | In-process singleton service (module-level Map) that maintains per-session live state during a capture session. Keyed by session_id. Stores: question_sequence (ordered array of questions asked), answered_question_count (integer), knowledge_surface_coverage_pct (float 0–100, computed from extracted chunk_types vs. product's expected coverage schema from pre_seed), context_summary (rolling string for GPT-4o system prompt), wer_compensation_applied (boolean). Provides init(), update(), freeze(), and get() methods. Auto-abandons sessions idle for 5 minutes: sets capture_sessions.status='abandoned' |
| D-04-06 | ActiveCaptureSessionScreen (S-03-a) | component | - session_id (uuid) — from POST /api/sessions/start response, passed as navigation param - first_question (string) — from POST /api/sessions/start response - product_context ({ sku, name, category, manufacturer }) — from POST /api/sessions/start response - earnings_estimate_range ({ min, max }) — from POST /api/sessions/start response - Device microphone audio stream (MediaRecorder or React Native equivalent) | - Calls POST /api/sessions/:id/audio for each audio chunk (continuous while session active) - Calls POST /api/sessions/:id/complete on DONE tap or auto-close confirmation - Navigates to S-03-b with { chunks_captured, earnings_estimate, session_duration_s } on 200 from /complete - Navigates to S-02-a on abandon (after confirmation modal tap) | Full-screen mobile UI for the live capture session. Layout: top 60% voice and question panel — large centered current question text (C-03-a-1, aria-live=polite), audio waveform showing microphone state listening/processing/idle (C-03-a-2), live transcript feed (C-03-a-3, aria-live=polite). Bottom 40% structuring panel list (C-03-a-6, aria-live=polite) with knowledge chunk cards per type (C-03-a-7). Earnings meter fixed top-right (C-03-a-4). Question progress below question text (C-03-a-5). DONE button fixed bottom, disabled until answered_question_count >= 1 (C-03-a-8). Subscribes to Supabase |
| D-04-07 | SessionCompleteScreen (S-03-b) | component | - chunks_captured (integer) — from POST /api/sessions/:id/complete response, passed as navigation param - earnings_estimate ({ min: number, max: number }) — from /complete response, nav param - session_duration_s (integer) — from /complete response, nav param | - Navigates to S-04-a (evaluation pending screen) on CTA tap | Full-screen centered mobile screen displayed after POST /api/sessions/:id/complete returns 200. Shows a single session summary card (C-03-b-1, role=status): headline 'Session submitted', body '<n> knowledge objects captured', earnings label 'Estimated earnings' with value '$<min>–$<max>', and CTA 'View results when ready' navigating to S-04-a. All content from navigation params — no API calls on this screen. |
| calls | purpose | service |
|---|---|---|
| - WebSocket connect per /audio call: wss://api.deepgram.com/v1/listen?model=nova-2&smart_format=true&interim_results=false&utterance_end_ms=1000 with header Authorization: Token <DEEPGRAM_API_KEY> - Send binary audio frames over WebSocket - Receive JSON: { channel: { alternatives: [ { transcript: string, words: [ { word: string, confidence: float } ] } ] } } - Close WebSocket after final transcript received (one WebSocket per /audio call, stateless) | Real-time streaming speech-to-text transcription of expert audio chunks with per-word confidence scores used to flag low-confidence terms (WER compensation threshold 0.75). | Deepgram Nova-2 |
| - POST https://api.openai.com/v1/chat/completions with model='gpt-4o', response_format={ type: 'json_object' } - System prompt constructed from SharedExpertContext: context_summary + question_sequence + extraction schema (chunk_type enum values + content field) + follow-up trigger rules (hesitation indicators, low-confidence phrase patterns, coverage gap signals) - User message: transcript_segment from Deepgram - Expected response JSON: { next_question: string|null, micro_objects_to_add: [ { chunk_type: string, content: string } ], signals_flagged: string[], knowledge_surface_coverage_pct: flo | Interview conductor: generates follow-up questions and extracts structured micro-objects (KnowledgeChunks) from the live transcript. Sole AI model in the real-time path — no model chaining. | OpenAI GPT-4o |
| - Gemini API call (model: gemini-1.5-pro or gemini-2.0-flash) with product details (name, sku, category, manufacturer) as context - Response: { base_questions: string[], knowledge_surface_schema: object } upserted into product_pre_seeds table - Total call budget: 10 seconds — timeout returns 503 to client | Synchronous pre-seed generation fallback when product_pre_seeds is absent or stale (>24 hours old). Generates base question sequence and knowledge surface schema for a product. | Google Gemini |
| - Server-side (per micro_object in /audio handler): supabaseAdmin.channel('session:<id>:chunks').send({ type: 'broadcast', event: 'chunk', payload: { chunk_type, content } }) - Client-side (S-03-a on mount): supabase.channel('session:<id>:chunks').on('broadcast', { event: 'chunk' }, handler).subscribe() | Push extracted knowledge chunk objects from server to the mobile structuring panel (S-03-a) in real time, eliminating polling. | Supabase Realtime |
- experts table exists with columns: id uuid PK, name text NOT NULL, bio text, role_type text NOT NULL, specialties text[], years_experience integer, stripe_account_id text — created by M-02
- Supabase Auth configured with role=expert JWT claim, middleware protecting /api/sessions/* routes — created by M-02
- products table exists with columns: id uuid PK, sku text NOT NULL, name text NOT NULL, category text, manufacturer text NOT NULL, coverage_pct float, is_fully_captured boolean, has_surge_badge boolean — created by M-03
- product_pre_seeds table exists with columns: id uuid PK, product_id uuid FK products.id, context_json JSONB NOT NULL, base_questions JSONB[] NOT NULL, created_at timestamptz NOT NULL — created by M-03
- DEEPGRAM_API_KEY environment secret present and valid for Nova-2 WebSocket access
- OPENAI_API_KEY environment secret present and valid for GPT-4o chat completions
- GEMINI_API_KEY environment secret present for synchronous pre-seed generation fallback
- M-05 evaluation_queue mechanism defined: either an evaluation_queue table (session_id uuid, created_at timestamptz) or a pg_notify channel named 'evaluation_queue' — M-04 inserts/notifies, M-05 listens
No items captured.
1.0