feat: consumption log top1 + elapsed since recording; RTSP play once
- Add top1_name/top1_conf to TSV and show top1–3 in pending markdown - Add 相对开录 column and pass since_recording_start from surgery start - Track surgery_started_wall and format_elapsed_mmss_since in session registry - Remove ffmpeg stream_loop from synthetic/demo fake RTSP (play once) - Fix fake_rtsp_from_file poll loop indentation; update README - Extend consumption TSV tests; add face test PNGs under tests/faces Made-with: Cursor
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
"""每例手术一个文本文件(制表符列):`start_surgery` 时截断并写表头,每次时间窗识别**追加**一行(仅 item_id, item_name, qty, doctor_id, timestamp)。待确认行首列为 ``pending:{confirmation_id}``,语音落锤后**整行替换**为与客户端一致的最终真值,不再重复追加。终端 Markdown 时间戳为可读形式;落盘时间戳为 ISO 区间便于程序解析。
|
||||
"""每例手术一个文本文件(制表符列):`start_surgery` 时截断并写表头,每次时间窗识别**追加**一行
|
||||
(item_id, item_name, qty, doctor_id, timestamp, top1–3 名与置信度)。待确认行首列为 ``pending:{confirmation_id}``、
|
||||
item_name 为「待确认」,top1–3 仍为模型输出;语音落锤后**整行替换**为与客户端一致的最终真值,不再重复追加。
|
||||
终端 Markdown 时间戳为可读形式;落盘时间戳为 ISO 区间便于程序解析。
|
||||
|
||||
手术结束时再追加一节汇总行:item_id, item_name, qty(无其它列);与 HTTP ``summary`` 同算法,由内存 ``details`` 经 ``build_consumption_summary`` 得到,非录制过程中按窗累计。
|
||||
|
||||
@@ -20,10 +23,11 @@ from app.services.consumable_vision_algorithm import ClsTop3, _norm_product_name
|
||||
from app.terminal_markdown import print_markdown_stderr
|
||||
|
||||
# 制表符分隔;时间范围用 U+2013 连接;本窗消耗数量恒为 1。
|
||||
# top2/top3 为模型原始排序(未按手术候选重排);item_id 只写与展示名不同的业务 id(label_id),与名称相同时留空。
|
||||
# top1/2/3 为模型原始排序(未按手术候选重排);确认行 item_name 与 top1_name 同为 Top1 标签。
|
||||
# item_id 只写与展示名不同的业务 id(label_id),与名称相同时留空。
|
||||
HEADER = (
|
||||
"item_id\titem_name\tqty\tdoctor_id\ttimestamp\t"
|
||||
"top2_name\ttop2_conf\ttop3_name\ttop3_conf\n"
|
||||
"top1_name\ttop1_conf\ttop2_name\ttop2_conf\ttop3_name\ttop3_conf\n"
|
||||
)
|
||||
SUMMARY_HEADER = "item_id\titem_name\tqty\n"
|
||||
_RANGE_SEP = "\u2013" # en dash,与样例 `00:00:00.000–00:00:45.000` 一致
|
||||
@@ -151,6 +155,8 @@ def build_tsv_line(
|
||||
"1",
|
||||
_encode_cell(doctor_id),
|
||||
_encode_cell(ts),
|
||||
_encode_cell(name1),
|
||||
_fmt_top_conf(best.t1_conf),
|
||||
_encode_cell(n2),
|
||||
_fmt_top_conf(best.t2_conf),
|
||||
_encode_cell(n3),
|
||||
@@ -217,6 +223,7 @@ def build_consumption_markdown(
|
||||
camera_id: str,
|
||||
wall_start_epoch: float,
|
||||
wall_end_epoch: float,
|
||||
since_recording_start: str | None = None,
|
||||
) -> str:
|
||||
"""终端用:与落盘列一致;本窗 qty 恒为 1。"""
|
||||
tsv_id, _ = resolve_consumption_ids(best.t1_name, best.t1_pid, name_to_code)
|
||||
@@ -224,14 +231,16 @@ def build_consumption_markdown(
|
||||
n2 = (best.t2_name or "").strip()
|
||||
n3 = (best.t3_name or "").strip()
|
||||
ts = format_consumption_timestamp_readable(camera_id, wall_start_epoch, wall_end_epoch)
|
||||
rel = _md_cell(since_recording_start or "—")
|
||||
return "\n".join(
|
||||
[
|
||||
"| item_id | item_name | qty | doctor_id | timestamp | top2 | top3 |",
|
||||
"| :--- | :--- | ---: | :--- | :--- | :--- | :--- |",
|
||||
"| {} | {} | 1 | {} | {} | {} | {} |".format(
|
||||
"| item_id | item_name | qty | doctor_id | 相对开录 | timestamp | top2 | top3 |",
|
||||
"| :--- | :--- | ---: | :--- | :--- | :--- | :--- | :--- |",
|
||||
"| {} | {} | 1 | {} | {} | {} | {} | {} |".format(
|
||||
_md_cell(tsv_id),
|
||||
_md_cell(n1),
|
||||
_md_cell(doctor_id),
|
||||
rel,
|
||||
_md_cell(ts),
|
||||
_md_cell(
|
||||
f"{n2} ({_fmt_top_conf(best.t2_conf)})" if n2 else "—",
|
||||
@@ -259,6 +268,7 @@ def _build_pending_tsv_line(
|
||||
) -> str:
|
||||
pid = f"pending:{confirmation_id}"
|
||||
ts = format_consumption_timestamp(camera_id, wall_start_epoch, wall_end_epoch)
|
||||
n1 = (model_snap.t1_name or "").strip()
|
||||
n2 = (model_snap.t2_name or "").strip()
|
||||
n3 = (model_snap.t3_name or "").strip()
|
||||
row = [
|
||||
@@ -267,6 +277,8 @@ def _build_pending_tsv_line(
|
||||
"1",
|
||||
_encode_cell(doctor_id),
|
||||
_encode_cell(ts),
|
||||
_encode_cell(n1),
|
||||
_fmt_top_conf(model_snap.t1_conf),
|
||||
_encode_cell(n2),
|
||||
_fmt_top_conf(model_snap.t2_conf),
|
||||
_encode_cell(n3),
|
||||
@@ -283,26 +295,31 @@ def build_pending_consumption_markdown(
|
||||
camera_id: str,
|
||||
wall_start_epoch: float,
|
||||
wall_end_epoch: float,
|
||||
since_recording_start: str | None = None,
|
||||
) -> str:
|
||||
pid = f"pending:{confirmation_id}"
|
||||
n1 = (model_snap.t1_name or "").strip()
|
||||
n2 = (model_snap.t2_name or "").strip()
|
||||
n3 = (model_snap.t3_name or "").strip()
|
||||
ts = format_consumption_timestamp_readable(camera_id, wall_start_epoch, wall_end_epoch)
|
||||
rel = _md_cell(since_recording_start or "—")
|
||||
|
||||
def _top_cell(name: str, conf: float) -> str:
|
||||
return _md_cell(f"{name} ({_fmt_top_conf(conf)})" if name else "—")
|
||||
|
||||
return "\n".join(
|
||||
[
|
||||
"| item_id | item_name | qty | doctor_id | timestamp | top2 | top3 |",
|
||||
"| :--- | :--- | ---: | :--- | :--- | :--- | :--- |",
|
||||
"| {} | {} | 1 | {} | {} | {} | {} |".format(
|
||||
"| item_id | item_name | qty | doctor_id | 相对开录 | timestamp | top1 | top2 | top3 |",
|
||||
"| :--- | :--- | ---: | :--- | :--- | :--- | :--- | :--- | :--- |",
|
||||
"| {} | {} | 1 | {} | {} | {} | {} | {} | {} |".format(
|
||||
_md_cell(pid),
|
||||
_md_cell(PENDING_CONSUMPTION_ITEM_NAME),
|
||||
_md_cell(doctor_id),
|
||||
rel,
|
||||
_md_cell(ts),
|
||||
_md_cell(
|
||||
f"{n2} ({_fmt_top_conf(model_snap.t2_conf)})" if n2 else "—",
|
||||
),
|
||||
_md_cell(
|
||||
f"{n3} ({_fmt_top_conf(model_snap.t3_conf)})" if n3 else "—",
|
||||
),
|
||||
_top_cell(n1, model_snap.t1_conf),
|
||||
_top_cell(n2, model_snap.t2_conf),
|
||||
_top_cell(n3, model_snap.t3_conf),
|
||||
),
|
||||
"",
|
||||
]
|
||||
@@ -320,8 +337,9 @@ def append_consumption_pending_window(
|
||||
wall_end_epoch: float,
|
||||
tsv_enabled: bool | None = None,
|
||||
markdown_terminal: bool | None = None,
|
||||
since_recording_start: str | None = None,
|
||||
) -> None:
|
||||
"""需医生确认的时间窗:落盘/终端记「待确认」,top2/3 仍保留模型提示;不更新消耗汇总。"""
|
||||
"""需医生确认的时间窗:落盘/终端记「待确认」,top1/2/3 保留模型提示;不更新消耗汇总。"""
|
||||
en_tsv = bp.CONSUMPTION_TSV_LOG_ENABLED if tsv_enabled is None else tsv_enabled
|
||||
en_md = (
|
||||
bp.CONSUMPTION_LOG_MARKDOWN_TERMINAL
|
||||
@@ -349,6 +367,7 @@ def append_consumption_pending_window(
|
||||
camera_id=camera_id,
|
||||
wall_start_epoch=wall_start_epoch,
|
||||
wall_end_epoch=wall_end_epoch,
|
||||
since_recording_start=since_recording_start,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -549,6 +568,7 @@ class ConsumptionTsvWriter:
|
||||
camera_id: str,
|
||||
wall_start_epoch: float,
|
||||
wall_end_epoch: float,
|
||||
since_recording_start: str | None = None,
|
||||
) -> None:
|
||||
if not bp.CONSUMPTION_TSV_LOG_ENABLED and not bp.CONSUMPTION_LOG_MARKDOWN_TERMINAL:
|
||||
return
|
||||
@@ -571,6 +591,7 @@ class ConsumptionTsvWriter:
|
||||
camera_id=camera_id,
|
||||
wall_start_epoch=wall_start_epoch,
|
||||
wall_end_epoch=wall_end_epoch,
|
||||
since_recording_start=since_recording_start,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -621,6 +642,7 @@ def append_consumption_window(
|
||||
camera_id: str,
|
||||
wall_start_epoch: float,
|
||||
wall_end_epoch: float,
|
||||
since_recording_start: str | None = None,
|
||||
) -> None:
|
||||
if not bp.CONSUMPTION_TSV_LOG_ENABLED and not bp.CONSUMPTION_LOG_MARKDOWN_TERMINAL:
|
||||
return
|
||||
@@ -643,5 +665,6 @@ def append_consumption_window(
|
||||
camera_id=camera_id,
|
||||
wall_start_epoch=wall_start_epoch,
|
||||
wall_end_epoch=wall_end_epoch,
|
||||
since_recording_start=since_recording_start,
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user