Veriscope is a real-time pipeline that:
The project is designed for political-debate streams with a configurable video delay (default: 30s).
This repo contains 3 connected subsystems:
texte/ and ingestion/: 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"]
run_fusion_to_temporal.sh forces --providers mistral.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 compose).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.env
Fill 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=30
Notes:
FACT_CHECK_POST_URL should target app-web inside Docker network.cle.env is auto-loaded by worker and transcript scripts../scripts/run_stack.sh up --build
docker compose up -d --build
docker compose ps
./scripts/run_stack.sh logs workflows-worker
Expected services:
temporal on 7233temporal-ui on 8080app-web on 8000app-reverb on 8081app-queueworkflows-workersource ingestion/.venv/bin/activate
python texte/realtime_transcript_fusion.py --list-devices
Example output:
index: 1, name: WOODBRASS UM3index: 2, name: Microphone MacBook AirUse --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-decisions
What this does:
/api/stream/fact-check.Open: http://localhost:8080
For each workflow:
WorkflowExecutionCompleted result,analysis_result.afficher_bandeaupost_result.postedpost_result.status_codetiming_debug.measured_delay_from_start_secondstiming_debug.delay_error_secondsInterpretation:
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.POST http://localhost:8000/api/stream/fact-checkapp/routes/api.phphttp://localhost:8000/overlays/fact-checkapp/routes/web.phpdocker compose logs -f app-web app-queue app-reverb workflows-worker
Look for:
app-web: /api/stream/fact-check hits,app-queue: VerifyFactCheckSceneTimestampJob running,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_MODEModuleNotFoundError: mistralaiYou 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.txt
zsh: command not found: temporalUse Docker stack (scripts/run_stack.sh) instead of local Temporal binary.
Namespace default is not foundEnsure temporal-create-namespace completed successfully. Restart full stack:
./scripts/run_stack.sh restart --build
OSError: [Errno 48] Address already in usePort is already used (often 8000). Stop conflicting process or use another port.
EngineDeadError)The transcript script now includes auto-reconnect logic. If persistent, relaunch script and check API key/network.
Check order:
post_result.posted=true),/api/stream/fact-check,http://localhost:8000/overlays/fact-check,Because workflow can intentionally skip when:
afficher_bandeau=false,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.We use cookies
We use cookies to analyze traffic and improve your experience. You can accept or reject analytics cookies.