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,