feat(fish_api): SQLite 快照投递、日志与 watch 空闲告警
- 新增 SQLite:measure/health 快照、delivery_cursor 单消费者 pop;clear/start_fresh 可清空库 - biomass GET 仅返回约定 data 字段,X-Fish-Biomass-New 表示是否有新快照;poller 读响应头 - loguru 桥接 uvicorn,子进程 stdout 流式输出;format_json_pretty 与算法摘要日志 - measure/action watch 无新任务时限流 WARNING;watch_idle 共用逻辑 - 依赖 loguru;新增 db、logging_config、subprocess_run、watch_idle、启动脚本 FishMeasure: 更新 fish_video_weight_evaluation 与 predict_weigth_from_svo2;移除未用 refbox/segmentation 脚本 Made-with: Cursor
This commit is contained in:
@@ -7,16 +7,61 @@ import subprocess
|
||||
import sys
|
||||
from datetime import date, datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from app.logging_config import format_json_pretty
|
||||
from app.settings import Settings
|
||||
from app.state import MeasureSnapshot
|
||||
from app.subprocess_run import run_subprocess_with_log
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def _py_exe(settings: Settings) -> str:
|
||||
return settings.python_fish_measure or sys.executable
|
||||
|
||||
|
||||
def _predict_weigth_from_svo2_extra_args(settings: Settings) -> List[str]:
|
||||
"""Flags aligned with FishMeasure/predict_weigth_from_svo2.py CLI."""
|
||||
out: List[str] = []
|
||||
if settings.predict_filter_pointcloud:
|
||||
out.append("--filter-pointcloud")
|
||||
if settings.predict_use_density_filter:
|
||||
out.append("--use-density-filter")
|
||||
if settings.predict_use_clustering_filter:
|
||||
out.append("--use-clustering-filter")
|
||||
if (
|
||||
settings.predict_use_pointcloud_classifier
|
||||
and settings.predict_pointcloud_classifier
|
||||
and Path(settings.predict_pointcloud_classifier).is_file()
|
||||
):
|
||||
out.extend(
|
||||
[
|
||||
"--pointcloud-classifier",
|
||||
settings.predict_pointcloud_classifier,
|
||||
"--use-pointcloud-classifier",
|
||||
"--pointcloud-classifier-threshold",
|
||||
str(settings.predict_pointcloud_classifier_threshold),
|
||||
]
|
||||
)
|
||||
if settings.predict_use_flatness_filter:
|
||||
out.append("--use-flatness-filter")
|
||||
out.extend(["--flatness-threshold", str(settings.predict_flatness_threshold)])
|
||||
out.extend(["--weight-top-k", str(settings.measure_weight_top_k)])
|
||||
if settings.measure_weight_top_by_length:
|
||||
out.append("--weight-top-by-length")
|
||||
else:
|
||||
out.append("--no-weight-top-by-length")
|
||||
if settings.predict_fish_video_weight_overlay:
|
||||
out.extend(
|
||||
[
|
||||
"--fish-video-weight-overlay",
|
||||
"--minute-interval-sec",
|
||||
str(settings.predict_minute_interval_sec),
|
||||
]
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def run_measure_subprocess(svo_path: Path, settings: Settings) -> None:
|
||||
script = settings.fish_measure_root / "predict_weigth_from_svo2.py"
|
||||
if not script.is_file():
|
||||
@@ -46,16 +91,16 @@ def run_measure_subprocess(svo_path: Path, settings: Settings) -> None:
|
||||
"--frame-stride",
|
||||
str(settings.predict_frame_stride),
|
||||
]
|
||||
cmd.extend(_predict_weigth_from_svo2_extra_args(settings))
|
||||
|
||||
proc = subprocess.run(
|
||||
proc = run_subprocess_with_log(
|
||||
cmd,
|
||||
cwd=str(settings.fish_measure_root),
|
||||
env=os.environ.copy(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
log_name="FishMeasure",
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
err = (proc.stderr or "") + (proc.stdout or "")
|
||||
err = proc.stdout or ""
|
||||
raise RuntimeError(
|
||||
f"predict_weigth_from_svo2.py failed ({proc.returncode}): {err[-4000:]}"
|
||||
)
|
||||
@@ -171,9 +216,28 @@ def build_measure_snapshot(svo_path: Path, settings: Settings) -> MeasureSnapsho
|
||||
"date": today,
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"[FishMeasure] parsed {}\navg_length_mm={} avg_weight_g={}\nweight_summary:\n{}",
|
||||
svo_path.name,
|
||||
length_mm,
|
||||
weight_g,
|
||||
format_json_pretty(summary if summary else {}),
|
||||
)
|
||||
logger.info(
|
||||
"[FishMeasure] API result_item:\n{}",
|
||||
format_json_pretty(result_item),
|
||||
)
|
||||
|
||||
out_dir = Path(data.get("output_dir", settings.measure_output_root / svo_path.stem))
|
||||
lv, rv = _find_preview_videos(out_dir)
|
||||
v_left, v_right = _publish_media(lv, rv, settings)
|
||||
logger.info(
|
||||
"[FishMeasure] media preview_paths={} {} | published_left={} published_right={}",
|
||||
lv,
|
||||
rv,
|
||||
v_left or "(none)",
|
||||
v_right or "(none)",
|
||||
)
|
||||
|
||||
return MeasureSnapshot(
|
||||
result=[result_item],
|
||||
@@ -187,5 +251,8 @@ def build_measure_snapshot(svo_path: Path, settings: Settings) -> MeasureSnapsho
|
||||
|
||||
|
||||
def run_full_measure(svo_path: Path, settings: Settings) -> MeasureSnapshot:
|
||||
logger.info("[FishMeasure] start svo={}", svo_path.resolve())
|
||||
run_measure_subprocess(svo_path, settings)
|
||||
return build_measure_snapshot(svo_path, settings)
|
||||
snap = build_measure_snapshot(svo_path, settings)
|
||||
logger.info("[FishMeasure] done svo={} result_len={}", svo_path.name, len(snap.result))
|
||||
return snap
|
||||
|
||||
Reference in New Issue
Block a user