feat: progressive multi-round face matching + pending person API

- Identity agent: per-face max matching, multi-round with derived
  seeds from high-confidence faces, angle diversity filter (cosine sim < 0.90)
- Pending person API: POST /file/:file_uuid/pending-person
  + GET /file/:file_uuid/pending-persons with status=pending, source=manual
- Update API docs (07_identity.md)
This commit is contained in:
Accusys
2026-06-24 03:42:04 +08:00
parent 766a1d9a6d
commit 14e886cc08
31 changed files with 5882 additions and 742 deletions
+31
View File
@@ -646,6 +646,10 @@ impl JobWorker {
Ok(())
}
}
crate::core::db::ProcessorType::FaceCluster => {
info!("Face clustering processor completed for {}", job.uuid);
Ok(())
}
crate::core::db::ProcessorType::Pose => {
if let Ok(result) = serde_json::from_str::<
crate::core::processor::PoseResult,
@@ -1093,6 +1097,33 @@ vector,
.filter(|r| job_processors.contains(&r.processor_type.as_str().to_string()))
.any(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Pending));
const MAX_RETRIES: i32 = 3;
if any_failed && !any_pending {
let failed_processors_to_retry: Vec<i32> = results
.iter()
.filter(|r| {
job_processors.contains(&r.processor_type.as_str().to_string())
&& matches!(r.status, crate::core::db::ProcessorJobStatus::Failed)
&& r.retry_count < MAX_RETRIES
})
.map(|r| r.id)
.collect();
if !failed_processors_to_retry.is_empty() {
info!("🔄 Attempting to retry {} failed processors...", failed_processors_to_retry.len());
for result_id in failed_processors_to_retry {
if let Ok(true) = self.db.retry_failed_processor(result_id, MAX_RETRIES).await {
if let Ok(mut conn) = self.redis.get_conn().await {
let redis_key = format!("momentry:progress:{}", uuid);
let _: Result<i32, _> = redis::AsyncCommands::del(&mut conn, &redis_key).await;
}
}
}
}
}
let any_skipped = results
.iter()
.filter(|r| job_processors.contains(&r.processor_type.as_str().to_string()))
+21
View File
@@ -747,6 +747,27 @@ impl ProcessorPool {
pid: 0,
})
}
ProcessorType::FaceCluster => {
let result = processor::process_face_cluster(
video_path,
output_path.to_str().unwrap(),
uuid,
Some(&sample_frames),
)
.await?;
tracing::info!(
"FACE_CLUSTER completed, output: {}",
output_path.to_str().unwrap()
);
Ok(ProcessorOutput {
data: serde_json::to_value(result)?,
chunks_produced: 0,
frames_processed: 0,
total_frames: 0,
retry_count: 0,
pid: 0,
})
}
ProcessorType::Pose => {
let result = processor::process_pose(
video_path,