# 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.