Livrare LOT 1 - Didi
This commit is contained in:
commit
5380c3fc63
990 changed files with 133308 additions and 0 deletions
246
ai_platform/modules/forensic_features/docs/CONTRACT.md
Normal file
246
ai_platform/modules/forensic_features/docs/CONTRACT.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue