feat: Phase 2.7 identity resolution for gaze/lip trace nodes
Implementation: - gaze_trace nodes: Query face_trace identity_id, add to properties - lip_trace nodes: Query face_trace identity_id, add to properties - Rule2: Extend identity resolution to support gaze_trace/lip_trace node types Architecture: - All face-related nodes now have identity_id in TKG properties - Rule2 unified identity resolution for face_trace/gaze_trace/lip_trace - TKG-only approach (no face_detections dependency for identity) Code Changes: - src/core/processor/tkg.rs: Add identity_id query in gaze/lip builders - src/core/chunk/rule2_ingest.rs: Extend node_type condition Docs: - docs_v1.0/DESIGN/TKG_PHASE2_7_IDENTITY_RESOLUTION.md Status: Implementation complete, pending test with valid file
This commit is contained in:
@@ -99,15 +99,16 @@ pub async fn ingest_rule2(pool: &PgPool, file_uuid: &str) -> Result<usize> {
|
||||
let (src_type, src_ext_id, src_label, _src_props) = source_node.unwrap();
|
||||
let (tgt_type, tgt_ext_id, tgt_label, tgt_props) = target_node.unwrap();
|
||||
|
||||
// Resolve identity names for face_trace nodes (Phase 2.3: TKG-only)
|
||||
let src_identity: Option<String> = if src_type == "face_trace" {
|
||||
// Resolve identity names for face_trace/gaze_trace/lip_trace nodes (Phase 2.7)
|
||||
let src_identity: Option<String> = if src_type == "face_trace" || src_type == "gaze_trace" || src_type == "lip_trace" {
|
||||
sqlx::query_scalar(&format!(
|
||||
"SELECT i.name FROM {} n \
|
||||
JOIN {} i ON i.id = (n.properties->>'identity_id')::bigint \
|
||||
WHERE n.file_uuid = $1 AND n.node_type = 'face_trace' AND n.external_id = $2 AND n.properties->>'identity_id' IS NOT NULL",
|
||||
WHERE n.file_uuid = $1 AND n.node_type = $2 AND n.external_id = $3 AND n.properties->>'identity_id' IS NOT NULL",
|
||||
nodes_table, id_table
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.bind(&src_type)
|
||||
.bind(&src_ext_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
@@ -115,14 +116,15 @@ pub async fn ingest_rule2(pool: &PgPool, file_uuid: &str) -> Result<usize> {
|
||||
None
|
||||
};
|
||||
|
||||
let tgt_identity: Option<String> = if tgt_type == "face_trace" {
|
||||
let tgt_identity: Option<String> = if tgt_type == "face_trace" || tgt_type == "gaze_trace" || tgt_type == "lip_trace" {
|
||||
sqlx::query_scalar(&format!(
|
||||
"SELECT i.name FROM {} n \
|
||||
JOIN {} i ON i.id = (n.properties->>'identity_id')::bigint \
|
||||
WHERE n.file_uuid = $1 AND n.node_type = 'face_trace' AND n.external_id = $2 AND n.properties->>'identity_id' IS NOT NULL",
|
||||
WHERE n.file_uuid = $1 AND n.node_type = $2 AND n.external_id = $3 AND n.properties->>'identity_id' IS NOT NULL",
|
||||
nodes_table, id_table
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.bind(&tgt_type)
|
||||
.bind(&tgt_ext_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
|
||||
@@ -1873,7 +1873,18 @@ async fn build_gaze_trace_nodes_from_qdrant(
|
||||
for (tid, frames) in &trace_frames {
|
||||
let external_id = format!("gaze_{}", tid);
|
||||
|
||||
// Compute gaze stats for this trace
|
||||
// Phase 2.7: Query face_trace identity_id
|
||||
let face_ext_id = format!("trace_{}", tid);
|
||||
let face_identity_id: Option<i64> = sqlx::query_scalar(&format!(
|
||||
"SELECT (properties->>'identity_id')::bigint FROM {}
|
||||
WHERE file_uuid=$1 AND node_type='face_trace' AND external_id=$2",
|
||||
nodes_table
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.bind(&face_ext_id)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
let mut frame_count = 0i64;
|
||||
let mut first_frame = i64::MAX;
|
||||
let mut last_frame = i64::MIN;
|
||||
@@ -1897,11 +1908,9 @@ async fn build_gaze_trace_nodes_from_qdrant(
|
||||
pitch_sum += pitch;
|
||||
roll_sum += roll;
|
||||
|
||||
// Gaze direction
|
||||
let gaze_dir = GazeDirection::from_yaw_pitch(yaw, pitch);
|
||||
*gaze_dir_counts.entry(gaze_dir.as_str()).or_default() += 1;
|
||||
|
||||
// Blink detection (eye openness from pitch variance)
|
||||
let openness = (pitch.abs() * 10.0).min(1.0);
|
||||
if prev_openness > 0.5 && openness < 0.2 {
|
||||
blink_candidates += 1;
|
||||
@@ -1934,6 +1943,7 @@ async fn build_gaze_trace_nodes_from_qdrant(
|
||||
|
||||
let props = serde_json::json!({
|
||||
"trace_id": tid,
|
||||
"identity_id": face_identity_id,
|
||||
"frame_count": frame_count,
|
||||
"start_frame": first_frame,
|
||||
"end_frame": last_frame,
|
||||
@@ -2401,6 +2411,18 @@ async fn build_lip_trace_nodes_from_qdrant(
|
||||
for (tid, frames) in &lip_data {
|
||||
let external_id = format!("lip_{}", tid);
|
||||
|
||||
// Phase 2.7: Query face_trace identity_id
|
||||
let face_ext_id = format!("trace_{}", tid);
|
||||
let face_identity_id: Option<i64> = sqlx::query_scalar(&format!(
|
||||
"SELECT (properties->>'identity_id')::bigint FROM {}
|
||||
WHERE file_uuid=$1 AND node_type='face_trace' AND external_id=$2",
|
||||
nodes_table
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.bind(&face_ext_id)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
let frame_count = frames.len() as i64;
|
||||
let first_frame = frames.iter().map(|(f, _, _)| *f).min().unwrap_or(0);
|
||||
let last_frame = frames.iter().map(|(f, _, _)| *f).max().unwrap_or(0);
|
||||
@@ -2413,7 +2435,6 @@ async fn build_lip_trace_nodes_from_qdrant(
|
||||
0.0
|
||||
};
|
||||
|
||||
// Compute movement variance
|
||||
let openness_values: Vec<f64> = frames
|
||||
.iter()
|
||||
.map(|(_, i, o)| if *o > 0.0 { i / o } else { 0.0 })
|
||||
@@ -2425,7 +2446,6 @@ async fn build_lip_trace_nodes_from_qdrant(
|
||||
.sum::<f64>()
|
||||
/ openness_values.len() as f64;
|
||||
|
||||
// Count speaking frames (openness > threshold)
|
||||
let speaking_threshold = avg_openness * 1.2;
|
||||
let speaking_frames = frames
|
||||
.iter()
|
||||
@@ -2438,7 +2458,6 @@ async fn build_lip_trace_nodes_from_qdrant(
|
||||
})
|
||||
.count() as i64;
|
||||
|
||||
// Get pose for this trace
|
||||
let (avg_yaw, avg_pitch) = if let Some((y, p, _)) = frames
|
||||
.iter()
|
||||
.filter_map(|(f, _, _)| {
|
||||
@@ -2456,6 +2475,7 @@ async fn build_lip_trace_nodes_from_qdrant(
|
||||
|
||||
let props = serde_json::json!({
|
||||
"trace_id": tid,
|
||||
"identity_id": face_identity_id,
|
||||
"frame_count": frame_count,
|
||||
"start_frame": first_frame,
|
||||
"end_frame": last_frame,
|
||||
|
||||
Reference in New Issue
Block a user