diff --git a/src/worker/job_worker.rs b/src/worker/job_worker.rs index 4e81e4c..d585e24 100644 --- a/src/worker/job_worker.rs +++ b/src/worker/job_worker.rs @@ -278,6 +278,13 @@ impl JobWorker { } async fn process_job(&self, job: crate::core::db::MonitorJob) -> Result<()> { + // Check if job still exists in database (may have been deleted by unregister) + let current_job = self.db.get_monitor_job_by_uuid(&job.uuid).await?; + if current_job.is_none() { + info!("Job {} no longer exists in database (possibly unregistered), skipping", job.uuid); + return Ok(()); + } + info!("Processing job: {} ({})", job.uuid, job.id); // Determine which processors to run based on job.processors field @@ -1833,39 +1840,53 @@ impl JobWorker { // 🚀 P4 Trigger: TKG Build (Face + ASRX) → then Rule2 ingestion if has_face && has_asrx { - info!("📝 Prerequisites met for TKG Build. Starting graph construction..."); - let db_clone = self.db.clone(); - let redis_clone = self.redis.clone(); - let uuid_clone = uuid.to_string(); - let output_dir_clone = crate::core::config::OUTPUT_DIR.clone(); - tokio::spawn(async move { - match crate::core::processor::tkg::build_tkg(&db_clone, &uuid_clone, &output_dir_clone, Some(redis_clone.clone())).await { - Ok(r) => { - let total_nodes = r.face_track_nodes + r.gaze_track_nodes + r.lip_track_nodes + r.text_region_nodes + r.appearance_trace_nodes + r.accessory_nodes + r.object_nodes + r.hand_nodes + r.speaker_nodes; - let total_edges = r.co_occurrence_edges + r.speaker_face_edges + r.face_face_edges + r.mutual_gaze_edges + r.lip_sync_edges + r.has_appearance_edges + r.wears_edges + r.hand_object_edges; - info!("✅ TKG build completed for {}: {} nodes, {} edges", uuid_clone, total_nodes, total_edges); + // Guard: only spawn TKG if nodes don't exist yet + let tkg_nodes_table = schema::table_name("tkg_nodes"); + let tkg_exists: bool = sqlx::query_scalar::<_, i32>(&format!( + "SELECT 1 FROM {tkg_nodes_table} WHERE file_uuid = $1 LIMIT 1" + )) + .bind(uuid) + .fetch_optional(self.db.pool()) + .await? + .unwrap_or(0) > 0; - let mut pp = PipelineProgress::new(&uuid_clone); - pp.update_stage("tkg_nodes", 1.0, "completed", Some(format!("{} nodes", total_nodes))); - pp.update_stage("tkg_edges", 1.0, "completed", Some(format!("{} edges", total_edges))); - publish_pipeline_progress(redis_clone.as_ref(), &uuid_clone, &pp).await; + if tkg_exists { + info!("✅ TKG already built for {}, skipping", uuid); + } else { + info!("📝 Prerequisites met for TKG Build. Starting graph construction..."); + let db_clone = self.db.clone(); + let redis_clone = self.redis.clone(); + let uuid_clone = uuid.to_string(); + let output_dir_clone = crate::core::config::OUTPUT_DIR.clone(); + tokio::spawn(async move { + match crate::core::processor::tkg::build_tkg(&db_clone, &uuid_clone, &output_dir_clone, Some(redis_clone.clone())).await { + Ok(r) => { + let total_nodes = r.face_track_nodes + r.gaze_track_nodes + r.lip_track_nodes + r.text_region_nodes + r.appearance_trace_nodes + r.accessory_nodes + r.object_nodes + r.hand_nodes + r.speaker_nodes; + let total_edges = r.co_occurrence_edges + r.speaker_face_edges + r.face_face_edges + r.mutual_gaze_edges + r.lip_sync_edges + r.has_appearance_edges + r.wears_edges + r.hand_object_edges; + info!("✅ TKG build completed for {}: {} nodes, {} edges", uuid_clone, total_nodes, total_edges); - // Trigger Rule 2 ingestion after TKG complete - if total_edges > 0 { - match crate::core::chunk::rule2_ingest::ingest_rule2(db_clone.pool(), &uuid_clone, None, None).await { - Ok(rule2_count) => { - info!("✅ Rule 2 ingestion completed for {}: {} relationship chunks", uuid_clone, rule2_count); - let mut pp = PipelineProgress::new(&uuid_clone); - pp.update_stage("rule2_ingestion", 1.0, "completed", Some(format!("{} chunks", rule2_count))); - publish_pipeline_progress(redis_clone.as_ref(), &uuid_clone, &pp).await; + let mut pp = PipelineProgress::new(&uuid_clone); + pp.update_stage("tkg_nodes", 1.0, "completed", Some(format!("{} nodes", total_nodes))); + pp.update_stage("tkg_edges", 1.0, "completed", Some(format!("{} edges", total_edges))); + publish_pipeline_progress(redis_clone.as_ref(), &uuid_clone, &pp).await; + + // Trigger Rule 2 ingestion after TKG complete + if total_edges > 0 { + match crate::core::chunk::rule2_ingest::ingest_rule2(db_clone.pool(), &uuid_clone, None, None).await { + Ok(rule2_count) => { + info!("✅ Rule 2 ingestion completed for {}: {} relationship chunks", uuid_clone, rule2_count); + let mut pp = PipelineProgress::new(&uuid_clone); + pp.update_stage("rule2_ingestion", 1.0, "completed", Some(format!("{} chunks", rule2_count))); + publish_pipeline_progress(redis_clone.as_ref(), &uuid_clone, &pp).await; + } + Err(e) => error!("❌ Rule 2 ingestion failed for {}: {}", uuid_clone, e), } - Err(e) => error!("❌ Rule 2 ingestion failed for {}: {}", uuid_clone, e), } } + Err(e) => error!("❌ TKG build failed for {}: {}", uuid_clone, e), } - Err(e) => error!("❌ TKG build failed for {}: {}", uuid_clone, e), - } - }); + }); + } } if !Self::ingestion_complete(self.db.pool(), uuid, job_processors).await {