gallery_server_client_s190js/KNN-SCAN.md

164 lines
9.1 KiB
Markdown

# Gestione dei prototipi facciali: flusso di assegnazione e aggiornamento
**Progetto:** server fotografico `s190js`
**Percorso di riferimento:** `/home/nvme/dockerdata/prove/s190js`
**Scopo:** descrivere il comportamento verificato nel codice, dalla nomina di un cluster al salvataggio dei prototipi usati per il riconoscimento facciale.
> **Nota sul livello di verifica:** questo documento descrive il codice esaminato durante l'analisi. Non dimostra che uno specifico job sia stato completato nel database in produzione: per quello occorre controllare i record reali.
## 1. Panoramica
Quando si assegna una persona a un cluster dalla revisione dei volti, la route aggiorna le associazioni dei volti e richiede un aggiornamento dei prototipi per quella persona.
La richiesta viene registrata nella tabella `face_prototype_jobs`. Un daemon controlla la coda e avvia `tools/runPrototypeWorker.js` in modalità persistente (`--persist`). Il worker esegue la selezione dei prototipi e, se l'elaborazione riesce, salva i risultati in una transazione SQLite.
Il confronto KNN/OpenCL in modalità `shadow` viene eseguito **dopo** il salvataggio. Serve a confrontare i risultati e non decide quali prototipi vengono salvati.
## 2. Componenti coinvolti
| Componente | Responsabilità |
|---|---|
| `routes/faceReview.js` | Gestisce l'assegnazione della persona al cluster e richiede l'aggiornamento dei prototipi. |
| `queuePrototypeRefresh()` in `routes/faceReview.js` | Crea o aggiorna un job `pending` per la persona e l'utente. |
| Tabella `face_prototype_jobs` | Registra le richieste, lo stato, i tentativi, gli errori e il risultato. |
| `tools/runPrototypeDaemon.js` | Controlla periodicamente la coda, recupera alcuni job bloccati e avvia il worker. |
| `tools/runPrototypeWorker.js` | Calcola la selezione, salva i prototipi e le valutazioni e aggiorna lo stato del job. |
| `person_face_prototypes` | Contiene i prototipi facciali associati alle persone. |
| `person_face_recognition_evaluations` | Contiene le valutazioni di riconoscimento calcolate dal worker. |
| `face_recognition_index_state` | Indica che l'indice di riconoscimento deve essere aggiornato (`dirty`). |
| `tools/faceKnnRuntime.js` e `tools/runFaceKnnShadow.js` | Eseguono il confronto KNN in modalità shadow, se abilitato e configurato. |
## 3. Dall'assegnazione del cluster alla coda
Il frontend invia una richiesta alla route:
```text
POST /face-review/clusters/:clusterId/assign-person
```
con i dati della persona e gli ID dei volti inclusi. Nel codice esaminato, la route:
1. verifica che la persona e il cluster siano validi;
2. verifica che i volti indicati appartengano al cluster;
3. aggiorna le membership, segnando i volti inclusi come accettati e quelli esclusi come esclusi;
4. assegna `person_id` ai volti inclusi e rimuove eventuali esclusioni corrispondenti;
5. gestisce i volti esclusi e le relative righe in `person_face_exclusions`;
6. aggiorna `face_clusters.person_id`;
7. chiama `queuePrototypeRefresh()` con `reason: 'cluster_assigned'`.
Questi aggiornamenti e l'accodamento sono eseguiti nel contesto della transazione della route.
### Come viene accorpata la coda
`queuePrototypeRefresh()` cerca un job con lo stesso `user`, lo stesso `person_id` e `status = 'pending'`.
- **Se lo trova:** aggiorna il job esistente, compresi `reason`, `requested_by` e `requested_at`.
- **Se non lo trova:** crea un nuovo job con `status = 'pending'`.
Il riutilizzo riguarda solo i job `pending`. Se esiste già un job `processing`, questa funzione non lo aggiorna: può creare un nuovo job `pending`.
## 4. Come il daemon esegue i job
Il `package.json` definisce:
```json
"prototype:daemon": "node tools/runPrototypeDaemon.js",
"prototype:worker": "node tools/runPrototypeWorker.js --persist",
"prototype:once": "node tools/runPrototypeDaemon.js --once"
```
Il daemon:
1. conta i job con `status = 'pending'`;
2. se non ne trova, attende l'intervallo configurato e ricontrolla (salvo la modalità `--once`);
3. se ne trova, avvia `tools/runPrototypeWorker.js --persist`;
4. dopo un successo, può avviare immediatamente un altro job pending;
5. se il worker fallisce, registra l'errore nei log e attende prima di ricontrollare la coda.
### Recupero dei job bloccati
`recoverStaleJobs()` può riportare da `processing` a `pending` i job che hanno `started_at` più vecchio della soglia `staleMinutes` configurata. Il job viene contrassegnato con il motivo `stale_processing_recovered`.
Questa è una protezione per i job rimasti bloccati in elaborazione; non equivale a un nuovo tentativo automatico di tutti i job in stato `error`.
## 5. Cosa fa il worker
### 5.1 Selezione e acquisizione
`loadPendingJob()` cerca un job `pending` unito alla tabella `persons`, ordinando per `requested_at` crescente. Quando inizia il salvataggio persistente, `claimJob()` tenta di cambiare lo stato da `pending` a `processing`, incrementando `attempts` e registrando la firma degli input.
Se un altro worker ha già acquisito il job, l'aggiornamento condizionale non riesce e il worker segnala un errore.
### 5.2 Calcolo e controlli
Il worker carica i prototipi correnti, le esclusioni e una firma dei dati di input. Esegue l'esperimento di selezione e filtra i volti esclusi. Se non resta alcun prototipo selezionato, il job fallisce.
Prima di salvare, `persistResult()` ricalcola la firma degli input. Se è cambiata durante l'elaborazione:
- il job corrente diventa `superseded`;
- viene creato o riutilizzato un job sostitutivo `pending`;
- il risultato registra l'ID del job sostitutivo.
Questo evita di applicare una selezione calcolata su dati che nel frattempo sono cambiati.
### 5.3 Salvataggio dei prototipi
Quando gli input sono ancora validi, `persistResult()`:
1. carica le esclusioni di riferimento;
2. mantiene attivi i prototipi bloccati (`locked`) che non sono esclusi;
3. disattiva i prototipi non bloccati precedenti della persona;
4. inserisce o aggiorna i prototipi automatici selezionati in `person_face_prototypes`;
5. aggiorna l'ordine di selezione e i metadati;
6. ricalcola e salva le valutazioni in `person_face_recognition_evaluations`;
7. aggiorna `face_recognition_index_state` impostando `status = 'dirty'` e `dirty = 1`;
8. porta il job a `done`, registra `completed_at` e salva il riepilogo in `result_json`.
Le operazioni descritte sono eseguite dentro una singola transazione SQLite. Se la transazione fallisce, le modifiche non vengono confermate.
## 6. Errori e stato dei job
| Stato | Significato nel flusso esaminato |
|---|---|
| `pending` | Richiesta in attesa di essere elaborata. |
| `processing` | Worker ha acquisito il job e lo sta elaborando. |
| `done` | Salvataggio completato e risultato registrato. |
| `error` | Il worker ha registrato un errore in `last_error`. |
| `superseded` | Gli input sono cambiati durante il calcolo; è stata richiesta una nuova elaborazione. |
Se il worker fallisce, `markJobError()` imposta `status = 'error'`, registra `completed_at` e scrive il dettaglio dell'errore in `last_error`. Il daemon seleziona soltanto job `pending`: perciò un job `error` non viene ripreso automaticamente da quel normale ciclo di coda. Il recupero dei job `processing` scaduti è un meccanismo distinto.
## 7. KNN/OpenCL in modalità shadow
Dopo che `persistResult()` ha completato la transazione, il worker prova a eseguire `runFaceKnnShadow()`.
- Il confronto avviene dopo il salvataggio dei prototipi.
- Non modifica i prototipi, le valutazioni o lo stato del job.
- Un risultato incoerente viene riportato nei log.
- Un errore dello shadow viene intercettato e registrato nei log: non annulla il salvataggio già completato.
Pertanto, la modalità shadow è un controllo comparativo e non è il componente che decide la selezione persistita.
## 8. Come verificare un caso reale
Per concludere che l'assegnazione di un determinato cluster abbia prodotto prototipi, bisogna verificare nel database:
1. il job della persona e dell'utente, con motivo `cluster_assigned` o un motivo successivo se il job è stato accorpato;
2. lo stato finale e, in caso di errore, il contenuto di `last_error`;
3. `result_json`, in particolare `selected_count` e `selected_face_ids`;
4. le righe attive di `person_face_prototypes` per quella persona;
5. le valutazioni in `person_face_recognition_evaluations`;
6. lo stato dell'indice in `face_recognition_index_state`.
**Importante:** l'esistenza del codice di accodamento e del worker prova che il flusso è implementato, ma soltanto i dati reali del database confermano l'esito di una specifica assegnazione.
## 9. File e punti del codice esaminati
- `routes/faceReview.js`: route di assegnazione e `queuePrototypeRefresh()`.
- `tools/runPrototypeDaemon.js`: coda, ciclo del daemon e recupero dei job `processing` scaduti.
- `tools/runPrototypeWorker.js`: acquisizione del job, selezione, salvataggio transazionale, gestione degli errori e chiamata allo shadow KNN.
- `package.json`: script `prototype:daemon`, `prototype:worker` e `prototype:once`.
- `tools/faceKnnRuntime.js` e `tools/runFaceKnnShadow.js`: runtime e confronto KNN shadow.
---
Documento descrittivo basato sul codice ispezionato; verificare il codice e il database della versione in esecuzione prima di usarlo come specifica operativa definitiva.