feat: Phase 1 handover - schema migration, correction mechanism, API fixes

Schema changes: dev.chunks->dev.chunk, remove old_chunk_id/chunk_index
Correction: asr-1.json format, generate/apply scripts
API: 37/37 endpoints fixed and tested
Docs: HANDOVER_V2.0.md for M4
This commit is contained in:
Accusys
2026-05-11 07:03:22 +08:00
parent ef894a44ad
commit 39ba5ddf76
147 changed files with 19843 additions and 3053 deletions

View File

@@ -2286,7 +2286,8 @@ async fn list_jobs(Query(params): Query<JobsQuery>) -> Result<Json<JobListRespon
.into_iter()
.map(|r| {
let status_str: String = r.try_get("status").unwrap_or_default();
let status = MonitorJobStatus::from_db_str(&status_str).unwrap_or(MonitorJobStatus::Pending);
let status =
MonitorJobStatus::from_db_str(&status_str).unwrap_or(MonitorJobStatus::Pending);
JobInfoResponse {
id: r.try_get("id").unwrap_or(0),
uuid: r.try_get("uuid").unwrap_or_default(),
@@ -2507,7 +2508,7 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
.route("/api/v1/files/scan", get(scan_files))
.route("/api/v1/file/:file_uuid/probe", get(probe_by_uuid))
.route("/api/v1/file/:file_uuid/process", post(trigger_processing))
.route("/api/v1/file/:file_uuid/chunks", get(list_pre_chunks))
.route("/api/v1/progress/:uuid", get(get_progress))
.route("/api/v1/jobs", get(list_jobs))
.route("/api/v1/config/cache", post(cache_toggle))
@@ -2585,7 +2586,7 @@ async fn get_ingest_stats(
State(state): State<AppState>,
) -> Result<Json<IngestStatsResponse>, StatusCode> {
let table_videos = schema::table_name("videos");
let table_chunks = schema::table_name("chunks");
let table_chunks = schema::table_name("chunk");
let total_videos: (i64,) = sqlx::query_as(&format!("SELECT COUNT(*) FROM {}", table_videos))
.fetch_one(state.db.pool())
@@ -3048,15 +3049,15 @@ async fn video_details(
Query(query): Query<VideoDetailsQuery>,
State(state): State<AppState>,
) -> Result<Json<VideoDetailsResponse>, StatusCode> {
let table = schema::table_name("chunks");
let table = schema::table_name("chunk");
if let Some(chunk_id) = query.chunk_id {
let row: Option<(
i32, String, String, i32, String, f64, i64, i64,
i32, String, String, String, f64, i64, i64,
Option<String>, serde_json::Value, Option<serde_json::Value>,
Option<String>, i32, Option<String>, Option<serde_json::Value>, Option<String>,
)> = sqlx::query_as(&format!(
"SELECT file_id, uuid, chunk_id, chunk_index, chunk_type::text, fps, start_frame, end_frame,
"SELECT file_id, uuid, chunk_id, chunk_type::text, fps, start_frame, end_frame,
text_content, content, metadata, vector_id, frame_count,
parent_chunk_id, visual_stats, summary_text
FROM {} WHERE chunk_id = $1 AND uuid = $2",
@@ -3081,20 +3082,20 @@ async fn video_details(
let row = row.ok_or(StatusCode::NOT_FOUND)?;
let fps = if row.5 > 0.0 { row.5 } else { 24.0 };
let start_frame = row.6;
let end_frame = row.7;
let fps = if row.4 > 0.0 { row.4 } else { 24.0 };
let start_frame = row.5;
let end_frame = row.6;
let duration_frames = end_frame - start_frame;
let start_time = start_frame as f64 / fps;
let end_time = end_frame as f64 / fps;
let row_metadata = row.10.clone();
let row_metadata = row.9.clone();
let mut summary_text = row.15.clone();
let mut summary_text = row.14.clone();
let mut metadata = None;
if let Some(ref pid_str) = row.13 {
if let Some(ref pid_str) = row.12 {
if !pid_str.is_empty() {
if let Ok(pid) = pid_str.parse::<i32>() {
let parent_table = schema::table_name("parent_chunks");
@@ -3168,7 +3169,7 @@ async fn video_details(
uuid: row.1.clone(),
details: VideoDetailsResult::Chunk(ChunkDetailResponse {
chunk_id: row.2.clone(),
chunk_type: row.4.clone(),
chunk_type: row.3.clone(),
frame_range: FrameRange {
start_frame,
end_frame,
@@ -3179,12 +3180,12 @@ async fn video_details(
start: start_time,
end: end_time,
},
text_content: row.8.clone(),
content: Some(row.9.clone()),
parent_id: row.13.clone(),
text_content: row.7.clone(),
content: Some(row.8.clone()),
parent_id: row.12.clone(),
summary_text,
metadata,
visual_stats: row.14.clone(),
visual_stats: row.13.clone(),
speaker_ids,
person_ids,
}),
@@ -3194,123 +3195,6 @@ async fn video_details(
Err(StatusCode::BAD_REQUEST)
}
#[derive(Debug, Deserialize)]
struct PreChunksQuery {
processor_type: Option<String>,
page: Option<usize>,
page_size: Option<usize>,
}
#[derive(Debug, Serialize)]
struct PreChunksResponse {
pre_chunks: Vec<PreChunkItem>,
count: i64,
page: usize,
page_size: usize,
}
#[derive(Debug, Serialize)]
struct PreChunkItem {
id: i64,
processor_type: String,
coordinate_type: String,
coordinate_index: i64,
start_frame: Option<i64>,
end_frame: Option<i64>,
start_time: Option<f64>,
end_time: Option<f64>,
fps: Option<f64>,
data: serde_json::Value,
identity_id: Option<String>,
confidence: Option<f64>,
created_at: String,
}
async fn list_pre_chunks(
Path(uuid): Path<String>,
Query(query): Query<PreChunksQuery>,
State(state): State<AppState>,
) -> Result<Json<PreChunksResponse>, StatusCode> {
let table = schema::table_name("pre_chunks");
let page = query.page.unwrap_or(1);
let page_size = query.page_size.unwrap_or(20);
let offset = (page - 1) * page_size;
let processor_filter = if let Some(pt) = &query.processor_type {
format!("AND processor_type = '{}'", pt.to_lowercase())
} else {
"".to_string()
};
let count_query = format!(
"SELECT COUNT(*) FROM {} WHERE file_uuid = $1 {}",
table, processor_filter
);
let count: i64 = sqlx::query(&count_query)
.bind(&uuid)
.fetch_one(state.db.pool())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.try_get(0)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let data_query = format!(
"SELECT id, processor_type, coordinate_type, coordinate_index,
start_frame, end_frame, start_time, end_time, fps,
data, created_at
FROM {}
WHERE file_uuid = $1 {}
ORDER BY coordinate_index ASC
LIMIT {} OFFSET {}",
table, processor_filter, page_size, offset
);
let rows: Vec<(
i64,
String,
String,
i64,
Option<i64>,
Option<i64>,
Option<f64>,
Option<f64>,
Option<f64>,
serde_json::Value,
chrono::DateTime<chrono::Utc>,
)> = sqlx::query_as(&data_query)
.bind(&uuid)
.fetch_all(state.db.pool())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let pre_chunks = rows
.iter()
.map(|row| PreChunkItem {
id: row.0,
processor_type: row.1.clone(),
coordinate_type: row.2.clone(),
coordinate_index: row.3,
start_frame: row.4,
end_frame: row.5,
start_time: row.6,
end_time: row.7,
fps: row.8,
data: row.9.clone(),
identity_id: None,
confidence: None,
created_at: row.10.to_rfc3339(),
})
.collect();
Ok(Json(PreChunksResponse {
pre_chunks,
count,
page,
page_size,
}))
}
#[derive(Debug, Serialize)]
struct DeleteVideoResponse {
success: bool,
@@ -3404,7 +3288,7 @@ async fn delete_video(
let videos_table = schema::table_name("videos");
let face_table = schema::table_name("face_detections");
let processor_table = schema::table_name("processor_results");
let chunks_table = schema::table_name("chunks");
let chunks_table = schema::table_name("chunk");
let parent_chunks_table = schema::table_name("parent_chunks");
// Check if video exists first