Files
momentry_core/src/api/checkin_api.rs
T
Accusys 2cfcfdd1af 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
2026-06-21 04:47:49 +08:00

120 lines
3.5 KiB
Rust

use axum::{
extract::{Path, State},
http::StatusCode,
routing::{get, post},
Json, Router,
};
use serde::{Deserialize, Serialize};
use crate::api::types::AppState;
use crate::core::checkin;
use crate::core::db::VideoStatus;
#[derive(Debug, Serialize)]
struct CheckinResponse {
file_uuid: String,
pre_chunks_moved: usize,
speaker_detections_moved: usize,
vectors_moved: usize,
status: String,
}
#[derive(Debug, Serialize)]
struct CheckoutResponse {
file_uuid: String,
rows_deleted: usize,
status: String,
}
#[derive(Debug, Serialize)]
struct WorkspaceStatusResponse {
file_uuid: String,
exists: bool,
}
async fn checkin_handler(
State(state): State<AppState>,
Path(file_uuid): Path<String>,
) -> Result<Json<CheckinResponse>, (StatusCode, Json<serde_json::Value>)> {
match checkin::checkin(&state.db, &file_uuid).await {
Ok(result) => {
if let Err(e) = state
.db
.update_video_status(&file_uuid, VideoStatus::Indexed)
.await
{
tracing::warn!(
"Failed to update video status to Indexed for {}: {}",
file_uuid,
e
);
}
Ok(Json(CheckinResponse {
file_uuid: result.file_uuid.clone(),
pre_chunks_moved: result.pre_chunks_moved,
speaker_detections_moved: result.speaker_detections_moved,
vectors_moved: result.vectors_moved,
status: "indexed".to_string(),
}))
}
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": format!("Checkin failed: {}", e),
"file_uuid": file_uuid,
})),
)),
}
}
async fn checkout_handler(
State(state): State<AppState>,
Path(file_uuid): Path<String>,
) -> Result<Json<CheckoutResponse>, (StatusCode, Json<serde_json::Value>)> {
match checkin::checkout(&state.db, &file_uuid).await {
Ok(result) => {
if let Err(e) = state
.db
.update_video_status(&file_uuid, VideoStatus::CheckedOut)
.await
{
tracing::warn!(
"Failed to update video status to CheckedOut for {}: {}",
file_uuid,
e
);
}
Ok(Json(CheckoutResponse {
file_uuid: result.file_uuid.clone(),
rows_deleted: result.rows_deleted,
status: "checked_out".to_string(),
}))
}
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": format!("Checkout failed: {}", e),
"file_uuid": file_uuid,
})),
)),
}
}
async fn workspace_status_handler(Path(file_uuid): Path<String>) -> Json<WorkspaceStatusResponse> {
use crate::core::db::workspace_sqlite::WorkspaceDb;
Json(WorkspaceStatusResponse {
file_uuid: file_uuid.clone(),
exists: WorkspaceDb::exists(&file_uuid),
})
}
pub fn checkin_routes() -> Router<AppState> {
Router::new()
.route("/api/v1/file/:file_uuid/checkin", post(checkin_handler))
.route("/api/v1/file/:file_uuid/checkout", post(checkout_handler))
.route(
"/api/v1/file/:file_uuid/workspace",
get(workspace_status_handler),
)
}