release: v1.3.0 - TKG node type renaming

Changes:
- Rust: face_trace → face_track (45 occurrences in 8 files)
- Rust: gaze_trace → gaze_track, lip_trace → lip_track
- Python: tkg_builder.py unified + pipeline_checklist.py fixed
- Swift: swift_hand.swift hand state detection (empty vs holding)

Node type changes:
  face_trace    → face_track
  person_trace  → body_track
  gaze_trace    → gaze_track
  lip_trace     → lip_track
  hand_trace    → hand_track
  speaker       → speaker_segment
  object        → detected_object
  text_trace    → text_region

Migration:
  PUBLIC schema: 12970 + 892 + 305 rows updated
This commit is contained in:
Accusys
2026-06-22 07:18:21 +08:00
parent bce9435823
commit 7e548f8b08
35 changed files with 2789 additions and 481 deletions
+80 -35
View File
@@ -743,7 +743,9 @@ impl JobWorker {
continue;
}
ProcessorJobStatus::Failed => {
if result.retry_count >= 3 {
if result.retry_count >= 3
&& !crate::core::config::processor::FORCE_RETRY.clone()
{
info!(
"Processor {} failed {} times, max retries reached (3), skipping",
processor_type.as_str(),
@@ -752,11 +754,19 @@ impl JobWorker {
started_count += 1;
continue;
}
info!(
"Processor {} previously failed (retry {}/3), retrying",
if crate::core::config::processor::FORCE_RETRY.clone() {
info!(
"Processor {} previously failed (retry {}), FORCE_RETRY enabled, retrying",
processor_type.as_str(),
result.retry_count + 1
result.retry_count
);
} else {
info!(
"Processor {} previously failed (retry {}/3), retrying",
processor_type.as_str(),
result.retry_count + 1
);
}
let _ = sqlx::query(&format!(
"UPDATE {} SET retry_count = retry_count + 1 WHERE job_id = $1 AND processor = $2",
schema::table_name("processor_results")
@@ -988,17 +998,6 @@ impl JobWorker {
let chunk_t = schema::table_name("chunk");
let fd_t = schema::table_name("face_detections");
macro_rules! check {
($sql:expr) => {
sqlx::query_scalar::<_, i32>($sql)
.fetch_one(pool)
.await
.unwrap_or(0)
> 0
};
}
let fu = uuid;
// Only check conditions relevant to the job's processors
let has_asr_or_asrx =
job_processors.is_empty() || job_processors.iter().any(|p| p == "asrx" || p == "asr");
@@ -1006,21 +1005,57 @@ impl JobWorker {
let has_face = job_processors.is_empty() || job_processors.iter().any(|p| p == "face");
let rule1 = !has_asr_or_asrx
|| check!(&format!(
"SELECT 1 FROM {chunk_t} WHERE file_uuid = '{fu}' AND chunk_type = 'sentence' LIMIT 1"
));
|| sqlx::query_scalar::<_, i32>(&format!(
"SELECT 1 FROM {chunk_t} WHERE file_uuid = $1 AND chunk_type = 'sentence' LIMIT 1"
))
.bind(uuid)
.fetch_optional(pool)
.await
.unwrap_or(None)
.unwrap_or(0)
> 0;
let vector = !has_asr_or_asrx
|| check!(&format!("SELECT 1 FROM {chunk_t} WHERE file_uuid = '{fu}' AND chunk_type = 'sentence' AND embedding IS NOT NULL LIMIT 1"));
|| sqlx::query_scalar::<_, i32>(&format!(
"SELECT 1 FROM {chunk_t} WHERE file_uuid = $1 AND chunk_type = 'sentence' AND embedding IS NOT NULL LIMIT 1"
))
.bind(uuid)
.fetch_optional(pool)
.await
.unwrap_or(None)
.unwrap_or(0)
> 0;
let rule3 = !has_cut
|| check!(&format!(
"SELECT 1 FROM {chunk_t} WHERE file_uuid = '{fu}' AND chunk_type = 'cut' LIMIT 1"
));
|| sqlx::query_scalar::<_, i32>(&format!(
"SELECT 1 FROM {chunk_t} WHERE file_uuid = $1 AND chunk_type = 'cut' LIMIT 1"
))
.bind(uuid)
.fetch_optional(pool)
.await
.unwrap_or(None)
.unwrap_or(0)
> 0;
let trace = !has_face
|| check!(&format!("SELECT COUNT(DISTINCT trace_id) FROM {fd_t} WHERE file_uuid = '{fu}' AND trace_id IS NOT NULL"));
|| sqlx::query_scalar::<_, i64>(&format!(
"SELECT COUNT(DISTINCT trace_id) FROM {fd_t} WHERE file_uuid = $1 AND trace_id IS NOT NULL"
))
.bind(uuid)
.fetch_one(pool)
.await
.unwrap_or(0)
> 0;
let all_ok = rule1 && vector && rule3 && trace;
if !all_ok {
tracing::info!(
"[Ingestion] waiting (uuid={fu}): rule1={rule1} vector={vector} rule3={rule3} trace={trace}"
"[Ingestion] waiting (uuid={}): rule1={} vector={} rule3={} trace={}",
uuid,
rule1,
vector,
rule3,
trace
);
}
all_ok
@@ -1057,18 +1092,22 @@ impl JobWorker {
let all_completed = results
.iter()
.filter(|r| job_processors.contains(&r.processor_type.as_str().to_string()))
.all(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Completed));
let any_failed = results
.iter()
.filter(|r| job_processors.contains(&r.processor_type.as_str().to_string()))
.any(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Failed));
let any_pending = results
.iter()
.filter(|r| job_processors.contains(&r.processor_type.as_str().to_string()))
.any(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Pending));
let any_skipped = results
.iter()
.filter(|r| job_processors.contains(&r.processor_type.as_str().to_string()))
.any(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Skipped));
let completed_count = results
@@ -1101,7 +1140,9 @@ impl JobWorker {
.map(|r| r.processor_type.as_str().to_string())
.collect();
let has_asrx = completed_processors.iter().any(|p| p == "asrx");
let has_asr_or_asrx = completed_processors
.iter()
.any(|p| p == "asrx" || p == "asr");
let has_cut = completed_processors.iter().any(|p| p == "cut");
let has_face = completed_processors.iter().any(|p| p == "face");
let has_yolo = completed_processors.iter().any(|p| p == "yolo");
@@ -1110,7 +1151,7 @@ impl JobWorker {
.update_job_processors_arrays(job_id, completed_processors, failed_processors.clone())
.await?;
if has_asrx {
if has_asr_or_asrx {
// Guard: only spawn Rule 1 if sentence chunks don't exist yet
let chunk_t = schema::table_name("chunk");
let already_spawned: bool = sqlx::query_scalar::<_, i32>(&format!(
@@ -1321,7 +1362,7 @@ impl JobWorker {
}
// 🚀 P3 Trigger: Identity Agent (Face + ASRX)
if has_face && has_asrx {
if has_face && has_asr_or_asrx {
info!("📝 Prerequisites met for Identity Agent. Starting analysis...");
let db_clone = self.db.clone();
let uuid_clone = uuid.to_string();
@@ -1513,21 +1554,22 @@ impl JobWorker {
let pool = db.pool();
let chunk_table = schema::table_name("chunk");
let rows = sqlx::query_as::<_, (String, String, i64, i64, f64, f64)>(
&format!(
"SELECT chunk_id, text_content, start_frame, end_frame, start_time, end_time \
let rows = sqlx::query_as::<_, (String, String, i64, i64, f64, f64)>(&format!(
"SELECT chunk_id, text_content, start_frame, end_frame, start_time, end_time \
FROM {} WHERE file_uuid = $1 AND chunk_type = 'relationship' \
AND embedding IS NULL AND (text_content IS NOT NULL AND text_content != '') \
ORDER BY id",
chunk_table
),
)
chunk_table
))
.bind(uuid)
.fetch_all(pool)
.await?;
if rows.is_empty() {
info!("[Vectorize-R2] No relationship chunks to vectorize for {}", uuid);
info!(
"[Vectorize-R2] No relationship chunks to vectorize for {}",
uuid
);
return Ok(());
}
@@ -1560,7 +1602,10 @@ impl JobWorker {
text: Some(text.clone()),
};
if let Err(e) = qdrant.upsert_vector(&chunk_id, &vector, payload).await {
error!("[Vectorize-R2] Qdrant upsert failed for {}: {}", chunk_id, e);
error!(
"[Vectorize-R2] Qdrant upsert failed for {}: {}",
chunk_id, e
);
continue;
}
stored += 1;