refactor: cargo fmt across 27 files + behavioral fixes
Behavioral changes: - postgres_db: reorder default processors (cut first), prevent overwriting completed status - qdrant_db: fix scroll pagination exit condition (next.is_none()) - job_worker: add idempotency guards for face trace / TKG build; better error logging - main.rs: add LineWriter for stdout buffering Remaining diff is cargo fmt reformatting (line wrapping, import ordering).
This commit is contained in:
@@ -11,11 +11,11 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::api::types::AppState;
|
||||
use crate::core::db::redis_client::RedisClient;
|
||||
use crate::core::db::schema;
|
||||
use crate::core::db::PostgresDb;
|
||||
use crate::core::db::QdrantDb;
|
||||
use crate::core::progress::{AgentPhase, AgentProgress, AgentStats, publish_agent_progress};
|
||||
use crate::core::db::redis_client::RedisClient;
|
||||
use crate::core::progress::{publish_agent_progress, AgentPhase, AgentProgress, AgentStats};
|
||||
|
||||
pub fn identity_agent_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
@@ -213,17 +213,23 @@ async fn match_from_photo(
|
||||
// 4. Find best matching trace via Qdrant _faces search
|
||||
let qdrant = QdrantDb::new();
|
||||
|
||||
let best_match: Option<(i32, f64)> = match qdrant.search_face_collection(
|
||||
"_faces",
|
||||
&embedding_f32,
|
||||
1,
|
||||
"file_uuid",
|
||||
"",
|
||||
Some(&file_uuid),
|
||||
).await {
|
||||
let best_match: Option<(i32, f64)> = match qdrant
|
||||
.search_face_collection(
|
||||
"_faces",
|
||||
&embedding_f32,
|
||||
1,
|
||||
"file_uuid",
|
||||
"",
|
||||
Some(&file_uuid),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hits) if !hits.is_empty() => {
|
||||
let (score, payload) = &hits[0];
|
||||
let trace_id = payload.get("trace_id").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
|
||||
let trace_id = payload
|
||||
.get("trace_id")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(0) as i32;
|
||||
Some((trace_id, *score))
|
||||
}
|
||||
_ => None,
|
||||
@@ -295,18 +301,27 @@ async fn match_from_trace(
|
||||
{"key": "trace_id", "match": {"value": req.trace_id}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", trace_filter, 500).await.unwrap_or_default();
|
||||
let points = qdrant
|
||||
.scroll_all_points("_faces", trace_filter, 500)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let all_faces: Vec<(Vec<f32>, i64)> = points.iter().filter_map(|p| {
|
||||
let vector = p.get("vector").and_then(|v| v.as_array())?;
|
||||
let embedding: Vec<f32> = vector.iter().filter_map(|v| v.as_f64().map(|f| f as f32)).collect();
|
||||
let frame = p["payload"]["frame"].as_i64()?;
|
||||
if embedding.len() == 512 {
|
||||
Some((embedding, frame))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect();
|
||||
let all_faces: Vec<(Vec<f32>, i64)> = points
|
||||
.iter()
|
||||
.filter_map(|p| {
|
||||
let vector = p.get("vector").and_then(|v| v.as_array())?;
|
||||
let embedding: Vec<f32> = vector
|
||||
.iter()
|
||||
.filter_map(|v| v.as_f64().map(|f| f as f32))
|
||||
.collect();
|
||||
let frame = p["payload"]["frame"].as_i64()?;
|
||||
if embedding.len() == 512 {
|
||||
Some((embedding, frame))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if all_faces.is_empty() {
|
||||
return Err((
|
||||
@@ -328,13 +343,16 @@ async fn match_from_trace(
|
||||
let mut query_embeddings: Vec<Vec<f32>> = Vec::new();
|
||||
|
||||
// Get bbox size info from Qdrant payload
|
||||
let face_sizes: Vec<(i64, i32)> = points.iter().filter_map(|p| {
|
||||
let frame = p["payload"]["frame"].as_i64()?;
|
||||
let bbox = &p["payload"]["bbox"];
|
||||
let w = bbox["width"].as_f64().unwrap_or(0.0) as i32;
|
||||
let h = bbox["height"].as_f64().unwrap_or(0.0) as i32;
|
||||
Some((frame, w * h))
|
||||
}).collect();
|
||||
let face_sizes: Vec<(i64, i32)> = points
|
||||
.iter()
|
||||
.filter_map(|p| {
|
||||
let frame = p["payload"]["frame"].as_i64()?;
|
||||
let bbox = &p["payload"]["bbox"];
|
||||
let w = bbox["width"].as_f64().unwrap_or(0.0) as i32;
|
||||
let h = bbox["height"].as_f64().unwrap_or(0.0) as i32;
|
||||
Some((frame, w * h))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let face_sizes_map: std::collections::HashMap<i64, i32> = face_sizes.into_iter().collect();
|
||||
|
||||
@@ -373,14 +391,17 @@ async fn match_from_trace(
|
||||
]
|
||||
});
|
||||
|
||||
let hits = match qdrant.search_face_collection(
|
||||
"_faces",
|
||||
qemb,
|
||||
1,
|
||||
"trace_id",
|
||||
&req.trace_id.to_string(),
|
||||
Some(&req.file_uuid),
|
||||
).await {
|
||||
let hits = match qdrant
|
||||
.search_face_collection(
|
||||
"_faces",
|
||||
qemb,
|
||||
1,
|
||||
"trace_id",
|
||||
&req.trace_id.to_string(),
|
||||
Some(&req.file_uuid),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
tracing::warn!("[match_from_trace] Qdrant search failed: {}", e);
|
||||
@@ -389,7 +410,10 @@ async fn match_from_trace(
|
||||
};
|
||||
|
||||
if let Some((score, payload)) = hits.first() {
|
||||
let trace_id = payload.get("trace_id").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
|
||||
let trace_id = payload
|
||||
.get("trace_id")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(0) as i32;
|
||||
if seen_trace_ids.insert(trace_id) {
|
||||
validated.push((0, trace_id, *score));
|
||||
}
|
||||
@@ -787,7 +811,10 @@ pub async fn bind_speakers(pool: &sqlx::PgPool, file_uuid: &str) -> anyhow::Resu
|
||||
{"key": "trace_id", "match": {"value": 1}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", trace_filter, 500).await.unwrap_or_default();
|
||||
let points = qdrant
|
||||
.scroll_all_points("_faces", trace_filter, 500)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
// Group by trace_id, collect frames
|
||||
let mut traces: HashMap<i32, Vec<i64>> = HashMap::new();
|
||||
@@ -855,15 +882,14 @@ pub async fn bind_speakers(pool: &sqlx::PgPool, file_uuid: &str) -> anyhow::Resu
|
||||
}
|
||||
|
||||
// Compute fps from video table
|
||||
let fps: f64 = sqlx::query_scalar::<_, f64>(
|
||||
"SELECT COALESCE(fps, 25.0) FROM videos WHERE file_uuid=$1"
|
||||
)
|
||||
.bind(file_uuid)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(25.0);
|
||||
let fps: f64 =
|
||||
sqlx::query_scalar::<_, f64>("SELECT COALESCE(fps, 25.0) FROM videos WHERE file_uuid=$1")
|
||||
.bind(file_uuid)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(25.0);
|
||||
|
||||
tracing::info!(
|
||||
"[SpeakerBind] Using fps={:.3} for {} ({} traces)",
|
||||
@@ -880,7 +906,8 @@ pub async fn bind_speakers(pool: &sqlx::PgPool, file_uuid: &str) -> anyhow::Resu
|
||||
}
|
||||
|
||||
// Get identity_id for this trace from Qdrant payload
|
||||
let identity_id: Option<i32> = points.iter()
|
||||
let identity_id: Option<i32> = points
|
||||
.iter()
|
||||
.find(|p| {
|
||||
p["payload"]["trace_id"].as_i64() == Some(*trace_id as i64)
|
||||
&& p["payload"]["identity_id"].as_i64().is_some()
|
||||
@@ -993,7 +1020,11 @@ pub async fn run_identity_agent(
|
||||
|
||||
let matched = match_faces_iterative(pool, file_uuid).await.unwrap_or(0);
|
||||
progress.stats.tmdb_matches = matched as i64;
|
||||
progress.update_phase(AgentPhase::TmdbMatching, 1.0, &format!("Face matching: {} matches", matched));
|
||||
progress.update_phase(
|
||||
AgentPhase::TmdbMatching,
|
||||
1.0,
|
||||
&format!("Face matching: {} matches", matched),
|
||||
);
|
||||
if let Some(r) = redis.as_ref() {
|
||||
publish_agent_progress(&r, file_uuid, &progress).await;
|
||||
}
|
||||
@@ -1051,9 +1082,11 @@ pub async fn run_identity_agent(
|
||||
.await;
|
||||
}
|
||||
progress.stats.identities_created = identities.len() as i64;
|
||||
progress.update_phase(AgentPhase::IdentityCreation, 1.0, &format!(
|
||||
"Created {} identities from clusters", identities.len()
|
||||
));
|
||||
progress.update_phase(
|
||||
AgentPhase::IdentityCreation,
|
||||
1.0,
|
||||
&format!("Created {} identities from clusters", identities.len()),
|
||||
);
|
||||
if let Some(r) = redis.as_ref() {
|
||||
publish_agent_progress(&r, file_uuid, &progress).await;
|
||||
}
|
||||
@@ -1081,7 +1114,11 @@ pub async fn run_identity_agent(
|
||||
|
||||
let bound = bind_speakers(pool, file_uuid).await.unwrap_or(0);
|
||||
progress.stats.speaker_bindings = bound as i64;
|
||||
progress.update_phase(AgentPhase::SpeakerBinding, 1.0, &format!("Speaker binding: {} bound", bound));
|
||||
progress.update_phase(
|
||||
AgentPhase::SpeakerBinding,
|
||||
1.0,
|
||||
&format!("Speaker binding: {} bound", bound),
|
||||
);
|
||||
if let Some(r) = redis.as_ref() {
|
||||
publish_agent_progress(&r, file_uuid, &progress).await;
|
||||
}
|
||||
@@ -1162,7 +1199,10 @@ async fn find_ready_files(pool: &sqlx::PgPool) -> anyhow::Result<Vec<String>> {
|
||||
{"key": "identity_id", "match": {"value": null}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", filter, 1000).await.unwrap_or_default();
|
||||
let points = qdrant
|
||||
.scroll_all_points("_faces", filter, 1000)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut file_uuids: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
for point in &points {
|
||||
|
||||
Reference in New Issue
Block a user