fix fish weight calculation by using correct parameters. output video

This commit is contained in:
zaiun xu
2026-04-14 20:55:15 +08:00
parent 8497d0eb1d
commit af67f61b63
520 changed files with 4450241 additions and 337 deletions

View File

@@ -13,7 +13,7 @@ import shutil
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional, Set, Tuple
from typing import Any, Dict, List, Optional, Set, Tuple
from app.settings import Settings
from app.state import HealthSnapshot, MeasureSnapshot
@@ -87,9 +87,14 @@ def init_db(settings: Settings) -> None:
video_right TEXT NOT NULL DEFAULT '',
error TEXT,
raw_prediction_path TEXT,
source_path TEXT
source_path TEXT,
client_id TEXT DEFAULT NULL,
pred REAL,
star INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_measure_client_id ON measure_snapshots(client_id);
CREATE TABLE IF NOT EXISTS health_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT NOT NULL,
@@ -116,10 +121,59 @@ def init_db(settings: Settings) -> None:
)
_migrate_delivery_cursor_from_legacy(conn)
_ensure_delivery_cursors(conn)
_migrate_add_client_id_column(conn)
_migrate_add_pred_star_columns(conn)
_migrate_add_calculation_log_column(conn)
finally:
conn.close()
def _migrate_add_client_id_column(conn: sqlite3.Connection) -> None:
"""为旧数据库添加 client_id 列(如果不存在)。"""
row = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='measure_snapshots'"
).fetchone()
if row is None:
return
# 检查 client_id 列是否存在
cols = conn.execute("PRAGMA table_info(measure_snapshots)").fetchall()
has_client_id = any(col[1] == "client_id" for col in cols)
if not has_client_id:
conn.execute("ALTER TABLE measure_snapshots ADD COLUMN client_id TEXT DEFAULT NULL")
conn.execute("CREATE INDEX idx_measure_client_id ON measure_snapshots(client_id)")
conn.commit()
def _migrate_add_pred_star_columns(conn: sqlite3.Connection) -> None:
"""为旧数据库添加 pred 和 star 列(如果不存在)。"""
row = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='measure_snapshots'"
).fetchone()
if row is None:
return
cols = conn.execute("PRAGMA table_info(measure_snapshots)").fetchall()
col_names = {col[1] for col in cols}
if "pred" not in col_names:
conn.execute("ALTER TABLE measure_snapshots ADD COLUMN pred REAL")
if "star" not in col_names:
conn.execute("ALTER TABLE measure_snapshots ADD COLUMN star INTEGER DEFAULT 0")
conn.commit()
def _migrate_add_calculation_log_column(conn: sqlite3.Connection) -> None:
"""为旧数据库添加 calculation_log 列(体重推算过程文本,对齐 test_dgcnn 终端输出)。"""
row = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='measure_snapshots'"
).fetchone()
if row is None:
return
cols = conn.execute("PRAGMA table_info(measure_snapshots)").fetchall()
col_names = {col[1] for col in cols}
if "calculation_log" not in col_names:
conn.execute("ALTER TABLE measure_snapshots ADD COLUMN calculation_log TEXT")
conn.commit()
def _migrate_delivery_cursor_from_legacy(conn: sqlite3.Connection) -> None:
"""旧表 delivery_cursor(kind) → delivery_client_cursor(default, kind)。"""
row = conn.execute(
@@ -172,6 +226,7 @@ def save_measure_snapshot(
settings: Settings,
snap: MeasureSnapshot,
source_path: Optional[str] = None,
client_id: Optional[str] = None,
) -> None:
init_db(settings)
conn = _connect(settings.sqlite_path)
@@ -185,8 +240,9 @@ def save_measure_snapshot(
"""
INSERT INTO measure_snapshots (
created_at, result_json, video_left, video_right,
error, raw_prediction_path, source_path
) VALUES (?, ?, ?, ?, ?, ?, ?)
error, raw_prediction_path, source_path, client_id, pred, star,
calculation_log
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
ts,
@@ -196,6 +252,10 @@ def save_measure_snapshot(
snap.error,
snap.raw_prediction_path,
source_path,
client_id,
snap.pred,
1 if snap.star else 0,
snap.calculation_log,
),
)
finally:
@@ -251,7 +311,7 @@ def get_latest_measure(settings: Settings) -> MeasureSnapshot:
row = conn.execute(
"""
SELECT created_at, result_json, video_left, video_right,
error, raw_prediction_path
error, raw_prediction_path, calculation_log
FROM measure_snapshots
ORDER BY id DESC
LIMIT 1
@@ -269,11 +329,53 @@ def get_latest_measure(settings: Settings) -> MeasureSnapshot:
updated_at=_parse_dt(row["created_at"]),
error=row["error"],
raw_prediction_path=row["raw_prediction_path"],
calculation_log=row["calculation_log"],
)
finally:
conn.close()
def list_all_measure_snapshots(settings: Settings) -> List[Dict[str, Any]]:
"""返回 ``measure_snapshots`` 全部行id 降序,最新在前),供调试接口使用。"""
init_db(settings)
conn = _connect(settings.sqlite_path)
try:
rows = conn.execute(
"""
SELECT id, created_at, result_json, video_left, video_right,
error, raw_prediction_path, source_path, client_id, pred, star,
calculation_log
FROM measure_snapshots
ORDER BY id DESC
"""
).fetchall()
out: List[Dict[str, Any]] = []
for row in rows:
data: Any = json.loads(row["result_json"])
if not isinstance(data, list):
data = []
st = row["star"]
out.append(
{
"id": row["id"],
"created_at": row["created_at"],
"result": data,
"video_left": row["video_left"] or "",
"video_right": row["video_right"] or "",
"error": row["error"],
"raw_prediction_path": row["raw_prediction_path"],
"source_path": row["source_path"],
"client_id": row["client_id"],
"pred": row["pred"],
"star": bool(st) if st is not None else False,
"calculation_log": row["calculation_log"],
}
)
return out
finally:
conn.close()
def get_latest_health(settings: Settings) -> HealthSnapshot:
init_db(settings)
conn = _connect(settings.sqlite_path)
@@ -412,7 +514,10 @@ def pop_next_measure(
settings: Settings,
client_id: str = DEFAULT_CLIENT_ID,
) -> Tuple[MeasureSnapshot, bool, Optional[int]]:
"""取该客户端队首未投递且可交付的 measure 快照并推进其游标;跳过不可交付行仅推进游标。"""
"""取该客户端队首未投递且可交付的 measure 快照并推进其游标;跳过不可交付行仅推进游标。
只返回与该 client_id 匹配的记录client_id 为 NULL 的记录对所有客户端可见,用于向后兼容)。
"""
cid = normalize_client_id(client_id)
init_db(settings)
conn = _connect(settings.sqlite_path)
@@ -421,16 +526,17 @@ def pop_next_measure(
last_id = _last_delivered_id(conn, "measure", "measure_snapshots", cid)
while True:
# 只查询匹配该 client_id 或 client_id 为 NULL 的记录
next_row = conn.execute(
"""
SELECT id, created_at, result_json, video_left, video_right,
error, raw_prediction_path
error, raw_prediction_path, pred, star, calculation_log
FROM measure_snapshots
WHERE id > ?
WHERE id > ? AND (client_id = ? OR client_id IS NULL)
ORDER BY id ASC
LIMIT 1
""",
(last_id,),
(last_id, cid),
).fetchone()
if next_row is None:
@@ -463,6 +569,9 @@ def pop_next_measure(
updated_at=_parse_dt(next_row["created_at"]),
error=err,
raw_prediction_path=next_row["raw_prediction_path"],
pred=next_row["pred"],
star=bool(next_row["star"]) if next_row["star"] is not None else False,
calculation_log=next_row["calculation_log"],
)
return snap, True, nid
except Exception:
@@ -592,6 +701,20 @@ def add_watch_processed(settings: Settings, path: str, kind: str) -> None:
conn.close()
def _safe_rm_tree(path: Path) -> None:
"""安全删除目录树(包括目录本身),忽略不存在或权限错误。"""
p = Path(path).resolve()
if not p.exists():
return
try:
if p.is_dir():
shutil.rmtree(p)
else:
p.unlink()
except OSError as e:
print(f"[prestart-fresh] skip remove {p}: {e}", flush=True)
def clear_runtime_compute_dirs(settings: Settings) -> None:
"""清空 FishMeasure / FishAction 运行时目录、托管预览、ingest 临时文件(保留目录本身)。