feat: 消费停录汇总与查结果同口径,并更新分类与 TSV
- 停录时由 details 经 build_consumption_summary 写 TSV/终端汇总,移除 consumption_log_totals 与时间窗累加 - 更新 consumable_classifier 权重、YAML 标签与 consumable_vision_algorithm - 扩展 consumption TSV 相关测试与配置注释 Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""每例手术一个文本文件(制表符列):`start_surgery` 时截断并写表头,每次时间窗识别**追加**一行(仅 item_id, item_name, qty, doctor_id, timestamp)。终端 Markdown 时间戳为可读形式;落盘时间戳为 ISO 区间便于程序解析。
|
||||
"""每例手术一个文本文件(制表符列):`start_surgery` 时截断并写表头,每次时间窗识别**追加**一行(仅 item_id, item_name, qty, doctor_id, timestamp)。待确认行首列为 ``pending:{confirmation_id}``,语音落锤后**整行替换**为与客户端一致的最终真值,不再重复追加。终端 Markdown 时间戳为可读形式;落盘时间戳为 ISO 区间便于程序解析。
|
||||
|
||||
手术结束时再追加一节汇总行:item_id, item_name, qty(无其它列)。
|
||||
手术结束时再追加一节汇总行:item_id, item_name, qty(无其它列);与 HTTP ``summary`` 同算法,由内存 ``details`` 经 ``build_consumption_summary`` 得到,非录制过程中按窗累计。
|
||||
|
||||
时间戳:在拉流起点记录 `time.time()`,与 `time.monotonic()` 时间窗对齐。直播 RTSP 经 OpenCV 一般无可靠绝对时码,以本机接收时刻为准。
|
||||
"""
|
||||
@@ -353,25 +353,15 @@ def append_consumption_pending_window(
|
||||
)
|
||||
|
||||
|
||||
def append_consumption_voice_resolution_line(
|
||||
def _build_voice_resolved_tsv_data_line(
|
||||
*,
|
||||
surgery_id: str,
|
||||
name_to_code: dict[str, str],
|
||||
chosen_label: str,
|
||||
doctor_id: str,
|
||||
wall_epoch: float,
|
||||
tsv_enabled: bool | None = None,
|
||||
) -> None:
|
||||
"""语音确认后追加一行最终耗材(医生 ID 多为 voice);top2/3 空列。
|
||||
|
||||
待确认流程下,时间窗仅记「待确认」,此处写入医生选定后的正式记录。
|
||||
"""
|
||||
en = settings.consumption_tsv_log_enabled if tsv_enabled is None else tsv_enabled
|
||||
if not en:
|
||||
return
|
||||
) -> str:
|
||||
"""与客户端一致的最终行(top2/3 空列,timestamp 为单点)。"""
|
||||
lb = (chosen_label or "").strip()
|
||||
if not lb:
|
||||
return
|
||||
norm = _norm_product_name(lb)
|
||||
p = (
|
||||
name_to_code.get(norm) or name_to_code.get(lb) or ""
|
||||
@@ -387,7 +377,7 @@ def append_consumption_voice_resolution_line(
|
||||
t2_pid="",
|
||||
t3_pid="",
|
||||
)
|
||||
line = build_tsv_line(
|
||||
return build_tsv_line(
|
||||
name_to_code=name_to_code,
|
||||
best=snap,
|
||||
doctor_id=doctor_id,
|
||||
@@ -395,6 +385,98 @@ def append_consumption_voice_resolution_line(
|
||||
wall_start_epoch=wall_epoch,
|
||||
wall_end_epoch=wall_epoch,
|
||||
)
|
||||
|
||||
|
||||
def _data_line_starts_with_pending_id(line: str, confirmation_id: str) -> bool:
|
||||
s = (line or "").rstrip("\r\n")
|
||||
if not s or s.startswith("item_id\t"):
|
||||
return False
|
||||
want = f"pending:{(confirmation_id or '').strip()}"
|
||||
first = s.split("\t", 1)[0]
|
||||
return first == _encode_cell(want) or first == want
|
||||
|
||||
|
||||
def replace_pending_line_with_voice_resolution(
|
||||
*,
|
||||
surgery_id: str,
|
||||
confirmation_id: str,
|
||||
name_to_code: dict[str, str],
|
||||
chosen_label: str,
|
||||
doctor_id: str,
|
||||
wall_epoch: float,
|
||||
tsv_enabled: bool | None = None,
|
||||
) -> None:
|
||||
"""将同一 ``confirmation_id`` 下先前追加的「待确认」行整行改为最终真值,不再多追加一行。
|
||||
|
||||
未找到 ``pending:{confirmation_id}`` 时回退为追加(兼容旧文件或行缺失)。读改写全程持
|
||||
:data:`_lock`,避免与并发的 append 交错。
|
||||
"""
|
||||
en = settings.consumption_tsv_log_enabled if tsv_enabled is None else tsv_enabled
|
||||
if not en:
|
||||
return
|
||||
if not (chosen_label or "").strip() or not (confirmation_id or "").strip():
|
||||
return
|
||||
new_line = _build_voice_resolved_tsv_data_line(
|
||||
name_to_code=name_to_code,
|
||||
chosen_label=chosen_label,
|
||||
doctor_id=doctor_id,
|
||||
wall_epoch=wall_epoch,
|
||||
)
|
||||
path = resolved_consumption_log_path(surgery_id)
|
||||
with _lock:
|
||||
if not path.is_file():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8") as f:
|
||||
f.write(HEADER)
|
||||
f.write(new_line)
|
||||
logger.warning(
|
||||
"consumption TSV 无文件,无法替换 pending 行,已写表头+最终行: {}",
|
||||
path,
|
||||
)
|
||||
return
|
||||
text = path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines(keepends=True)
|
||||
replaced = False
|
||||
out: list[str] = []
|
||||
for line in lines:
|
||||
if (
|
||||
not replaced
|
||||
and _data_line_starts_with_pending_id(line, confirmation_id)
|
||||
):
|
||||
out.append(new_line)
|
||||
replaced = True
|
||||
else:
|
||||
out.append(line)
|
||||
if not replaced:
|
||||
logger.warning(
|
||||
"未在 TSV 中找到 pending 行,回退为追加: surgery={} confirm={}",
|
||||
surgery_id,
|
||||
confirmation_id,
|
||||
)
|
||||
with path.open("a", encoding="utf-8") as f:
|
||||
f.write(new_line)
|
||||
return
|
||||
path.write_text("".join(out), encoding="utf-8")
|
||||
|
||||
|
||||
def append_consumption_voice_resolution_line(
|
||||
*,
|
||||
surgery_id: str,
|
||||
name_to_code: dict[str, str],
|
||||
chosen_label: str,
|
||||
doctor_id: str,
|
||||
wall_epoch: float,
|
||||
tsv_enabled: bool | None = None,
|
||||
) -> None:
|
||||
"""已弃用:请使用 ``replace_pending_line_with_voice_resolution`` 并传 ``confirmation_id``。"""
|
||||
if not (chosen_label or "").strip():
|
||||
return
|
||||
line = _build_voice_resolved_tsv_data_line(
|
||||
name_to_code=name_to_code,
|
||||
chosen_label=chosen_label,
|
||||
doctor_id=doctor_id,
|
||||
wall_epoch=wall_epoch,
|
||||
)
|
||||
append_consumption_tsv_line(surgery_id, line)
|
||||
|
||||
|
||||
@@ -467,19 +549,9 @@ class ConsumptionTsvWriter:
|
||||
camera_id: str,
|
||||
wall_start_epoch: float,
|
||||
wall_end_epoch: float,
|
||||
running_totals: dict[str, tuple[str, int]] | None = None,
|
||||
) -> None:
|
||||
if not self._s.consumption_tsv_log_enabled and not self._s.consumption_log_markdown_terminal:
|
||||
return
|
||||
_tsv_id, totals_key = resolve_consumption_ids(
|
||||
best.t1_name, best.t1_pid, name_to_code
|
||||
)
|
||||
iname = (best.t1_name or "").strip()
|
||||
if running_totals is not None:
|
||||
if totals_key not in running_totals:
|
||||
running_totals[totals_key] = (iname, 0)
|
||||
prev_name, q = running_totals[totals_key]
|
||||
running_totals[totals_key] = (prev_name, q + 1)
|
||||
if self._s.consumption_tsv_log_enabled:
|
||||
line = build_tsv_line(
|
||||
name_to_code=name_to_code,
|
||||
@@ -549,19 +621,9 @@ def append_consumption_window(
|
||||
camera_id: str,
|
||||
wall_start_epoch: float,
|
||||
wall_end_epoch: float,
|
||||
running_totals: dict[str, tuple[str, int]] | None = None,
|
||||
) -> None:
|
||||
if not settings.consumption_tsv_log_enabled and not settings.consumption_log_markdown_terminal:
|
||||
return
|
||||
_tsv_id, totals_key = resolve_consumption_ids(
|
||||
best.t1_name, best.t1_pid, name_to_code
|
||||
)
|
||||
iname = (best.t1_name or "").strip()
|
||||
if running_totals is not None:
|
||||
if totals_key not in running_totals:
|
||||
running_totals[totals_key] = (iname, 0)
|
||||
prev_name, q = running_totals[totals_key]
|
||||
running_totals[totals_key] = (prev_name, q + 1)
|
||||
if settings.consumption_tsv_log_enabled:
|
||||
line = build_tsv_line(
|
||||
name_to_code=name_to_code,
|
||||
|
||||
Reference in New Issue
Block a user