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:
Kevin
2026-04-24 14:27:56 +08:00
parent 557fcee803
commit b651364877
9 changed files with 318 additions and 155 deletions

View File

@@ -7,7 +7,7 @@
- 置信度 ≥ 自动阈值但 Top1 不在候选内 → 视 voice_confirmation_enabled 入 pending。
- 中等置信度 → 入 pending若有可展示候选项
需医生确认时:消耗 TSV / 内存明细记「待确认」(不写模型 top1 商品名);语音确认后再落最终耗材并更新汇总
需医生确认时:消耗 TSV / 内存明细记「待确认」(不写模型 top1 商品名);语音确认后**替换**该条 TSV 为最终耗材。停录时 TSV/终端汇总与查结果 API 同口径,由 details 经 ``build_consumption_summary`` 生成
"""
from __future__ import annotations
@@ -85,7 +85,6 @@ class VisionClassificationHandler:
camera_id=camera_id,
wall_start_epoch=ready.wall_lo,
wall_end_epoch=ready.wall_hi,
running_totals=state.consumption_log_totals,
)
async def handle(
@@ -99,7 +98,8 @@ class VisionClassificationHandler:
) -> None:
conf = cls_res.confidence
label = (cls_res.label or "").strip()
item_id = resolve_consumption_item_id(label, "", state.name_to_code)
t1_pid = (ready.best.t1_pid if ready is not None else "")
item_id = resolve_consumption_item_id(label, t1_pid, state.name_to_code)
voice_floor = self._s.video_voice_confirm_min_confidence
if conf < voice_floor:
return

View File

@@ -29,6 +29,7 @@ from app.services.video.session_registry import (
)
from app.services.video.stream_worker import CameraStreamWorker, redact_rtsp_url
from app.services.video.types import VideoBackendKind
from app.schemas import SurgeryConsumptionDetail, build_consumption_summary
from app.services.consumption_tsv_log import (
append_consumption_log_summary,
init_consumption_log_file,
@@ -214,11 +215,21 @@ class CameraSessionManager:
if isinstance(res, BaseException):
logger.warning("surgery task finished with error: {}", res)
totals = dict(run.state.consumption_log_totals)
details = list(run.state.details)
detail_rows = [
SurgeryConsumptionDetail(
item_id=r.item_id,
item_name=r.item_name,
qty=r.qty,
doctor_id=r.doctor_id,
timestamp=r.timestamp,
)
for r in details
]
summ = build_consumption_summary(detail_rows)
totals = {s.item_id: (s.item_name, s.total_quantity) for s in summ}
append_consumption_log_summary(surgery_id, totals)
print_consumption_summary_markdown(totals)
details = list(run.state.details)
await self._archive.persist_or_archive(surgery_id, details)
# ------------------------------------------------------------------

View File

@@ -22,8 +22,7 @@ from app.services.consumable_vision_algorithm import (
_norm_product_name,
)
from app.services.consumption_tsv_log import (
append_consumption_voice_resolution_line,
resolve_consumption_ids,
replace_pending_line_with_voice_resolution,
resolve_consumption_item_id,
)
from app.services.voice_confirm import build_prompt_text
@@ -74,8 +73,6 @@ class SurgerySessionState:
last_asr_text: str | None = None
#: 最近一次语音确认错误说明ASR/解析失败等)。
last_voice_error: str | None = None
#: 视觉时间窗落盘用量累计供停录时写汇总item_id -> 首次名称, 次数)。
consumption_log_totals: dict[str, tuple[str, int]] = field(default_factory=dict)
@dataclass
@@ -281,6 +278,7 @@ class SurgerySessionRegistry:
self._finalize_voice_confirmed_consumption_log(
state=st,
surgery_id=surgery_id,
confirmation_id=confirmation_id,
chosen_label=label,
)
try:
@@ -295,20 +293,16 @@ class SurgerySessionRegistry:
*,
state: SurgerySessionState,
surgery_id: str,
confirmation_id: str,
chosen_label: str,
) -> None:
"""待确认流程在语音落锤后:汇总 +1 最终耗材,并追加 TSV 正式行"""
"""待确认流程在语音落锤后:将 TSV 中原 pending 行替换为最终真值。停录汇总与 HTTP 一致,由 details 经 ``build_consumption_summary`` 得到"""
cl = (chosen_label or "").strip()
if not cl:
return
_, key_chosen = resolve_consumption_ids(cl, "", state.name_to_code)
tot = state.consumption_log_totals
if key_chosen not in tot:
tot[key_chosen] = (cl, 0)
nm, q = tot[key_chosen]
tot[key_chosen] = (nm, q + 1)
append_consumption_voice_resolution_line(
replace_pending_line_with_voice_resolution(
surgery_id=surgery_id,
confirmation_id=confirmation_id,
name_to_code=state.name_to_code,
chosen_label=cl,
doctor_id=self._s.video_voice_confirm_doctor_id,