feat: Phase 2.6 edges migration to Qdrant (TKG-only architecture)
Phase 2.6.1: co_occurrence_edges migration - build_co_occurrence_edges_from_qdrant() - Qdrant embeddings → frame grouping → YOLO objects - Result: 6679 edges (vs 6701 PostgreSQL) Phase 2.6.2: face_face_edges migration - build_face_face_edges_from_qdrant() - Qdrant embeddings → frame grouping → face pairs - mutual_gaze detection preserved - Result: 6 edges (exact match) Phase 2.6.3: speaker_face_edges migration - build_speaker_face_edges_from_qdrant() - Qdrant embeddings → trace_id frame ranges - SPEAKS_AS edge creation Architecture: - All edges use Qdrant payload (no face_detections queries) - PostgreSQL fallback for empty Qdrant - Estimated 3.6x performance improvement Testing: - Playground (3003): ✓ All Phase 2.6 logs verified - Edge counts: ✓ Close match with PostgreSQL - Fallback: ✓ Working Docs: - docs_v1.0/DESIGN/TKG_PHASE2_6_EDGES_MIGRATION.md - docs_v1.0/M4_workspace/2026-06-21_phase2_6_test.md
This commit is contained in:
@@ -2,7 +2,7 @@ use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
routing::post,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -578,6 +578,127 @@ async fn watcher_auto_register_toggle(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ProcessorCountInfo {
|
||||
processor: String,
|
||||
has_json: bool,
|
||||
frame_count: Option<u32>,
|
||||
segment_count: Option<u32>,
|
||||
chunk_count: Option<u32>,
|
||||
last_modified: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ProcessorCountsResponse {
|
||||
file_uuid: String,
|
||||
output_dir: String,
|
||||
processors: Vec<ProcessorCountInfo>,
|
||||
}
|
||||
|
||||
async fn get_processor_counts(
|
||||
State(state): State<AppState>,
|
||||
Path(file_uuid): Path<String>,
|
||||
) -> Result<Json<ProcessorCountsResponse>, StatusCode> {
|
||||
let videos_table = schema::table_name("videos");
|
||||
let full_uuid: Option<String> = sqlx::query_scalar(&format!(
|
||||
"SELECT file_uuid FROM {} WHERE file_uuid = $1 OR file_uuid LIKE $2",
|
||||
videos_table
|
||||
))
|
||||
.bind(&file_uuid)
|
||||
.bind(&format!("{}%", file_uuid))
|
||||
.fetch_optional(state.db.pool())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("DB error: {}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?
|
||||
.or_else(|| {
|
||||
if file_uuid.len() == 32 {
|
||||
Some(file_uuid.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let file_uuid = full_uuid.ok_or(StatusCode::NOT_FOUND)?;
|
||||
let output_dir = std::env::var("MOMENTRY_OUTPUT_DIR")
|
||||
.unwrap_or_else(|_| "/Users/accusys/momentry/output_dev".to_string());
|
||||
|
||||
let processors = crate::core::db::ProcessorType::all();
|
||||
let mut results = Vec::new();
|
||||
|
||||
for processor in &processors {
|
||||
let proc_name = processor.as_str();
|
||||
let json_path =
|
||||
std::path::Path::new(&output_dir).join(format!("{}.{}.json", file_uuid, proc_name));
|
||||
|
||||
let has_json = json_path.exists();
|
||||
let mut frame_count = None;
|
||||
let mut segment_count = None;
|
||||
let mut chunk_count = None;
|
||||
let mut last_modified = None;
|
||||
|
||||
if has_json {
|
||||
if let Ok(metadata) = std::fs::metadata(&json_path) {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
let chrono_dt: chrono::DateTime<chrono::Utc> = modified.into();
|
||||
last_modified = Some(chrono_dt.to_rfc3339());
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(content) = std::fs::read_to_string(&json_path) {
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
frame_count = json
|
||||
.get("frame_count")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| v as u32);
|
||||
segment_count = json
|
||||
.get("segments")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.len() as u32);
|
||||
chunk_count = json
|
||||
.get("child_chunks")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.len() as u32)
|
||||
.or_else(|| {
|
||||
json.get("parent_chunks")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.len() as u32)
|
||||
});
|
||||
if chunk_count.is_none() {
|
||||
chunk_count = json
|
||||
.get("chunks")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.len() as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.push(ProcessorCountInfo {
|
||||
processor: proc_name.to_string(),
|
||||
has_json,
|
||||
frame_count,
|
||||
segment_count,
|
||||
chunk_count,
|
||||
last_modified,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(ProcessorCountsResponse {
|
||||
file_uuid,
|
||||
output_dir,
|
||||
processors: results,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn verify_file_handler(
|
||||
Path(file_uuid): Path<String>,
|
||||
) -> Json<crate::verification::FileVerificationReport> {
|
||||
let report = crate::verification::verifier::verify_file(&file_uuid);
|
||||
Json(report)
|
||||
}
|
||||
|
||||
pub fn processing_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/api/v1/file/:file_uuid/process", post(trigger_processing))
|
||||
@@ -597,4 +718,9 @@ pub fn processing_routes() -> Router<AppState> {
|
||||
"/api/v1/config/watcher-auto-register",
|
||||
post(watcher_auto_register_toggle),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/file/:file_uuid/processor-counts",
|
||||
get(get_processor_counts),
|
||||
)
|
||||
.route("/api/v1/file/:file_uuid/verify", get(verify_file_handler))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user