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:
+35
-25
@@ -1094,13 +1094,18 @@ pub async fn exec_tkg_node_detail(
|
||||
}
|
||||
|
||||
/// Search for people by clothing color using appearance data
|
||||
pub async fn exec_search_by_appearance(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result<String, String> {
|
||||
let file_uuid = args.get("file_uuid")
|
||||
pub async fn exec_search_by_appearance(
|
||||
pool: &sqlx::PgPool,
|
||||
args: &serde_json::Value,
|
||||
) -> Result<String, String> {
|
||||
let file_uuid = args
|
||||
.get("file_uuid")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("file_uuid is required".to_string())?;
|
||||
let color = args.get("color")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("color is required (red, blue, green, yellow, orange, cyan, purple, white, black)".to_string())?;
|
||||
let color = args.get("color").and_then(|v| v.as_str()).ok_or(
|
||||
"color is required (red, blue, green, yellow, orange, cyan, purple, white, black)"
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let output_dir = std::env::var("MOMENTRY_OUTPUT_DIR")
|
||||
.unwrap_or_else(|_| "/Users/accusys/momentry/output".to_string());
|
||||
@@ -1117,7 +1122,8 @@ pub async fn exec_search_by_appearance(pool: &sqlx::PgPool, args: &serde_json::V
|
||||
// Get video path from videos table
|
||||
let videos_table = schema::table_name("videos");
|
||||
let video_path: Option<String> = sqlx::query_scalar(&format!(
|
||||
"SELECT file_path FROM {} WHERE file_uuid = $1", videos_table
|
||||
"SELECT file_path FROM {} WHERE file_uuid = $1",
|
||||
videos_table
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.fetch_optional(pool)
|
||||
@@ -1129,29 +1135,33 @@ pub async fn exec_search_by_appearance(pool: &sqlx::PgPool, args: &serde_json::V
|
||||
return Err("Video path not found".to_string());
|
||||
}
|
||||
|
||||
let executor = crate::core::processor::PythonExecutor::new()
|
||||
let executor = crate::core::processor::PythonExecutor::new().map_err(|e| e.to_string())?;
|
||||
|
||||
executor
|
||||
.run(
|
||||
&script_path,
|
||||
&[
|
||||
"--file-uuid",
|
||||
file_uuid,
|
||||
"--color",
|
||||
color,
|
||||
"--video-path",
|
||||
&video_path,
|
||||
"--appearance-path",
|
||||
&appearance_path,
|
||||
"--output",
|
||||
&output_path,
|
||||
],
|
||||
None,
|
||||
"CLOTHING_COLOR_SEARCH",
|
||||
Some(std::time::Duration::from_secs(300)),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
executor.run(
|
||||
&script_path,
|
||||
&[
|
||||
"--file-uuid", file_uuid,
|
||||
"--color", color,
|
||||
"--video-path", &video_path,
|
||||
"--appearance-path", &appearance_path,
|
||||
"--output", &output_path,
|
||||
],
|
||||
None,
|
||||
"CLOTHING_COLOR_SEARCH",
|
||||
Some(std::time::Duration::from_secs(300)),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Read results
|
||||
if std::path::Path::new(&output_path).exists() {
|
||||
let content = std::fs::read_to_string(&output_path)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let content = std::fs::read_to_string(&output_path).map_err(|e| e.to_string())?;
|
||||
Ok(content)
|
||||
} else {
|
||||
Err("Color search output not found".to_string())
|
||||
|
||||
@@ -290,10 +290,7 @@ fn collect_ocr_text(
|
||||
|
||||
/// Group ALL OCR frames by proximity into chunks
|
||||
/// Returns vec of (start_frame, end_frame, combined_ocr_text)
|
||||
fn group_ocr_frames(
|
||||
ocr_map: &BTreeMap<i64, Vec<String>>,
|
||||
_fps: f64,
|
||||
) -> Vec<(i64, i64, String)> {
|
||||
fn group_ocr_frames(ocr_map: &BTreeMap<i64, Vec<String>>, _fps: f64) -> Vec<(i64, i64, String)> {
|
||||
const MAX_FRAME_GAP: i64 = 5; // ~0.2s at 24fps
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::core::db::redis_client::RedisClient;
|
||||
use crate::core::db::schema;
|
||||
use anyhow::{Context, Result};
|
||||
use serde_json::Value;
|
||||
use sqlx::PgPool;
|
||||
use tracing::{info, warn};
|
||||
use std::sync::Arc;
|
||||
use crate::core::db::redis_client::RedisClient;
|
||||
use tracing::{info, warn};
|
||||
|
||||
fn t(name: &str) -> String {
|
||||
let schema = std::env::var("DATABASE_SCHEMA").unwrap_or_else(|_| "dev".to_string());
|
||||
@@ -24,7 +24,12 @@ pub type Rule2ProgressFn = Box<dyn Fn(&str, usize, usize) + Send + Sync>;
|
||||
/// 2. Resolve source/target nodes and identities.
|
||||
/// 3. Generate natural language description (template-based).
|
||||
/// 4. Insert chunks with chunk_type='relationship'.
|
||||
pub async fn ingest_rule2(pool: &PgPool, file_uuid: &str, redis: Option<Arc<RedisClient>>, progress_fn: Option<Rule2ProgressFn>) -> Result<usize> {
|
||||
pub async fn ingest_rule2(
|
||||
pool: &PgPool,
|
||||
file_uuid: &str,
|
||||
redis: Option<Arc<RedisClient>>,
|
||||
progress_fn: Option<Rule2ProgressFn>,
|
||||
) -> Result<usize> {
|
||||
let edges_table = t("tkg_edges");
|
||||
let nodes_table = t("tkg_nodes");
|
||||
let chunk_table = t("chunk");
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::core::chunk::types::{Chunk, ChunkRule, ChunkType};
|
||||
use crate::core::db::qdrant_db::QdrantDb;
|
||||
use crate::core::db::schema;
|
||||
use crate::core::db::PostgresDb;
|
||||
use crate::core::db::qdrant_db::QdrantDb;
|
||||
use anyhow::{Context, Result};
|
||||
use serde_json::json;
|
||||
use sqlx::Row;
|
||||
use tracing::{error, info};
|
||||
use std::collections::HashMap;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub async fn ingest_traces(db: &PostgresDb, file_uuid: &str) -> Result<usize> {
|
||||
let pool = db.pool();
|
||||
@@ -27,21 +27,38 @@ pub async fn ingest_traces(db: &PostgresDb, file_uuid: &str) -> Result<usize> {
|
||||
{"key": "trace_id", "match": {"value": 1}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", face_filter, 500).await.unwrap_or_default();
|
||||
let points = qdrant
|
||||
.scroll_all_points("_faces", face_filter, 500)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut trace_data: HashMap<i32, (i64, i64, f64, f64, i64, f64, f64, f64, f64)> = HashMap::new();
|
||||
let mut trace_data: HashMap<i32, (i64, i64, f64, f64, i64, f64, f64, f64, f64)> =
|
||||
HashMap::new();
|
||||
for point in &points {
|
||||
let payload = &point["payload"];
|
||||
let trace_id = payload["trace_id"].as_i64().unwrap_or(0) as i32;
|
||||
let frame = payload["frame"].as_i64().unwrap_or(0);
|
||||
let timestamp = payload.get("timestamp_secs").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||
let timestamp = payload
|
||||
.get("timestamp_secs")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(0.0);
|
||||
let bbox = &payload["bbox"];
|
||||
let x = bbox["x"].as_f64().unwrap_or(0.0);
|
||||
let y = bbox["y"].as_f64().unwrap_or(0.0);
|
||||
let w = bbox["width"].as_f64().unwrap_or(0.0);
|
||||
let h = bbox["height"].as_f64().unwrap_or(0.0);
|
||||
|
||||
let entry = trace_data.entry(trace_id).or_insert((i64::MAX, i64::MIN, f64::MAX, f64::MIN, 0, 0.0, 0.0, 0.0, 0.0));
|
||||
let entry = trace_data.entry(trace_id).or_insert((
|
||||
i64::MAX,
|
||||
i64::MIN,
|
||||
f64::MAX,
|
||||
f64::MIN,
|
||||
0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
));
|
||||
entry.0 = entry.0.min(frame);
|
||||
entry.1 = entry.1.max(frame);
|
||||
if timestamp > 0.0 {
|
||||
@@ -55,20 +72,33 @@ pub async fn ingest_traces(db: &PostgresDb, file_uuid: &str) -> Result<usize> {
|
||||
entry.8 += h;
|
||||
}
|
||||
|
||||
let traces: Vec<TraceAgg> = trace_data.into_iter().map(|(trace_id, (first_f, last_f, first_t, last_t, count, sum_x, sum_y, sum_w, sum_h))| {
|
||||
TraceAgg {
|
||||
trace_id,
|
||||
first_frame: first_f,
|
||||
last_frame: last_f,
|
||||
first_time: if first_t != f64::MAX { first_t } else { first_f as f64 / fps },
|
||||
last_time: if last_t != f64::MIN { last_t } else { last_f as f64 / fps },
|
||||
face_count: count,
|
||||
avg_x: sum_x / count as f64,
|
||||
avg_y: sum_y / count as f64,
|
||||
avg_w: sum_w / count as f64,
|
||||
avg_h: sum_h / count as f64,
|
||||
}
|
||||
}).collect();
|
||||
let traces: Vec<TraceAgg> = trace_data
|
||||
.into_iter()
|
||||
.map(
|
||||
|(trace_id, (first_f, last_f, first_t, last_t, count, sum_x, sum_y, sum_w, sum_h))| {
|
||||
TraceAgg {
|
||||
trace_id,
|
||||
first_frame: first_f,
|
||||
last_frame: last_f,
|
||||
first_time: if first_t != f64::MAX {
|
||||
first_t
|
||||
} else {
|
||||
first_f as f64 / fps
|
||||
},
|
||||
last_time: if last_t != f64::MIN {
|
||||
last_t
|
||||
} else {
|
||||
last_f as f64 / fps
|
||||
},
|
||||
face_count: count,
|
||||
avg_x: sum_x / count as f64,
|
||||
avg_y: sum_y / count as f64,
|
||||
avg_w: sum_w / count as f64,
|
||||
avg_h: sum_h / count as f64,
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
if traces.is_empty() {
|
||||
info!("No traces found for {}", file_uuid);
|
||||
|
||||
@@ -1325,7 +1325,8 @@ impl PostgresDb {
|
||||
.await?;
|
||||
|
||||
// ── TKG Operation Log ──
|
||||
sqlx::query("CREATE TABLE IF NOT EXISTS tkg_operation_log (
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS tkg_operation_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
file_uuid VARCHAR(32) NOT NULL,
|
||||
operation VARCHAR(20) NOT NULL,
|
||||
@@ -1342,10 +1343,15 @@ impl PostgresDb {
|
||||
started_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
completed_at TIMESTAMPTZ,
|
||||
properties JSONB
|
||||
)").execute(pool).await?;
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_tkg_op_file_uuid ON tkg_operation_log(file_uuid)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
)",
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"CREATE INDEX IF NOT EXISTS idx_tkg_op_file_uuid ON tkg_operation_log(file_uuid)",
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
// ── Functions & Triggers ──
|
||||
sqlx::query(
|
||||
@@ -1902,7 +1908,7 @@ impl PostgresDb {
|
||||
&format!(
|
||||
r#"
|
||||
INSERT INTO {} (uuid, video_path, status, video_id, processors)
|
||||
VALUES ($1, $2, 'pending', $3, ARRAY['asr','cut','ocr','face','face_cluster','pose','asrx'])
|
||||
VALUES ($1, $2, 'pending', $3, ARRAY['cut','asr','face','ocr','asrx','pose','appearance'])
|
||||
RETURNING id, uuid, video_path, status, current_processor, progress_total, progress_current, error_count, last_error, started_at::TEXT, updated_at::TEXT, created_at::TEXT, processors, completed_processors, failed_processors, video_id
|
||||
"#,
|
||||
jobs_table
|
||||
@@ -2775,7 +2781,8 @@ impl PostgresDb {
|
||||
)],
|
||||
) -> Result<()> {
|
||||
let table = schema::table_name("pre_chunks");
|
||||
self.delete_pre_chunks_by_processor(uuid, processor_type).await?;
|
||||
self.delete_pre_chunks_by_processor(uuid, processor_type)
|
||||
.await?;
|
||||
for (frame, ts, data, text, _) in chunks {
|
||||
sqlx::query(&format!(
|
||||
"INSERT INTO {} (file_uuid, processor_type, chunk_type, start_frame, start_time, data, text_content) \
|
||||
@@ -3127,13 +3134,23 @@ impl PostgresDb {
|
||||
let table = schema::table_name("videos");
|
||||
let key = processor.to_uppercase();
|
||||
let path = format!("{{progress,{}}}", key);
|
||||
let status_path = format!("{{progress,{},status}}", key);
|
||||
sqlx::query(&format!(
|
||||
"UPDATE {} SET processing_status = jsonb_set(COALESCE(processing_status, '{{}}'::jsonb), $1::text[], $2::jsonb) WHERE file_uuid = $3", table
|
||||
"UPDATE {} SET processing_status = \
|
||||
CASE \
|
||||
WHEN COALESCE(processing_status #>> $4::text[], '') = 'completed' \
|
||||
THEN processing_status \
|
||||
ELSE jsonb_set(COALESCE(processing_status, '{{}}'::jsonb), $1::text[], $2::jsonb) \
|
||||
END \
|
||||
WHERE file_uuid = $3",
|
||||
table
|
||||
))
|
||||
.bind(&path)
|
||||
.bind(serde_json::json!({"processed": current, "total": total, "status": status}))
|
||||
.bind(uuid)
|
||||
.execute(&self.pool).await?;
|
||||
.bind(&status_path)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -876,7 +876,7 @@ impl QdrantDb {
|
||||
.await?;
|
||||
let batch_len = batch.len();
|
||||
all_points.extend(batch);
|
||||
if batch_len < page_size {
|
||||
if batch_len < page_size || next.is_none() {
|
||||
break;
|
||||
}
|
||||
offset = next;
|
||||
|
||||
+1
-1
@@ -22,6 +22,6 @@ pub mod storage;
|
||||
pub mod text;
|
||||
pub mod thumbnail;
|
||||
pub mod time;
|
||||
pub mod tmdb;
|
||||
pub mod tkg;
|
||||
pub mod tmdb;
|
||||
pub mod vision;
|
||||
|
||||
@@ -33,7 +33,13 @@ pub async fn store_asrx_chunks(db: &PostgresDb, uuid: &str) -> Result<()> {
|
||||
"start_frame": segment.start_frame,
|
||||
"end_frame": segment.end_frame,
|
||||
});
|
||||
pre_chunks.push((segment.start_frame as i64, Some(segment.start_time), data, None, None));
|
||||
pre_chunks.push((
|
||||
segment.start_frame as i64,
|
||||
Some(segment.start_time),
|
||||
data,
|
||||
None,
|
||||
None,
|
||||
));
|
||||
speaker_detections.push((
|
||||
segment.speaker_id.clone().unwrap_or_default(),
|
||||
segment.start_time,
|
||||
|
||||
@@ -159,7 +159,10 @@ pub async fn build_heuristic_scene_meta(
|
||||
{"key": "trace_id", "match": {"value": 1}}
|
||||
]
|
||||
});
|
||||
let points = qdrant.scroll_all_points("_faces", face_filter, 500).await.unwrap_or_default();
|
||||
let points = qdrant
|
||||
.scroll_all_points("_faces", face_filter, 500)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut frame_face_counts: HashMap<i64, i64> = HashMap::new();
|
||||
for point in &points {
|
||||
|
||||
+99
-50
@@ -18,9 +18,9 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PipelineStage {
|
||||
pub name: String,
|
||||
pub weight: f64, // Weight in overall progress (0.0-1.0)
|
||||
pub progress: f64, // Stage progress (0.0-1.0)
|
||||
pub status: String, // "pending", "running", "completed", "failed"
|
||||
pub weight: f64, // Weight in overall progress (0.0-1.0)
|
||||
pub progress: f64, // Stage progress (0.0-1.0)
|
||||
pub status: String, // "pending", "running", "completed", "failed"
|
||||
pub detail: Option<String>,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct PipelineStage {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PipelineProgress {
|
||||
pub file_uuid: String,
|
||||
pub overall_progress: f64, // 0.0-1.0 weighted sum of all stages
|
||||
pub overall_progress: f64, // 0.0-1.0 weighted sum of all stages
|
||||
pub stages: Vec<PipelineStage>,
|
||||
pub updated_at: String,
|
||||
}
|
||||
@@ -40,23 +40,71 @@ impl PipelineProgress {
|
||||
overall_progress: 0.0,
|
||||
stages: vec![
|
||||
// Processors (30% total)
|
||||
PipelineStage { name: "processors".into(), weight: 0.30, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage {
|
||||
name: "processors".into(),
|
||||
weight: 0.30,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
// Post-processor triggers (20% total)
|
||||
PipelineStage { name: "rule1_ingestion".into(), weight: 0.05, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage { name: "face_tracing".into(), weight: 0.05, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage { name: "identity_agent".into(), weight: 0.10, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage {
|
||||
name: "rule1_ingestion".into(),
|
||||
weight: 0.05,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
PipelineStage {
|
||||
name: "face_tracing".into(),
|
||||
weight: 0.05,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
PipelineStage {
|
||||
name: "identity_agent".into(),
|
||||
weight: 0.10,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
// TKG Build (35% total)
|
||||
PipelineStage { name: "tkg_nodes".into(), weight: 0.20, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage { name: "tkg_edges".into(), weight: 0.15, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage {
|
||||
name: "tkg_nodes".into(),
|
||||
weight: 0.20,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
PipelineStage {
|
||||
name: "tkg_edges".into(),
|
||||
weight: 0.15,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
// Rule 2 Ingestion (15%)
|
||||
PipelineStage { name: "rule2_ingestion".into(), weight: 0.15, progress: 0.0, status: "pending".into(), detail: None },
|
||||
PipelineStage {
|
||||
name: "rule2_ingestion".into(),
|
||||
weight: 0.15,
|
||||
progress: 0.0,
|
||||
status: "pending".into(),
|
||||
detail: None,
|
||||
},
|
||||
],
|
||||
updated_at: chrono::Utc::now().to_rfc3339(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a stage's progress and recalculate overall progress
|
||||
pub fn update_stage(&mut self, stage_name: &str, progress: f64, status: &str, detail: Option<String>) {
|
||||
pub fn update_stage(
|
||||
&mut self,
|
||||
stage_name: &str,
|
||||
progress: f64,
|
||||
status: &str,
|
||||
detail: Option<String>,
|
||||
) {
|
||||
if let Some(stage) = self.stages.iter_mut().find(|s| s.name == stage_name) {
|
||||
stage.progress = progress.clamp(0.0, 1.0);
|
||||
stage.status = status.to_string();
|
||||
@@ -67,7 +115,9 @@ impl PipelineProgress {
|
||||
|
||||
/// Recalculate overall progress as weighted sum
|
||||
fn recalculate_overall(&mut self) {
|
||||
self.overall_progress = self.stages.iter()
|
||||
self.overall_progress = self
|
||||
.stages
|
||||
.iter()
|
||||
.map(|s| s.weight * s.progress)
|
||||
.sum::<f64>()
|
||||
.clamp(0.0, 1.0);
|
||||
@@ -90,24 +140,24 @@ impl PipelineProgress {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TkgPhase {
|
||||
FaceTracing = 0, // Phase 0: Populate trace_id from face.json
|
||||
FaceTrackNodes = 1, // Build face_track nodes
|
||||
GazeTrackNodes = 2, // Build gaze_track nodes
|
||||
LipTrackNodes = 3, // Build lip_track nodes
|
||||
TextRegionNodes = 4, // Build text_region nodes
|
||||
AppearanceNodes = 5, // Build appearance_trace nodes
|
||||
AccessoryNodes = 6, // Build accessory nodes
|
||||
ObjectNodes = 7, // Build yolo_object nodes
|
||||
HandNodes = 8, // Build hand nodes
|
||||
SpeakerNodes = 9, // Build speaker nodes
|
||||
CoOccurrenceEdges = 10, // Build co_occurrence edges
|
||||
SpeakerFaceEdges = 11, // Build speaker_face edges
|
||||
FaceFaceEdges = 12, // Build face_face edges
|
||||
MutualGazeEdges = 13, // Build mutual_gaze edges
|
||||
LipSyncEdges = 14, // Build lip_sync edges
|
||||
HasAppearanceEdges = 15,// Build has_appearance edges
|
||||
WearsEdges = 16, // Build wears edges
|
||||
HandObjectEdges = 17, // Build hand_object edges
|
||||
FaceTracing = 0, // Phase 0: Populate trace_id from face.json
|
||||
FaceTrackNodes = 1, // Build face_track nodes
|
||||
GazeTrackNodes = 2, // Build gaze_track nodes
|
||||
LipTrackNodes = 3, // Build lip_track nodes
|
||||
TextRegionNodes = 4, // Build text_region nodes
|
||||
AppearanceNodes = 5, // Build appearance_trace nodes
|
||||
AccessoryNodes = 6, // Build accessory nodes
|
||||
ObjectNodes = 7, // Build yolo_object nodes
|
||||
HandNodes = 8, // Build hand nodes
|
||||
SpeakerNodes = 9, // Build speaker nodes
|
||||
CoOccurrenceEdges = 10, // Build co_occurrence edges
|
||||
SpeakerFaceEdges = 11, // Build speaker_face edges
|
||||
FaceFaceEdges = 12, // Build face_face edges
|
||||
MutualGazeEdges = 13, // Build mutual_gaze edges
|
||||
LipSyncEdges = 14, // Build lip_sync edges
|
||||
HasAppearanceEdges = 15, // Build has_appearance edges
|
||||
WearsEdges = 16, // Build wears edges
|
||||
HandObjectEdges = 17, // Build hand_object edges
|
||||
Completed = 18,
|
||||
Failed = 19,
|
||||
}
|
||||
@@ -282,12 +332,7 @@ impl TkgProgress {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_phase(
|
||||
&mut self,
|
||||
phase: TkgPhase,
|
||||
phase_progress: f64,
|
||||
message: &str,
|
||||
) {
|
||||
pub fn update_phase(&mut self, phase: TkgPhase, phase_progress: f64, message: &str) {
|
||||
self.phase = phase.name().to_string();
|
||||
self.phase_index = phase as usize;
|
||||
self.phase_progress = phase_progress.clamp(0.0, 1.0);
|
||||
@@ -339,12 +384,7 @@ impl AgentProgress {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_phase(
|
||||
&mut self,
|
||||
phase: AgentPhase,
|
||||
phase_progress: f64,
|
||||
message: &str,
|
||||
) {
|
||||
pub fn update_phase(&mut self, phase: AgentPhase, phase_progress: f64, message: &str) {
|
||||
self.phase = phase.name().to_string();
|
||||
self.phase_index = phase as usize;
|
||||
self.phase_progress = phase_progress.clamp(0.0, 1.0);
|
||||
@@ -357,13 +397,21 @@ impl AgentProgress {
|
||||
}
|
||||
|
||||
pub fn mark_completed(&mut self) {
|
||||
self.update_phase(AgentPhase::Completed, 1.0, "Identity Agent processing completed");
|
||||
self.update_phase(
|
||||
AgentPhase::Completed,
|
||||
1.0,
|
||||
"Identity Agent processing completed",
|
||||
);
|
||||
self.overall_progress = 1.0;
|
||||
self.phase_progress = 1.0;
|
||||
}
|
||||
|
||||
pub fn mark_failed(&mut self, error: &str) {
|
||||
self.update_phase(AgentPhase::Failed, 0.0, &format!("Identity Agent failed: {}", error));
|
||||
self.update_phase(
|
||||
AgentPhase::Failed,
|
||||
0.0,
|
||||
&format!("Identity Agent failed: {}", error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,10 +530,7 @@ pub async fn publish_agent_progress(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_progress(
|
||||
redis: &Arc<RedisClient>,
|
||||
file_uuid: &str,
|
||||
) -> Option<CombinedProgress> {
|
||||
pub async fn get_progress(redis: &Arc<RedisClient>, file_uuid: &str) -> Option<CombinedProgress> {
|
||||
let tkg_key = format!(
|
||||
"{}progress:{}:tkg",
|
||||
crate::core::config::REDIS_KEY_PREFIX.as_str(),
|
||||
@@ -542,7 +587,11 @@ pub async fn publish_pipeline_progress(
|
||||
// Merge: update stages from new progress onto existing
|
||||
for new_stage in &progress.stages {
|
||||
if new_stage.status == "completed" || new_stage.progress > 0.0 {
|
||||
if let Some(existing_stage) = existing.stages.iter_mut().find(|s| s.name == new_stage.name) {
|
||||
if let Some(existing_stage) = existing
|
||||
.stages
|
||||
.iter_mut()
|
||||
.find(|s| s.name == new_stage.name)
|
||||
{
|
||||
existing_stage.status = new_stage.status.clone();
|
||||
existing_stage.progress = new_stage.progress;
|
||||
existing_stage.detail = new_stage.detail.clone();
|
||||
|
||||
+65
-29
@@ -23,7 +23,11 @@ impl TkgLogger {
|
||||
.bind(operation)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
tracing::info!("[TKG-Log] Started operation {} for {}", operation, file_uuid);
|
||||
tracing::info!(
|
||||
"[TKG-Log] Started operation {} for {}",
|
||||
operation,
|
||||
file_uuid
|
||||
);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -55,7 +59,11 @@ impl TkgLogger {
|
||||
pub async fn complete_operation(&self, error: Option<&str>) -> Result<()> {
|
||||
if let Some(log_id) = self.log_id {
|
||||
let table = schema::table_name("tkg_operation_log");
|
||||
let status = if error.is_some() { "failed" } else { "completed" };
|
||||
let status = if error.is_some() {
|
||||
"failed"
|
||||
} else {
|
||||
"completed"
|
||||
};
|
||||
sqlx::query(&format!(
|
||||
"UPDATE {table} \
|
||||
SET status = $1, \
|
||||
@@ -68,14 +76,41 @@ impl TkgLogger {
|
||||
.bind(log_id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
tracing::info!("[TKG-Log] Operation {} completed with status: {}", log_id, status);
|
||||
tracing::info!(
|
||||
"[TKG-Log] Operation {} completed with status: {}",
|
||||
log_id,
|
||||
status
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_operations(pool: &PgPool, file_uuid: &str) -> Result<Vec<crate::core::tkg::models::TkgOperationLog>> {
|
||||
pub async fn get_operations(
|
||||
pool: &PgPool,
|
||||
file_uuid: &str,
|
||||
) -> Result<Vec<crate::core::tkg::models::TkgOperationLog>> {
|
||||
let table = schema::table_name("tkg_operation_log");
|
||||
let rows = sqlx::query_as::<_, (i64, String, String, Option<String>, Option<String>, i32, i32, i32, i32, i32, i32, String, Option<String>, String, Option<String>, Option<serde_json::Value>)>(&format!(
|
||||
let rows = sqlx::query_as::<
|
||||
_,
|
||||
(
|
||||
i64,
|
||||
String,
|
||||
String,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
i32,
|
||||
i32,
|
||||
i32,
|
||||
i32,
|
||||
i32,
|
||||
i32,
|
||||
String,
|
||||
Option<String>,
|
||||
String,
|
||||
Option<String>,
|
||||
Option<serde_json::Value>,
|
||||
),
|
||||
>(&format!(
|
||||
"SELECT id, file_uuid, operation, node_type, edge_type, \
|
||||
nodes_created, nodes_updated, nodes_deleted, \
|
||||
edges_created, edges_updated, edges_deleted, \
|
||||
@@ -86,34 +121,35 @@ impl TkgLogger {
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok(rows.into_iter().map(|r| crate::core::tkg::models::TkgOperationLog {
|
||||
id: r.0,
|
||||
file_uuid: r.1,
|
||||
operation: r.2,
|
||||
node_type: r.3,
|
||||
edge_type: r.4,
|
||||
nodes_created: r.5,
|
||||
nodes_updated: r.6,
|
||||
nodes_deleted: r.7,
|
||||
edges_created: r.8,
|
||||
edges_updated: r.9,
|
||||
edges_deleted: r.10,
|
||||
status: r.11,
|
||||
error_message: r.12,
|
||||
started_at: r.13,
|
||||
completed_at: r.14,
|
||||
properties: r.15,
|
||||
}).collect())
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| crate::core::tkg::models::TkgOperationLog {
|
||||
id: r.0,
|
||||
file_uuid: r.1,
|
||||
operation: r.2,
|
||||
node_type: r.3,
|
||||
edge_type: r.4,
|
||||
nodes_created: r.5,
|
||||
nodes_updated: r.6,
|
||||
nodes_deleted: r.7,
|
||||
edges_created: r.8,
|
||||
edges_updated: r.9,
|
||||
edges_deleted: r.10,
|
||||
status: r.11,
|
||||
error_message: r.12,
|
||||
started_at: r.13,
|
||||
completed_at: r.14,
|
||||
properties: r.15,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn delete_operations(pool: &PgPool, file_uuid: &str) -> Result<i64> {
|
||||
let table = schema::table_name("tkg_operation_log");
|
||||
let result = sqlx::query(&format!(
|
||||
"DELETE FROM {table} WHERE file_uuid = $1"
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
let result = sqlx::query(&format!("DELETE FROM {table} WHERE file_uuid = $1"))
|
||||
.bind(file_uuid)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(result.rows_affected() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
pub mod service;
|
||||
pub mod log;
|
||||
pub mod models;
|
||||
pub mod service;
|
||||
|
||||
pub use service::TkgService;
|
||||
pub use log::TkgLogger;
|
||||
pub use models::*;
|
||||
pub use service::TkgService;
|
||||
|
||||
+17
-6
@@ -57,15 +57,26 @@ pub struct TkgBuildStats {
|
||||
|
||||
impl TkgBuildStats {
|
||||
pub fn total_nodes(&self) -> i32 {
|
||||
self.face_track_nodes + self.gaze_track_nodes + self.lip_track_nodes
|
||||
+ self.text_region_nodes + self.appearance_trace_nodes + self.accessory_nodes
|
||||
+ self.object_nodes + self.hand_nodes + self.speaker_nodes
|
||||
self.face_track_nodes
|
||||
+ self.gaze_track_nodes
|
||||
+ self.lip_track_nodes
|
||||
+ self.text_region_nodes
|
||||
+ self.appearance_trace_nodes
|
||||
+ self.accessory_nodes
|
||||
+ self.object_nodes
|
||||
+ self.hand_nodes
|
||||
+ self.speaker_nodes
|
||||
}
|
||||
|
||||
pub fn total_edges(&self) -> i32 {
|
||||
self.co_occurrence_edges + self.speaker_face_edges + self.face_face_edges
|
||||
+ self.mutual_gaze_edges + self.lip_sync_edges + self.has_appearance_edges
|
||||
+ self.wears_edges + self.hand_object_edges
|
||||
self.co_occurrence_edges
|
||||
+ self.speaker_face_edges
|
||||
+ self.face_face_edges
|
||||
+ self.mutual_gaze_edges
|
||||
+ self.lip_sync_edges
|
||||
+ self.has_appearance_edges
|
||||
+ self.wears_edges
|
||||
+ self.hand_object_edges
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
|
||||
+43
-23
@@ -1,5 +1,5 @@
|
||||
use crate::core::db::PostgresDb;
|
||||
use crate::core::db::schema;
|
||||
use crate::core::db::PostgresDb;
|
||||
use crate::core::tkg::log::TkgLogger;
|
||||
use crate::core::tkg::models::{TkgBuildStats, TkgOperationLog};
|
||||
use anyhow::Result;
|
||||
@@ -20,8 +20,11 @@ impl TkgService {
|
||||
let mut logger = TkgLogger::new(self.db.pool().clone());
|
||||
logger.log_id = Some(log_id);
|
||||
|
||||
let redis = crate::core::db::RedisClient::new().ok().map(|r| std::sync::Arc::new(r));
|
||||
let result = crate::core::processor::tkg::build_tkg(&self.db, file_uuid, output_dir, redis).await;
|
||||
let redis = crate::core::db::RedisClient::new()
|
||||
.ok()
|
||||
.map(|r| std::sync::Arc::new(r));
|
||||
let result =
|
||||
crate::core::processor::tkg::build_tkg(&self.db, file_uuid, output_dir, redis).await;
|
||||
|
||||
match result {
|
||||
Ok(r) => {
|
||||
@@ -45,8 +48,12 @@ impl TkgService {
|
||||
hand_object_edges: r.hand_object_edges as i32,
|
||||
};
|
||||
logger.complete_operation(None).await?;
|
||||
tracing::info!("[TKG-Service] Build completed for {}: {} nodes, {} edges",
|
||||
file_uuid, stats.total_nodes(), stats.total_edges());
|
||||
tracing::info!(
|
||||
"[TKG-Service] Build completed for {}: {} nodes, {} edges",
|
||||
file_uuid,
|
||||
stats.total_nodes(),
|
||||
stats.total_edges()
|
||||
);
|
||||
Ok(stats)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -57,7 +64,12 @@ impl TkgService {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rebuild(&self, file_uuid: &str, output_dir: &str, force: bool) -> Result<TkgBuildStats> {
|
||||
pub async fn rebuild(
|
||||
&self,
|
||||
file_uuid: &str,
|
||||
output_dir: &str,
|
||||
force: bool,
|
||||
) -> Result<TkgBuildStats> {
|
||||
let operation = if force { "rebuild_force" } else { "rebuild" };
|
||||
let log_id = TkgLogger::start_operation(self.db.pool(), file_uuid, operation).await?;
|
||||
|
||||
@@ -70,7 +82,11 @@ impl TkgService {
|
||||
// Update the original log entry
|
||||
if let Some(id) = Some(log_id) {
|
||||
let table = schema::table_name("tkg_operation_log");
|
||||
let status = if result.is_ok() { "completed" } else { "failed" };
|
||||
let status = if result.is_ok() {
|
||||
"completed"
|
||||
} else {
|
||||
"failed"
|
||||
};
|
||||
let error = result.as_ref().err().map(|e| e.to_string());
|
||||
sqlx::query(&format!(
|
||||
"UPDATE {table} SET status = $1, error_message = $2, completed_at = NOW() WHERE id = $3"
|
||||
@@ -92,11 +108,15 @@ impl TkgService {
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
TkgLogger::new(self.db.pool().clone()).complete_operation(None).await?;
|
||||
TkgLogger::new(self.db.pool().clone())
|
||||
.complete_operation(None)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
TkgLogger::new(self.db.pool().clone()).complete_operation(Some(&e.to_string())).await?;
|
||||
TkgLogger::new(self.db.pool().clone())
|
||||
.complete_operation(Some(&e.to_string()))
|
||||
.await?;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
@@ -107,23 +127,23 @@ impl TkgService {
|
||||
let edges_table = schema::table_name("tkg_edges");
|
||||
|
||||
// Delete edges first (foreign key constraint)
|
||||
let edges_deleted = sqlx::query(&format!(
|
||||
"DELETE FROM {edges_table} WHERE file_uuid = $1"
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.execute(self.db.pool())
|
||||
.await?;
|
||||
let edges_deleted = sqlx::query(&format!("DELETE FROM {edges_table} WHERE file_uuid = $1"))
|
||||
.bind(file_uuid)
|
||||
.execute(self.db.pool())
|
||||
.await?;
|
||||
|
||||
// Delete nodes
|
||||
let nodes_deleted = sqlx::query(&format!(
|
||||
"DELETE FROM {nodes_table} WHERE file_uuid = $1"
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.execute(self.db.pool())
|
||||
.await?;
|
||||
let nodes_deleted = sqlx::query(&format!("DELETE FROM {nodes_table} WHERE file_uuid = $1"))
|
||||
.bind(file_uuid)
|
||||
.execute(self.db.pool())
|
||||
.await?;
|
||||
|
||||
tracing::info!("[TKG-Service] Deleted {} nodes and {} edges for {}",
|
||||
nodes_deleted.rows_affected(), edges_deleted.rows_affected(), file_uuid);
|
||||
tracing::info!(
|
||||
"[TKG-Service] Deleted {} nodes and {} edges for {}",
|
||||
nodes_deleted.rows_affected(),
|
||||
edges_deleted.rows_affected(),
|
||||
file_uuid
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -69,7 +69,10 @@ pub async fn match_faces_against_tmdb(db: &PostgresDb, file_uuid: &str) -> Resul
|
||||
info!("[TKG-MATCH] No TMDb identity seeds in _seeds collection");
|
||||
return Ok(0);
|
||||
}
|
||||
info!("[TKG-MATCH] {} TMDb seeds loaded from _seeds", tmdb_rows.len());
|
||||
info!(
|
||||
"[TKG-MATCH] {} TMDb seeds loaded from _seeds",
|
||||
tmdb_rows.len()
|
||||
);
|
||||
|
||||
// Step 2: Load face embeddings from Qdrant _faces, grouped by trace_id
|
||||
let face_filter = serde_json::json!({
|
||||
@@ -81,7 +84,10 @@ pub async fn match_faces_against_tmdb(db: &PostgresDb, file_uuid: &str) -> Resul
|
||||
let face_points = match qdrant.scroll_all_points("_faces", face_filter, 1000).await {
|
||||
Ok(pts) => pts,
|
||||
Err(e) => {
|
||||
warn!("[TKG-MATCH] Failed to scroll _faces for {}: {}", file_uuid, e);
|
||||
warn!(
|
||||
"[TKG-MATCH] Failed to scroll _faces for {}: {}",
|
||||
file_uuid, e
|
||||
);
|
||||
return Ok(0);
|
||||
}
|
||||
};
|
||||
@@ -118,7 +124,11 @@ pub async fn match_faces_against_tmdb(db: &PostgresDb, file_uuid: &str) -> Resul
|
||||
}
|
||||
|
||||
let total = trace_faces.len();
|
||||
info!("[TKG-MATCH] {} traces with {} faces", total, face_points.len());
|
||||
info!(
|
||||
"[TKG-MATCH] {} traces with {} faces",
|
||||
total,
|
||||
face_points.len()
|
||||
);
|
||||
|
||||
// Step 3: Iterative matching
|
||||
const TH: f32 = 0.50;
|
||||
@@ -341,7 +351,9 @@ async fn quality_check_temporal_collisions_qdrant(
|
||||
]
|
||||
});
|
||||
let payload = serde_json::json!({"identity_id": serde_json::Value::Null});
|
||||
let _ = qdrant.update_payload_by_filter("_faces", filter, payload).await;
|
||||
let _ = qdrant
|
||||
.update_payload_by_filter("_faces", filter, payload)
|
||||
.await;
|
||||
|
||||
unbound += 1;
|
||||
warn!("[TKG-QC] Collision identity={}: trace {} vs trace {} ({} overlap frames). Unbound trace {} ({} points)",
|
||||
|
||||
Reference in New Issue
Block a user