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:
@@ -426,6 +426,7 @@ async fn process_yolo_module(
|
||||
video_path,
|
||||
yolo_path.to_str().unwrap(),
|
||||
Some(uuid),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let yolo_json = serde_json::to_string_pretty(&yolo_result)?;
|
||||
@@ -460,6 +461,7 @@ async fn process_ocr_module(
|
||||
video_path,
|
||||
ocr_path.to_str().unwrap(),
|
||||
Some(uuid),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let ocr_json = serde_json::to_string_pretty(&ocr_result)?;
|
||||
@@ -497,6 +499,7 @@ async fn process_face_module(
|
||||
video_path,
|
||||
face_path.to_str().unwrap(),
|
||||
Some(uuid),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let face_json = serde_json::to_string_pretty(&face_result)?;
|
||||
@@ -531,6 +534,7 @@ async fn process_pose_module(
|
||||
video_path,
|
||||
pose_path.to_str().unwrap(),
|
||||
Some(uuid),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let pose_json = serde_json::to_string_pretty(&pose_result)?;
|
||||
@@ -551,6 +555,47 @@ async fn process_pose_module(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_appearance_module(
|
||||
appearance_path: &Path,
|
||||
video_path: &str,
|
||||
pose_path: &Path,
|
||||
uuid: &str,
|
||||
progress_state: &Arc<Mutex<ProgressState>>,
|
||||
ui: &Arc<Mutex<Option<ProgressUi>>>,
|
||||
) -> anyhow::Result<()> {
|
||||
{
|
||||
let mut state = progress_state.lock().unwrap();
|
||||
state.get_processor(ProcessorType::Appearance).start(1);
|
||||
}
|
||||
let appearance_result = momentry_core::core::processor::process_appearance(
|
||||
video_path,
|
||||
pose_path.to_str().unwrap(),
|
||||
appearance_path.to_str().unwrap(),
|
||||
Some(uuid),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let appearance_json = serde_json::to_string_pretty(&appearance_result)?;
|
||||
std::fs::write(appearance_path, &appearance_json)?;
|
||||
let output_dir = OutputDir::new();
|
||||
let _ = output_dir.backup_file(uuid, "appearance.json");
|
||||
println!(
|
||||
" ✓ Appearance saved: {} frames",
|
||||
appearance_result.frame_count
|
||||
);
|
||||
{
|
||||
let mut state = progress_state.lock().unwrap();
|
||||
state
|
||||
.get_processor(ProcessorType::Appearance)
|
||||
.complete(&format!("{} frames", appearance_result.frame_count));
|
||||
state.stop();
|
||||
}
|
||||
if let Some(ref mut ui) = *ui.lock().unwrap() {
|
||||
let _ = ui.render();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_story_module(
|
||||
story_path: &Path,
|
||||
video_path: &str,
|
||||
@@ -643,7 +688,7 @@ enum Commands {
|
||||
Process {
|
||||
/// UUID or path
|
||||
target: String,
|
||||
/// Modules to process (comma separated: asr,cut,asrx,yolo,ocr,face,pose,story,caption)
|
||||
/// Modules to process (comma separated: appearance,asr,cut,asrx,yolo,ocr,face,pose,story,caption)
|
||||
/// If not specified, processes all modules
|
||||
#[arg(short, long, value_delimiter = ',')]
|
||||
modules: Option<Vec<String>>,
|
||||
@@ -826,9 +871,11 @@ enum N8nAction {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Load development environment — try absolute path first
|
||||
if dotenv::from_filename("/Users/accusys/momentry_core_0.1/.env.development").is_err() {
|
||||
// Fallback to relative path (for development)
|
||||
let _ = dotenv::from_filename(".env.development");
|
||||
let env_loaded = dotenv::from_filename("/Users/accusys/momentry_core_0.1/.env.development")
|
||||
.is_ok()
|
||||
|| dotenv::from_filename(".env.development").is_ok();
|
||||
if !env_loaded {
|
||||
eprintln!("[WARN] No .env.development found, using defaults or env vars");
|
||||
}
|
||||
|
||||
tracing_subscriber::fmt::init();
|
||||
@@ -839,6 +886,10 @@ async fn main() -> Result<()> {
|
||||
"Redis prefix: {}",
|
||||
*momentry_core::core::config::REDIS_KEY_PREFIX
|
||||
);
|
||||
tracing::info!(
|
||||
"Database schema: {}",
|
||||
*momentry_core::core::config::DATABASE_SCHEMA
|
||||
);
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
@@ -1011,6 +1062,7 @@ async fn main() -> Result<()> {
|
||||
.filter_map(|name| {
|
||||
let name_lower = name.to_lowercase();
|
||||
match name_lower.as_str() {
|
||||
"appearance" => Some(ProcessorType::Appearance),
|
||||
"asr" => Some(ProcessorType::Asr),
|
||||
"cut" => Some(ProcessorType::Cut),
|
||||
"asrx" => Some(ProcessorType::Asrx),
|
||||
@@ -1037,6 +1089,7 @@ async fn main() -> Result<()> {
|
||||
.filter_map(|name| {
|
||||
let name_lower = name.to_lowercase();
|
||||
match name_lower.as_str() {
|
||||
"appearance" => Some(ProcessorType::Appearance),
|
||||
"asr" => Some(ProcessorType::Asr),
|
||||
"cut" => Some(ProcessorType::Cut),
|
||||
"asrx" => Some(ProcessorType::Asrx),
|
||||
@@ -1618,6 +1671,69 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Process Appearance (color/histogram analysis, depends on Pose)
|
||||
if should_process(ProcessorType::Appearance) {
|
||||
let appearance_path = output_dir.get_output_path(&uuid, "appearance.json");
|
||||
let pose_path = output_dir.get_output_path(&uuid, "pose.json");
|
||||
let decision = decide_processing(&appearance_path, force, resume);
|
||||
|
||||
match decision {
|
||||
ProcessingDecision::SkipComplete => {
|
||||
println!("\nAppearance: ✓ Already complete, skipping");
|
||||
}
|
||||
ProcessingDecision::ForceReprocess => {
|
||||
println!("\nAppearance: ⟳ Force reprocessing from scratch...");
|
||||
std::fs::remove_file(&appearance_path).ok();
|
||||
if is_cloud(ProcessorType::Appearance) {
|
||||
println!(" [Cloud processing not implemented yet - run locally]");
|
||||
} else {
|
||||
process_appearance_module(
|
||||
&appearance_path,
|
||||
video_path,
|
||||
&pose_path,
|
||||
&uuid,
|
||||
&progress_state,
|
||||
&ui,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
ProcessingDecision::ResumePartial => {
|
||||
println!("\nAppearance: ↻ Resuming from checkpoint...");
|
||||
if is_cloud(ProcessorType::Appearance) {
|
||||
println!(" [Cloud processing not implemented yet - run locally]");
|
||||
} else {
|
||||
process_appearance_module(
|
||||
&appearance_path,
|
||||
video_path,
|
||||
&pose_path,
|
||||
&uuid,
|
||||
&progress_state,
|
||||
&ui,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
ProcessingDecision::Process => {
|
||||
if is_cloud(ProcessorType::Appearance) {
|
||||
println!("\nAppearance: ☁️ Running via cloud...");
|
||||
println!(" [Cloud processing not implemented yet - run locally]");
|
||||
} else {
|
||||
println!("\nAppearance: ⚙️ Processing...");
|
||||
process_appearance_module(
|
||||
&appearance_path,
|
||||
video_path,
|
||||
&pose_path,
|
||||
&uuid,
|
||||
&progress_state,
|
||||
&ui,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process Story (video narrative)
|
||||
if should_process(ProcessorType::Story) {
|
||||
let story_path = output_dir.get_output_path(&uuid, "story.json");
|
||||
@@ -1770,6 +1886,10 @@ async fn main() -> Result<()> {
|
||||
let path = output_dir.get_output_path(&uuid, "pose.json");
|
||||
println!(" - Pose JSON: {}", path.display());
|
||||
}
|
||||
if should_process(ProcessorType::Appearance) {
|
||||
let path = output_dir.get_output_path(&uuid, "appearance.json");
|
||||
println!(" - Appearance JSON: {}", path.display());
|
||||
}
|
||||
if should_process(ProcessorType::Story) {
|
||||
let path = output_dir.get_output_path(&uuid, "story.json");
|
||||
println!(" - Story JSON: {}", path.display());
|
||||
|
||||
Reference in New Issue
Block a user