Veriscope is a real-time pipeline that:
- captures live speech from a microphone,
- transcribes complete phrases continuously,
- launches one Temporal workflow per phrase,
- runs fact-check analysis,
- posts validated results to the app API,
- broadcasts overlay updates for OBS.
The project is designed for political-debate streams with a configurable video delay (default: 30s).
This repo contains 3 connected subsystems:
texte/andingestion/: realtime speech-to-text and JSON emission.workflows/: Temporal launcher, workflow, worker, activities (analysis + POST).app/: Laravel API + broadcast overlay page (/overlays/fact-check) + OBS scene switching.
flowchart LR
Mic["Virtual/Physical Mic"] --> STT["Realtime STT (Mistral)"]
STT --> JSONL["JSONL transcript lines"]
JSONL --> Launcher["workflows/debate_jsonl_to_temporal.py"]
Launcher --> Temporal["Temporal Workflow per line"]
Temporal --> Activity["analyze_debate_line + correction check"]
Activity --> Post["POST /api/stream/fact-check"]
Post --> App["Laravel app-web"]
App --> Reverb["Broadcast stream.fact-check"]
Reverb --> Overlay["/overlays/fact-check in OBS Browser Source"]
- The fusion launcher is now Mistral-only for stability.
- ElevenLabs scripts still exist, but
run_fusion_to_temporal.shforces--providers mistral. - One workflow is started per transcript line.
- Delay is computed from the estimated phrase start (
metadata.timestamp_start) to align display with delayed video.
README.md: this file (global runbook).docker-compose.yml: full stack (Temporal + app + worker + reverb + queue + mediamtx).scripts/run_stack.sh: stack helper (up/down/restart/ps/logs).cle.env.example: env template for workflows.texte/realtime_transcript_fusion.py: realtime STT JSON emitter (Mistral stream).texte/run_fusion_to_temporal.sh: one-command STT -> Temporal launcher.workflows/debate_jsonl_to_temporal.py: reads JSONL, computes delay, starts workflows.workflows/debate_workflow.py: Temporal workflow orchestration.workflows/activities.py: fact-check analysis + POST to app API.app/routes/api.php:POST /api/stream/fact-check.app/routes/web.php: overlay page route.
- Docker Desktop (with
docker compose). - Python 3.11+ (local venv for transcription scripts).
- A working input microphone (physical or virtual).
- API key in
cle.env.
cd /path/to/workspace
git clone https://github.com/Barbapapazes/hackathon-paris.git
cd hackathon-paris
cd ingestion
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r ../texte/requirements.txt
cd ..cp cle.env.example cle.envFill at least:
MISTRAL_API_KEY=...
FACT_CHECK_POST_URL=http://app-web:8000/api/stream/fact-check
VIDEO_STREAM_DELAY_SECONDS=30
FACT_CHECK_ANALYSIS_TIMEOUT_SECONDS=30Notes:
FACT_CHECK_POST_URLshould targetapp-webinside Docker network.cle.envis auto-loaded by worker and transcript scripts.
./scripts/run_stack.sh up --builddocker compose up -d --builddocker compose ps
./scripts/run_stack.sh logs workflows-workerExpected services:
temporalon7233temporal-uion8080app-webon8000app-reverbon8081app-queueworkflows-worker
source ingestion/.venv/bin/activate
python texte/realtime_transcript_fusion.py --list-devicesExample output:
index: 1, name: WOODBRASS UM3index: 2, name: Microphone MacBook Air
Use --input-device-index with the selected index.
cd /Users/godefroy.meynard/Documents/test_datagouv_mcp/hackaton_audio/hackathon-paris
source ingestion/.venv/bin/activate
VIDEO_DELAY_SECONDS=30 MAX_WAIT_NEXT_PHRASE_SECONDS=0.5 ANALYSIS_TIMEOUT_SECONDS=20 \
./texte/run_fusion_to_temporal.sh \
--input-device-index 1 \
--personne "Valérie Pécresse" \
--source-video "TF1 20h" \
--question-posee "" \
--show-decisionsWhat this does:
- emits transcript JSON lines,
- starts Temporal workflows continuously,
- waits dynamic delay to match video stream,
- posts postable fact-check payloads to
/api/stream/fact-check.
Open: http://localhost:8080
For each workflow:
- open workflow execution,
- inspect
WorkflowExecutionCompletedresult, - check:
analysis_result.afficher_bandeaupost_result.postedpost_result.status_codetiming_debug.measured_delay_from_start_secondstiming_debug.delay_error_seconds
Interpretation:
afficher_bandeau=false=> workflow intentionally skipped posting.analysis_not_postable=> no valid overlay payload built.posted=true+200=> API accepted and broadcast path should trigger.
- Endpoint:
POST http://localhost:8000/api/stream/fact-check - Route file:
app/routes/api.php
- Overlay URL:
http://localhost:8000/overlays/fact-check - Route file:
app/routes/web.php
docker compose logs -f app-web app-queue app-reverb workflows-workerLook for:
app-web:/api/stream/fact-checkhits,app-queue:VerifyFactCheckSceneTimestampJobrunning,workflows-worker: analysis activity and post results.
Each emitted line follows:
{
"personne": "Valérie Pécresse",
"question_posee": "",
"affirmation": "Last N committed phrases",
"affirmation_courante": "Current complete phrase",
"metadata": {
"source_video": "TF1 20h",
"timestamp_elapsed": "00:24",
"timestamp_start": "2026-03-01T13:37:11.031Z",
"timestamp_end": "2026-03-01T13:37:15.599Z",
"timestamp": "2026-03-01T13:37:15.599Z"
}
}timestamp_start is used for delay alignment.
Each Temporal run receives:
current_json: current transcript line.last_minute_json: aggregate context for last 60s.post_delay_seconds: computed remaining delay before post.analysis_timeout_seconds.next_json: next phrase payload when available.
You can tune behavior at launch time:
VIDEO_DELAY_SECONDS(default 30)MAX_WAIT_NEXT_PHRASE_SECONDS(default 1.0)ANALYSIS_TIMEOUT_SECONDS(default 30)
And in cle.env:
VIDEO_STREAM_DELAY_SECONDSFACT_CHECK_ANALYSIS_TIMEOUT_SECONDSMISTRAL_WEB_SEARCH_MODELSOURCE_SELECTION_MODE- rate-limit backoff settings.
You are not in the venv or dependencies are missing.
cd ingestion
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r ../texte/requirements.txtUse Docker stack (scripts/run_stack.sh) instead of local Temporal binary.
Ensure temporal-create-namespace completed successfully. Restart full stack:
./scripts/run_stack.sh restart --buildPort is already used (often 8000). Stop conflicting process or use another port.
The transcript script now includes auto-reconnect logic. If persistent, relaunch script and check API key/network.
Check order:
- workflow actually posts (
post_result.posted=true), - app receives
/api/stream/fact-check, - OBS Browser Source points to
http://localhost:8000/overlays/fact-check, - OBS websocket scene names match app config.
Because workflow can intentionally skip when:
afficher_bandeau=false,- missing sources,
- next phrase detected as self-correction.
git checkout -b codex/<feature>
# edit
python3 -m py_compile workflows/*.py texte/*.py
bash -n texte/*.sh scripts/*.sh
git add <files>
git commit -m "feat: ..."
git push origin codex/<feature>workflows/README.md: Temporal-specific details.texte/README.md: STT scripts and options.app/README.md: API payload examples.