164 lines
9.1 KiB
Markdown
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.
|