From cd5a904a161e0f2d95c7feef92fdced2ed1df1ae Mon Sep 17 00:00:00 2001 From: duxingchen Date: Thu, 3 Sep 2026 16:27:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=95=88=E8=83=BD=E5=88=86=E6=9E=90flow?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=AD=9B=E9=80=89=E6=94=B9=E4=B8=BA=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=BA=A7(=E8=AF=A5=E6=97=B6=E9=97=B4=E6=9C=89?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E5=8D=B3=E5=85=A5=E9=80=89),=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=AE=8C=E6=95=B4=E8=BF=94=E5=9B=9E=E4=B8=8D=E5=88=87?= =?UTF-8?q?=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/analytics_service.py | 65 ++++++++++++++--------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/backend/app/services/analytics_service.py b/backend/app/services/analytics_service.py index dbcb53a..b35480b 100644 --- a/backend/app/services/analytics_service.py +++ b/backend/app/services/analytics_service.py @@ -342,25 +342,10 @@ async def get_flow_compare( if not product_rows: return FlowResponse(devices=[], series=[]) - devices = [ - FlowDevice( - product_sn=r[1], external_serial=r[2], - material_name=r[3] or "", spec_model=r[4] or "", - lead_time=0.0, - started_at="", - ) - for r in product_rows - ] - # 显式传入身份证时按输入顺序排列 - if product_sns: - order = {sn: i for i, sn in enumerate(product_sns)} - devices.sort(key=lambda d: order.get(d.product_sn, len(order))) - id_to_sn = {r[0]: r[1] for r in product_rows} - sn_to_index = {d.product_sn: i for i, d in enumerate(devices)} product_ids = [r[0] for r in product_rows] - # ── 这些设备的全部任务(含工序名,用于区间图) ── + # ── 候选设备的全部任务(含工序名,用于区间图) ── task_rows = (await db.execute( select( Task.product_id, Task.assignee_id, Task.task_name, @@ -374,25 +359,57 @@ async def get_flow_compare( ) )).all() - # ── 解析为区间记录,并计算每台设备的 T0(最早)与 max_end(最晚) ── + # ── 设备级时间筛选 ── + # 语义:时间筛选 = "在该时间有活动的设备",而非"把任务切片只保留该时间"。 + # 设备一旦入选,其任务必须完整返回(完整生命周期),绝不因设备在筛选中无新动作 + # 而把它的历史任务清空。 + if since is not None or until is not None: + active_pids: set = set() + for row in task_rows: + pid = row[0] + start = _to_bj(row[3] or row[4]) + end = _to_bj(row[5]) if row[5] else now + if start is None: + continue + if since is not None and end < since: + continue + if until is not None and start > until: + continue + active_pids.add(pid) + product_rows = [r for r in product_rows if r[0] in active_pids] + if not product_rows: + return FlowResponse(devices=[], series=[]) + + # 显式传入身份证时按输入顺序排列 + if product_sns: + order = {sn: i for i, sn in enumerate(product_sns)} + product_rows.sort(key=lambda r: order.get(r[1], len(order))) + devices = [ + FlowDevice( + product_sn=r[1], external_serial=r[2], + material_name=r[3] or "", spec_model=r[4] or "", + lead_time=0.0, + started_at="", + ) + for r in product_rows + ] + sn_to_index = {d.product_sn: i for i, d in enumerate(devices)} + kept_ids = {r[0] for r in product_rows} + + # ── 解析为区间记录(完整生命周期,不再按时间切片) ── intervals: list[dict] = [] t0_by_device: dict[int, datetime] = {} max_end_by_device: dict[int, datetime] = {} for row in task_rows: pid, assignee_id, task_name, received_at, created_at, completed_at, task_type = row - sn = id_to_sn.get(pid) - if sn is None or sn not in sn_to_index: + if pid not in kept_ids: continue + sn = id_to_sn[pid] idx = sn_to_index[sn] start = _to_bj(received_at or created_at) end = _to_bj(completed_at) if completed_at else now if start is None: continue - # 时间筛选:任务区间与 [since, until] 有交集 - if since is not None and end is not None and end < since: - continue - if until is not None and start is not None and start > until: - continue is_main = 0 if task_type == "SPAWN" else 1 intervals.append({ "idx": idx,