fix: ASRX duplication, TKG edges, trace ingest, and add pipeline progress publishing
- ASRX handler no longer stores duplicate 'asr' pre_chunks - Pre_chunks storage made idempotent (delete-before-insert) - Rule 1 + trace_ingest changed to query 'asrx' not 'asr' - Trace chunks removed (dynamic from TKG/Qdrant) - TKG scroll_face_points fixed: trace_id >= 1 (not == 1) - TKG AsrxSegmentEntry: start/end -> start_time/end_time (match ASRX JSON) - Unregister error handling: log instead of silent discard - Add publish_pipeline_progress calls at each pipeline stage (processors, rule1, face_trace, identity_agent, TKG, rule2, completion)
This commit is contained in:
@@ -7,9 +7,11 @@ use axum::{
|
||||
Router,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::core::db::qdrant_db::QdrantDb;
|
||||
use crate::core::db::{schema, PostgresDb};
|
||||
|
||||
/// Shared video query params: mode=normal|debug, audio=on|off
|
||||
@@ -217,15 +219,32 @@ async fn bbox_overlay_video(
|
||||
|
||||
let start_sec = start_f as f64 / fps;
|
||||
|
||||
// Get face bboxes
|
||||
// frame_number is BIGINT (i64) in database
|
||||
let face_table = schema::table_name("face_detections");
|
||||
let rows: Vec<(i64, i32, i32, i32, i32, Option<i32>, Option<String>)> = sqlx::query_as(
|
||||
&format!("SELECT frame_number, x, y, width, height, trace_id, face_id FROM {} WHERE file_uuid = $1 AND frame_number BETWEEN $2 AND $3 ORDER BY frame_number", face_table)
|
||||
)
|
||||
.bind(face_fuid).bind(start_f).bind(end_f)
|
||||
.fetch_all(state.db.pool()).await
|
||||
.unwrap_or_else(|e| { tracing::error!("bbox query error: {}", e); vec![] });
|
||||
// Get face bboxes from Qdrant _faces
|
||||
use crate::core::db::qdrant_db::QdrantDb;
|
||||
use serde_json::json;
|
||||
|
||||
let qdrant = QdrantDb::new();
|
||||
let face_filter = json!({
|
||||
"must": [
|
||||
{"key": "file_uuid", "match": {"value": face_fuid}},
|
||||
{"key": "frame", "range": {"gte": start_f, "lte": end_f}},
|
||||
{"key": "trace_id", "match": {"value": 1}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", face_filter, 500).await.unwrap_or_default();
|
||||
|
||||
let rows: Vec<(i64, i32, i32, i32, i32, Option<i32>, Option<String>)> = points.iter().filter_map(|p| {
|
||||
let payload = &p["payload"];
|
||||
let frame = payload["frame"].as_i64()?;
|
||||
let bbox = &payload["bbox"];
|
||||
let x = bbox["x"].as_f64()? as i32;
|
||||
let y = bbox["y"].as_f64()? as i32;
|
||||
let w = bbox["width"].as_f64()? as i32;
|
||||
let h = bbox["height"].as_f64()? as i32;
|
||||
let trace_id = payload["trace_id"].as_i64().map(|t| t as i32);
|
||||
let face_id = payload.get("face_id").and_then(|v| v.as_str()).map(|s| s.to_string());
|
||||
Some((frame, x, y, w, h, trace_id, face_id))
|
||||
}).collect();
|
||||
|
||||
// Build filters — each bbox enabled only on its frame
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
@@ -334,16 +353,26 @@ async fn trace_video_inner(
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let (video_path, fps, _width, _height) = row.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
// Query face detections to find frame range for target trace
|
||||
// frame_number is BIGINT (i64) in database
|
||||
let face_table = schema::table_name("face_detections");
|
||||
let rows: Vec<(i64, i32, i32, i32, i32)> = sqlx::query_as(&format!(
|
||||
"SELECT frame_number, x, y, width, height FROM {} WHERE file_uuid = $1 AND trace_id = $2 ORDER BY frame_number",
|
||||
face_table
|
||||
))
|
||||
.bind(&file_uuid).bind(trace_id)
|
||||
.fetch_all(state.db.pool()).await
|
||||
.unwrap_or_else(|e| { tracing::error!("trace query error: {}", e); vec![] });
|
||||
// Query face detections from Qdrant to find frame range for target trace
|
||||
let qdrant = QdrantDb::new();
|
||||
let trace_filter = json!({
|
||||
"must": [
|
||||
{"key": "file_uuid", "match": {"value": file_uuid}},
|
||||
{"key": "trace_id", "match": {"value": trace_id}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", trace_filter, 500).await.unwrap_or_default();
|
||||
|
||||
let rows: Vec<(i64, i32, i32, i32, i32)> = points.iter().filter_map(|p| {
|
||||
let payload = &p["payload"];
|
||||
let frame = payload["frame"].as_i64()?;
|
||||
let bbox = &payload["bbox"];
|
||||
let x = bbox["x"].as_f64()? as i32;
|
||||
let y = bbox["y"].as_f64()? as i32;
|
||||
let w = bbox["width"].as_f64()? as i32;
|
||||
let h = bbox["height"].as_f64()? as i32;
|
||||
Some((frame, x, y, w, h))
|
||||
}).collect();
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
@@ -393,22 +422,50 @@ async fn trace_video_inner(
|
||||
let end_fn = ((start_sec + duration) * fps) as i64;
|
||||
|
||||
// Query all traces with identity names and bbox positions in the visible frame range
|
||||
// frame_number is BIGINT (i64) in database
|
||||
let identities_table = schema::table_name("identities");
|
||||
let all_rows: Vec<(i32, i64, i32, i32, i32, i32, Option<String>)> = sqlx::query_as(&format!(
|
||||
"SELECT fd.trace_id, fd.frame_number, fd.x, fd.y, fd.width, fd.height, i.name \
|
||||
FROM {} fd \
|
||||
LEFT JOIN {} i ON fd.identity_id = i.id \
|
||||
WHERE fd.file_uuid = $1 AND fd.frame_number BETWEEN $2 AND $3 AND fd.trace_id IS NOT NULL \
|
||||
ORDER BY fd.trace_id, fd.frame_number",
|
||||
face_table, identities_table
|
||||
))
|
||||
.bind(&file_uuid)
|
||||
.bind(start_fn)
|
||||
.bind(end_fn)
|
||||
.fetch_all(state.db.pool())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let all_points = qdrant.scroll_all_points("_faces", json!({
|
||||
"must": [
|
||||
{"key": "file_uuid", "match": {"value": file_uuid}},
|
||||
{"key": "frame", "range": {"gte": start_fn, "lte": end_fn}},
|
||||
{"key": "trace_id", "match": {"value": 1}}
|
||||
]
|
||||
}), 1000).await.unwrap_or_default();
|
||||
|
||||
// Get identity names for traces that have identity_id
|
||||
let mut identity_names: HashMap<i32, String> = HashMap::new();
|
||||
for point in &all_points {
|
||||
let payload = &point["payload"];
|
||||
if let Some(iid) = payload["identity_id"].as_i64() {
|
||||
let trace_id = payload["trace_id"].as_i64().unwrap_or(0) as i32;
|
||||
if iid > 0 && !identity_names.contains_key(&trace_id) {
|
||||
if let Some(name) = sqlx::query_scalar::<_, String>(&format!(
|
||||
"SELECT name FROM {} WHERE id = $1",
|
||||
identities_table
|
||||
))
|
||||
.bind(iid as i32)
|
||||
.fetch_optional(state.db.pool())
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
identity_names.insert(trace_id, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let all_rows: Vec<(i32, i64, i32, i32, i32, i32, Option<String>)> = all_points.iter().filter_map(|p| {
|
||||
let payload = &p["payload"];
|
||||
let trace_id = payload["trace_id"].as_i64()? as i32;
|
||||
let frame = payload["frame"].as_i64()?;
|
||||
let bbox = &payload["bbox"];
|
||||
let x = bbox["x"].as_f64()? as i32;
|
||||
let y = bbox["y"].as_f64()? as i32;
|
||||
let w = bbox["width"].as_f64()? as i32;
|
||||
let h = bbox["height"].as_f64()? as i32;
|
||||
let name = identity_names.get(&trace_id).cloned();
|
||||
Some((trace_id, frame, x, y, w, h, name))
|
||||
}).collect();
|
||||
|
||||
// Group frames by trace_id, compute start_frame per trace; collect bbox per frame
|
||||
// frame_number is i64 (BIGINT), so HashMaps need i64 for frame values
|
||||
@@ -1082,21 +1139,31 @@ async fn stranger_video_inner(
|
||||
fps
|
||||
);
|
||||
|
||||
// Query face detections by stranger_id directly
|
||||
let face_table = schema::table_name("face_detections");
|
||||
tracing::debug!("[stranger_video] face_table: {}", face_table);
|
||||
// Query face detections by stranger_id from Qdrant _faces
|
||||
use crate::core::db::qdrant_db::QdrantDb;
|
||||
use serde_json::json;
|
||||
|
||||
// frame_number is BIGINT (i64) in database
|
||||
let rows: Vec<(i64, i32, i32, i32, i32)> = sqlx::query_as(&format!(
|
||||
"SELECT frame_number, x, y, width, height FROM {} WHERE file_uuid = $1 AND stranger_id = $2 ORDER BY frame_number",
|
||||
face_table
|
||||
))
|
||||
.bind(&file_uuid).bind(stranger_id)
|
||||
.fetch_all(state.db.pool()).await
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::error!("[stranger_video] Face query error: {}", e);
|
||||
vec![]
|
||||
let qdrant = QdrantDb::new();
|
||||
let face_filter = json!({
|
||||
"must": [
|
||||
{"key": "file_uuid", "match": {"value": file_uuid}},
|
||||
{"key": "stranger_id", "match": {"value": stranger_id}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", face_filter, 1000).await.unwrap_or_default();
|
||||
|
||||
let rows: Vec<(i64, i32, i32, i32, i32)> = points.iter()
|
||||
.filter_map(|p| {
|
||||
let payload = &p["payload"];
|
||||
let frame = payload["frame"].as_i64()?;
|
||||
let bbox = &payload["bbox"];
|
||||
let x = bbox["x"].as_f64()? as i32;
|
||||
let y = bbox["y"].as_f64()? as i32;
|
||||
let w = bbox["width"].as_f64()? as i32;
|
||||
let h = bbox["height"].as_f64()? as i32;
|
||||
Some((frame, x, y, w, h))
|
||||
})
|
||||
.collect();
|
||||
|
||||
tracing::info!("[stranger_video] Found {} faces", rows.len());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user