Livrare LOT 1 - Didi

This commit is contained in:
Dezvoltari Evotech 2026-06-25 14:13:25 -07:00
commit 5380c3fc63
990 changed files with 133308 additions and 0 deletions

View file

@ -0,0 +1,246 @@
# Output Schema Contract
Schema completă returnată de `POST /api/forensic-evidence`. Acest contract
este **stabil** între versiuni — clienții se pot baza pe câmpurile listate aici.
## Top-level structure
```jsonc
{
// ── Metadata request ─────────────────────────────────────────────────
"video_path": "video.mp4", // string — numele fișierului uploadat
"n_frames_extracted": 64, // int — câte cadre s-au extras cu ffmpeg
"every_n_frames_used": 3, // int — pasul efectiv folosit
"auto_skipped": ["m26 (no audio)"], // array<string> — module sărite + motiv
"modules_run": ["m25", "m27", "m28", "m29"], // array<string> — IDs efectiv rulate
"execution_time_ms": 48432.0, // float — timp total în ms
// ── Verdict fuzionat (info, NU obligatoriu) ──────────────────────────
"fusion": {
"score": 0.67, // float 0..1 | null
"label": "FAKE", // "FAKE" | "REAL" | "INCERT" | "NO_SIGNAL"
"confidence": 0.78, // float 0..1
"n_contributing": 4, // int — module care au contribuit
"n_no_signal": 0, // int — module fără semnal
"disagreement": 0.005, // float — varianța scorurilor
"contributions": { // dict<module_id, float> — pondere efectivă
"m25": 0.09,
"m27": 0.23,
"m28": 0.24,
"m29": 0.11
}
},
// ── Explicație human-readable (1-3 propoziții) ───────────────────────
"explanation": [
"Verdict forensic: FAKE (score=0.67, confidence=0.78, din 4 detectoare active)",
"Top contributors: m28 INCERT, m27 FAKE, m25 INCERT"
],
// ── Output per modul (cel mai important pentru parsing detaliat) ─────
"modules": {
"m25": { ... vezi „Per-module schema" mai jos ... },
"m27": { ... },
"m28": { ... },
"m29": { ... }
},
// ── Text formatat pentru injectare directă în prompt LLM ─────────────
"evidence_text": "FORENSIC EVIDENCE (objective measurements)...", // string ~1500-3000 chars
// ── Imagini pentru atașare la apel multimodal LLM ────────────────────
"images": [
{
"name": "m25_pulse_signal.png", // numele fișierului
"tool_id": "m25", // care modul l-a generat
"abs_path": "/app/data/results/{job_id}/images/m25_pulse_signal.png",
"size_bytes": 69384, // dimensiune pe disk
"data_url": "data:image/png;base64,..." // OPTIONAL, doar dacă encode_images=1
}
// ... 8-15 imagini total tipic
],
// ── Summary structurat pentru parsing programatic ────────────────────
"summary": {
"overall_score": 0.67, // = fusion.score (duplicat pentru convenience)
"overall_label": "FAKE",
"overall_confidence": 0.78,
"n_modules_run": 4,
"n_modules_signal": 4,
"disagreement": 0.005
},
// ── Instrucțiune fixă pentru LLM (cum să folosească evidence) ────────
"instruction_for_llm": "HOW TO USE FORENSIC EVIDENCE ABOVE:\n- These are objective...",
// ── Erori globale (NU per modul) ─────────────────────────────────────
"errors": []
}
```
## Per-module schema
Fiecare modul în `modules.{module_id}` are **EXACT aceeași structură**:
```jsonc
{
// ── Identificare ─────────────────────────────────────────────────────
"tool": {
"id": "m25", // string — module ID stabil
"name": "Physiology", // human-readable
"version": "1.0", // versiune algoritm (semantic)
"input_type": "overview_frames" // "overview_frames" | "video_path"
},
// ── Sumar — primary contract LLM ─────────────────────────────────────
"summary": {
// Câmpuri OBLIGATORII (toate modulele le au):
"frames_analyzed": 20, // int — cadre procesate efectiv
"frames_with_signal": 18, // int — cadre cu semnal valid
"primary_score": 0.63, // float 0..1 | null
// 1.0 = maxim suspect FAKE
// 0.0 = maxim natural REAL
// null = NU s-a putut calcula
"primary_label": "INCERT", // derivat din primary_score:
// null → "NO_SIGNAL"
// >=0.65 → "FAKE"
// <=0.35 → "REAL"
// else → "INCERT"
"confidence": 0.41, // float 0..1 — încredere în primary_score
"evidence": [ // array<string>, max 5 — pentru LLM
"Pulse: 82 BPM, SNR=1.1 dB (no plausible cardiac signal)",
"Blink count: 1 over 5.1s (natural)"
],
// Câmpuri SPECIFICE modulului (vezi docs/MODULES.md pentru lista completă):
"pulse_bpm": 82.35, // (m25)
"pulse_snr_db": 1.1, // (m25)
"blink_count_total": 1, // (m25)
"blink_asymmetry_ms": null, // (m25)
"lip_sync_offset_ms": 187.0, // (m26, doar dacă audio prezent)
"f0_std_hz": 12.3, // (m26)
"voice_ratio": 0.95, // (m26)
"npr_score_mean": 0.48, // (m27)
"jpeg_recon_score_mean": 0.78, // (m27)
"hf_score_mean": null, // (m27, doar dacă M27_USE_HF=1)
"peak_suspicion_max": 0.90, // (m28)
"boundary_mean_suspicion": 0.137, // (m28)
"lighting_mismatch_deg_mean": 95.0, // (m29)
"catchlight_consistency_mean": null // (m29)
// ... (vezi MODULES.md pentru lista exhaustivă per modul)
},
// ── Per-frame breakdown (opțional, pentru debug) ─────────────────────
"per_frame": [
{
"frame_index": 0,
"signal_present": true,
// câmpuri specifice modulului
"ear_left": 0.31,
"ear_right": 0.29
}
// ...
],
// ── Metrici la nivel de clip (date raw care nu sunt în summary) ──────
"metrics": {
"rppg_samples": 20,
"blinks_left": [{...}], // detalii per blink (m25)
"blinks_right": [{...}],
// ...
},
// ── PNG-uri generate de acest modul ──────────────────────────────────
"artifacts": {
"images": [
"m25_pulse_signal.png",
"m25_blink_timeline.png"
]
},
"errors": [], // array<string> — erori non-fatale
"warnings": ["MediaPipe lipsă; fallback la Haar"],
"execution_time_ms": 1247.3 // float — timp execuție modul
}
```
## Reguli stricte (garantate de `tools/_contract.py`)
1. **`primary_score`** este **întotdeauna** în [0, 1] sau `null`.
2. **`primary_label`** este derivat strict din `primary_score`:
| Score range | Label |
|-------------|-------|
| `null` | `"NO_SIGNAL"` |
| `>= 0.65` | `"FAKE"` |
| `<= 0.35` | `"REAL"` |
| `(0.35, 0.65)` | `"INCERT"` |
3. **`confidence`** reflectă cât material valid a avut tool-ul. Video scurte
sau cu puține cadre valide → confidence scăzut.
4. **`evidence`** conține **max 5** propoziții human-readable, fără jargon
tehnic excesiv. Gata de inserat în prompt LLM.
5. **`artifacts.images`** conține **doar nume de fișier** (nu paths absolute).
Paths se construiesc cu `results_dir/images/{name}`.
6. **`errors`** se umple **doar cu erori non-fatale** (modul produce un
rezultat parțial). Crash → excepție → orchestrator capturează în `errors`
global, nu per modul.
7. **NU există NaN/Inf** în output JSON. Toate float-urile sunt finite sau `null`.
8. **NO_SIGNAL este normal**, NU eroare. Modul care nu poate calcula nimic
(ex. m26 fără audio, m25 cu prea puține cadre) returnează:
```json
{
"summary": {
"primary_score": null,
"primary_label": "NO_SIGNAL",
"confidence": 0.0,
"evidence": ["Reason: no audio track in video"]
},
...
}
```
## Versionare
Versiunea schemei e implicită în structură. Schimbări breaking sunt anunțate
prin **versiune nouă a modulului** (`tool.version`). Câmpurile noi pot fi
adăugate la `summary` fără să rupă clienții existenți care le ignoră.
Câmpurile **OBLIGATORII** listate mai sus sunt stabile între versiuni.
## Validare schema în client
```python
def validate_response(data: dict) -> bool:
required_top = {"video_path", "modules_run", "fusion", "modules",
"evidence_text", "images", "summary"}
if not required_top.issubset(data.keys()):
return False
for mid, mod in data["modules"].items():
if "summary" not in mod or "tool" not in mod:
return False
s = mod["summary"]
required_summary = {"primary_score", "primary_label",
"confidence", "evidence"}
if not required_summary.issubset(s.keys()):
return False
return True
```
## Câmpuri specifice complete per modul
Pentru lista exhaustivă a câmpurilor `summary.tool_specific_field_*` pentru
fiecare modul m25-m29, vezi:
- [MODULES.md](MODULES.md) — secțiunea "Output specific" pentru fiecare modul
Acestea sunt **opționale** pentru clienți — toți ar trebui să se bazeze pe
câmpurile obligatorii (primary_score, primary_label, confidence, evidence)
plus `evidence_text` la nivel top-level care e gata pentru LLM.