From ebe3f13a58cdcc96652a96067550dae1379ed177 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Mon, 2 Feb 2026 11:11:19 +0800 Subject: [PATCH 01/12] =?UTF-8?q?1.=E5=B7=A5=E5=86=B5=E7=AC=A6=E5=8F=B7?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/get_operating_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index 3c17142..79b1b52 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -40,9 +40,9 @@ class OperatingModePredictor: "桥位施工桥梁,制梁前": 30, "桥位施工桥梁,上部结构施工中": 1, "架桥机(运梁车)通过": 7, - "桥梁主体工程完工后,第1至3个月": 7, + "桥梁主体工程完工后,第1至3个月": 7, "桥梁主体工程完工后,第4至6个月": 14, - "桥梁主体工程完工后,6个月以后": 30, + "桥梁主体工程完工后,6个月以后": 30, "轨道铺设期间,前": 30, "轨道铺设期间,后": 14, "轨道铺设完成后,第1个月": 14, From 5cfdadf02f50e83b1e59f4ea6a2b7ec71f9ab5cd Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Mon, 2 Feb 2026 11:14:11 +0800 Subject: [PATCH 02/12] =?UTF-8?q?1.=E5=B7=A5=E5=86=B5=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E7=AC=A6=E5=8F=B7=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/get_operating_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index 79b1b52..1ab050a 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -126,9 +126,9 @@ class OperatingModePredictor: "GDB_2_3": {"trigger_days": 30, "next_candidates": ["轨道板(道床)铺设后,3个月以后"]}, "GDB_AFTER_3": {"trigger_days": None, "next_candidates": None}, "YZLQ_BEFORE_JS": {"trigger_days": 1, "next_candidates": ["架桥机(运梁车)通过"]}, - "YZLQ_AFTER_JS": {"trigger_days": 7, "next_candidates": ["桥梁主体工程完工后,第1至3个月"]}, + "YZLQ_AFTER_JS": {"trigger_days": 7, "next_candidates": ["桥梁主体工程完工后,第1至3个月"]}, "QL_ZHUTI_1_3": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,第4至6个月"]}, - "QL_ZHUTI_4_6": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,6个月以后"]}, + "QL_ZHUTI_4_6": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,6个月以后"]}, "QL_ZHUTI_AFTER_6": {"trigger_days": None, "next_candidates": None}, "GD_1": {"trigger_days": 30, "next_candidates": ["轨道铺设完成后,2至3个月"]}, "GD_2_3": {"trigger_days": 60, "next_candidates": ["轨道铺设完成后,4至12个月"]}, From 28cddd74091cc820484cc2c9aa376d5e821dc508 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Tue, 3 Feb 2026 14:45:01 +0800 Subject: [PATCH 03/12] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0get=5Fuplaod=5Fdata,?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E4=BB=8A=E6=97=A5=E4=B8=8A=E4=BC=A0=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8E=A5=E5=8F=A3=202.=E7=94=A8=E6=88=B7mode?= =?UTF-8?q?l=E6=B7=BB=E5=8A=A0=E4=B8=89=E4=B8=AA=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/account.py | 67 ++++++++++++++++++++++++++++++++++++++++++- app/models/account.py | 3 ++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/app/api/account.py b/app/api/account.py index e118fbd..aa1fcdc 100644 --- a/app/api/account.py +++ b/app/api/account.py @@ -9,7 +9,7 @@ from ..schemas.account import ( AccountApiResponse, AccountListResponse,AccountGetRequestYH ) from ..services.account import AccountService - +import requests router = APIRouter(prefix="/accounts", tags=["账号管理"]) @router.post("/create", response_model=AccountApiResponse, status_code=status.HTTP_201_CREATED) @@ -124,3 +124,68 @@ def delete_account(request: AccountDeleteRequest, db: Session = Depends(get_db)) message="账号删除成功", data=None ) +# 获取今日上传的数据接口 +@router.post("/get_uplaod_data", response_model=AccountListResponse) +def get_account(request: AccountGetRequest, db: Session = Depends(get_db)): + """根据多种条件查询账号,并合并外部接口的 is_ok 字段""" + # 1. 从数据库查询账号列表 + accounts = AccountService.search_accounts( + db, + account_id=request.account_id, + username=request.username, + project_name=request.project_name, + status=request.status, + today_updated=request.today_updated, + yh_id=request.yh_id, + cl_name=request.cl_name + ) + + # 2. 调用外部接口获取 today_data,构建 user_name -> is_ok 映射 + user_is_ok_map = {} # 存储映射关系,提升匹配效率 + url = "https://engineering.yuxindazhineng.com/index/index/get_over_data" + payload = {"user_id": "68c0dbfdb7cbcd616e7c5ab5"} + + try: + # 发送POST请求并解析JSON响应 + response = requests.request("POST", url, data=payload).json() + if response['code'] == 0: + today_data = response['data'] + + # 遍历 today_data,构建映射字典 + for item in today_data: + # 校验字段是否存在,避免 KeyError 异常 + if 'user_name' in item and 'is_ok' in item: + user_name = item['user_name'] + is_ok = item['is_ok'] + user_is_ok_map[user_name] = is_ok # 键:user_name,值:is_ok + + except Exception as e: + # 捕获接口调用/解析异常,不阻断核心业务(数据库查询结果正常返回) + print(f"外部接口调用失败或数据解析异常:{str(e)}") + + # 3. 遍历 accounts,给每个账号对象添加 is_ok 字段(关键步骤) + for account in accounts: + # 匹配 username(数据库)和 user_name(外部接口) + current_username = account.username + # 从映射字典中获取 is_ok,无匹配则默认赋值 0(可根据业务调整默认值) + is_ok_value = user_is_ok_map.get(current_username, 0) + + # 给 account 对象添加 is_ok 字段(兼容 Pydantic 模型,需满足对应配置) + account.is_ok = is_ok_value + + # 4. 处理空结果返回 + if not accounts: + return AccountListResponse( + code=ResponseCode.ACCOUNT_NOT_FOUND, + message=ResponseMessage.ACCOUNT_NOT_FOUND, + total=0, + data=[] + ) + + # 5. 正常返回结果(包含 is_ok 字段的 accounts) + return AccountListResponse( + code=ResponseCode.SUCCESS, + message="查询成功", + total=len(accounts), + data=accounts + ) \ No newline at end of file diff --git a/app/models/account.py b/app/models/account.py index eab0431..1ca7046 100644 --- a/app/models/account.py +++ b/app/models/account.py @@ -17,6 +17,9 @@ class Account(Base): max_variation = Column(Integer, default=1, comment="变化量的绝对值,单位是毫米") yh_id = Column(String(1000), comment="宇恒一号用户id") cl_name = Column(String(100), nullable=True, comment="测量人员") + device_name = Column(String(1000), comment="设备名称") + device_port = Column(String(1000), comment="设备端口") + device_ip = Column(String(1000), comment="设备局域网内ip地址") # 模型转字典 From e130134791e52c47ffeeaffec0d6c38d3eda971b Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Tue, 3 Feb 2026 14:54:34 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/account.py | 114 +++++++++++++++++++++--------------------- app/models/account.py | 6 +-- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/app/api/account.py b/app/api/account.py index aa1fcdc..c9b32e7 100644 --- a/app/api/account.py +++ b/app/api/account.py @@ -124,68 +124,68 @@ def delete_account(request: AccountDeleteRequest, db: Session = Depends(get_db)) message="账号删除成功", data=None ) -# 获取今日上传的数据接口 -@router.post("/get_uplaod_data", response_model=AccountListResponse) -def get_account(request: AccountGetRequest, db: Session = Depends(get_db)): - """根据多种条件查询账号,并合并外部接口的 is_ok 字段""" - # 1. 从数据库查询账号列表 - accounts = AccountService.search_accounts( - db, - account_id=request.account_id, - username=request.username, - project_name=request.project_name, - status=request.status, - today_updated=request.today_updated, - yh_id=request.yh_id, - cl_name=request.cl_name - ) +# # 获取今日上传的数据接口 +# @router.post("/get_uplaod_data", response_model=AccountListResponse) +# def get_account(request: AccountGetRequest, db: Session = Depends(get_db)): +# """根据多种条件查询账号,并合并外部接口的 is_ok 字段""" +# # 1. 从数据库查询账号列表 +# accounts = AccountService.search_accounts( +# db, +# account_id=request.account_id, +# username=request.username, +# project_name=request.project_name, +# status=request.status, +# today_updated=request.today_updated, +# yh_id=request.yh_id, +# cl_name=request.cl_name +# ) - # 2. 调用外部接口获取 today_data,构建 user_name -> is_ok 映射 - user_is_ok_map = {} # 存储映射关系,提升匹配效率 - url = "https://engineering.yuxindazhineng.com/index/index/get_over_data" - payload = {"user_id": "68c0dbfdb7cbcd616e7c5ab5"} +# # 2. 调用外部接口获取 today_data,构建 user_name -> is_ok 映射 +# user_is_ok_map = {} # 存储映射关系,提升匹配效率 +# url = "https://engineering.yuxindazhineng.com/index/index/get_over_data" +# payload = {"user_id": "68c0dbfdb7cbcd616e7c5ab5"} - try: - # 发送POST请求并解析JSON响应 - response = requests.request("POST", url, data=payload).json() - if response['code'] == 0: - today_data = response['data'] +# try: +# # 发送POST请求并解析JSON响应 +# response = requests.request("POST", url, data=payload).json() +# if response['code'] == 0: +# today_data = response['data'] - # 遍历 today_data,构建映射字典 - for item in today_data: - # 校验字段是否存在,避免 KeyError 异常 - if 'user_name' in item and 'is_ok' in item: - user_name = item['user_name'] - is_ok = item['is_ok'] - user_is_ok_map[user_name] = is_ok # 键:user_name,值:is_ok +# # 遍历 today_data,构建映射字典 +# for item in today_data: +# # 校验字段是否存在,避免 KeyError 异常 +# if 'user_name' in item and 'is_ok' in item: +# user_name = item['user_name'] +# is_ok = item['is_ok'] +# user_is_ok_map[user_name] = is_ok # 键:user_name,值:is_ok - except Exception as e: - # 捕获接口调用/解析异常,不阻断核心业务(数据库查询结果正常返回) - print(f"外部接口调用失败或数据解析异常:{str(e)}") +# except Exception as e: +# # 捕获接口调用/解析异常,不阻断核心业务(数据库查询结果正常返回) +# print(f"外部接口调用失败或数据解析异常:{str(e)}") - # 3. 遍历 accounts,给每个账号对象添加 is_ok 字段(关键步骤) - for account in accounts: - # 匹配 username(数据库)和 user_name(外部接口) - current_username = account.username - # 从映射字典中获取 is_ok,无匹配则默认赋值 0(可根据业务调整默认值) - is_ok_value = user_is_ok_map.get(current_username, 0) +# # 3. 遍历 accounts,给每个账号对象添加 is_ok 字段(关键步骤) +# for account in accounts: +# # 匹配 username(数据库)和 user_name(外部接口) +# current_username = account.username +# # 从映射字典中获取 is_ok,无匹配则默认赋值 0(可根据业务调整默认值) +# is_ok_value = user_is_ok_map.get(current_username, 0) - # 给 account 对象添加 is_ok 字段(兼容 Pydantic 模型,需满足对应配置) - account.is_ok = is_ok_value +# # 给 account 对象添加 is_ok 字段(兼容 Pydantic 模型,需满足对应配置) +# account.is_ok = is_ok_value - # 4. 处理空结果返回 - if not accounts: - return AccountListResponse( - code=ResponseCode.ACCOUNT_NOT_FOUND, - message=ResponseMessage.ACCOUNT_NOT_FOUND, - total=0, - data=[] - ) +# # 4. 处理空结果返回 +# if not accounts: +# return AccountListResponse( +# code=ResponseCode.ACCOUNT_NOT_FOUND, +# message=ResponseMessage.ACCOUNT_NOT_FOUND, +# total=0, +# data=[] +# ) - # 5. 正常返回结果(包含 is_ok 字段的 accounts) - return AccountListResponse( - code=ResponseCode.SUCCESS, - message="查询成功", - total=len(accounts), - data=accounts - ) \ No newline at end of file +# # 5. 正常返回结果(包含 is_ok 字段的 accounts) +# return AccountListResponse( +# code=ResponseCode.SUCCESS, +# message="查询成功", +# total=len(accounts), +# data=accounts +# ) \ No newline at end of file diff --git a/app/models/account.py b/app/models/account.py index 1ca7046..b4f828f 100644 --- a/app/models/account.py +++ b/app/models/account.py @@ -17,9 +17,9 @@ class Account(Base): max_variation = Column(Integer, default=1, comment="变化量的绝对值,单位是毫米") yh_id = Column(String(1000), comment="宇恒一号用户id") cl_name = Column(String(100), nullable=True, comment="测量人员") - device_name = Column(String(1000), comment="设备名称") - device_port = Column(String(1000), comment="设备端口") - device_ip = Column(String(1000), comment="设备局域网内ip地址") + # device_name = Column(String(1000), comment="设备名称") + # device_port = Column(String(1000), comment="设备端口") + # device_ip = Column(String(1000), comment="设备局域网内ip地址") # 模型转字典 From cd3ced88334616ade64452d33b5133851fdd1b13 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Tue, 3 Feb 2026 15:10:04 +0800 Subject: [PATCH 05/12] =?UTF-8?q?1.=E5=88=A0=E9=99=A4=E5=BC=95=E7=94=A8req?= =?UTF-8?q?uest=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/account.py | 67 +--------------------------------------------- 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/app/api/account.py b/app/api/account.py index c9b32e7..e118fbd 100644 --- a/app/api/account.py +++ b/app/api/account.py @@ -9,7 +9,7 @@ from ..schemas.account import ( AccountApiResponse, AccountListResponse,AccountGetRequestYH ) from ..services.account import AccountService -import requests + router = APIRouter(prefix="/accounts", tags=["账号管理"]) @router.post("/create", response_model=AccountApiResponse, status_code=status.HTTP_201_CREATED) @@ -124,68 +124,3 @@ def delete_account(request: AccountDeleteRequest, db: Session = Depends(get_db)) message="账号删除成功", data=None ) -# # 获取今日上传的数据接口 -# @router.post("/get_uplaod_data", response_model=AccountListResponse) -# def get_account(request: AccountGetRequest, db: Session = Depends(get_db)): -# """根据多种条件查询账号,并合并外部接口的 is_ok 字段""" -# # 1. 从数据库查询账号列表 -# accounts = AccountService.search_accounts( -# db, -# account_id=request.account_id, -# username=request.username, -# project_name=request.project_name, -# status=request.status, -# today_updated=request.today_updated, -# yh_id=request.yh_id, -# cl_name=request.cl_name -# ) - -# # 2. 调用外部接口获取 today_data,构建 user_name -> is_ok 映射 -# user_is_ok_map = {} # 存储映射关系,提升匹配效率 -# url = "https://engineering.yuxindazhineng.com/index/index/get_over_data" -# payload = {"user_id": "68c0dbfdb7cbcd616e7c5ab5"} - -# try: -# # 发送POST请求并解析JSON响应 -# response = requests.request("POST", url, data=payload).json() -# if response['code'] == 0: -# today_data = response['data'] - -# # 遍历 today_data,构建映射字典 -# for item in today_data: -# # 校验字段是否存在,避免 KeyError 异常 -# if 'user_name' in item and 'is_ok' in item: -# user_name = item['user_name'] -# is_ok = item['is_ok'] -# user_is_ok_map[user_name] = is_ok # 键:user_name,值:is_ok - -# except Exception as e: -# # 捕获接口调用/解析异常,不阻断核心业务(数据库查询结果正常返回) -# print(f"外部接口调用失败或数据解析异常:{str(e)}") - -# # 3. 遍历 accounts,给每个账号对象添加 is_ok 字段(关键步骤) -# for account in accounts: -# # 匹配 username(数据库)和 user_name(外部接口) -# current_username = account.username -# # 从映射字典中获取 is_ok,无匹配则默认赋值 0(可根据业务调整默认值) -# is_ok_value = user_is_ok_map.get(current_username, 0) - -# # 给 account 对象添加 is_ok 字段(兼容 Pydantic 模型,需满足对应配置) -# account.is_ok = is_ok_value - -# # 4. 处理空结果返回 -# if not accounts: -# return AccountListResponse( -# code=ResponseCode.ACCOUNT_NOT_FOUND, -# message=ResponseMessage.ACCOUNT_NOT_FOUND, -# total=0, -# data=[] -# ) - -# # 5. 正常返回结果(包含 is_ok 字段的 accounts) -# return AccountListResponse( -# code=ResponseCode.SUCCESS, -# message="查询成功", -# total=len(accounts), -# data=accounts -# ) \ No newline at end of file From 192c299f80a27684041c06870f87f3c464d26244 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Tue, 3 Feb 2026 16:31:26 +0800 Subject: [PATCH 06/12] =?UTF-8?q?1.=E8=8E=B7=E5=8F=96=E4=BB=8A=E6=97=A5?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=9A=84=E6=95=B0=E6=8D=AE=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E7=94=A8urllib=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/account.py | 106 ++++++++++++++++++++++++++++++++++++++++- app/models/account.py | 7 +-- app/schemas/account.py | 13 ++++- 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/app/api/account.py b/app/api/account.py index e118fbd..4019df7 100644 --- a/app/api/account.py +++ b/app/api/account.py @@ -9,7 +9,12 @@ from ..schemas.account import ( AccountApiResponse, AccountListResponse,AccountGetRequestYH ) from ..services.account import AccountService - +import json +from typing import List, Optional +from urllib.error import HTTPError, URLError +from urllib import request as urllib_request +from urllib.parse import urlencode +from socket import timeout router = APIRouter(prefix="/accounts", tags=["账号管理"]) @router.post("/create", response_model=AccountApiResponse, status_code=status.HTTP_201_CREATED) @@ -112,6 +117,7 @@ def update_account(request: AccountUpdateRequest, db: Session = Depends(get_db)) @router.post("/delete", response_model=AccountApiResponse) def delete_account(request: AccountDeleteRequest, db: Session = Depends(get_db)): + """删除账号""" if not AccountService.delete_account(db, request.account_id): return AccountApiResponse( @@ -124,3 +130,101 @@ def delete_account(request: AccountDeleteRequest, db: Session = Depends(get_db)) message="账号删除成功", data=None ) +# 获取今日上传的数据接口 +@router.post("/get_uplaod_data", response_model=AccountListResponse) +def get_uplaod_data(request: AccountGetRequest, db: Session = Depends(get_db)): + """根据多种条件查询账号,并合并外部接口的 is_ok 字段(仅返回 today_data 中存在的账号)""" + # 1. 从数据库查询账号列表(原有逻辑不变) + accounts = AccountService.search_accounts( + db, + account_id=request.account_id, + username=request.username, + project_name=request.project_name, + status=request.status, + today_updated=request.today_updated, + yh_id=request.yh_id, + cl_name=request.cl_name + ) + + # 2. 调用外部接口构建 user_is_ok_map(替换为 urllib 实现,修复命名冲突和超时异常) + user_is_ok_map = {} + api_url = "https://engineering.yuxindazhineng.com/index/index/get_over_data" + payload = {"user_id": "68c0dbfdb7cbcd616e7c5ab5"} + + try: + # 步骤1:将表单 payload 转为 URL 编码的字节流(urllib POST 要求数据为字节流) + payload_bytes = urlencode(payload).encode("utf-8") + + # 步骤2:构造 Request 对象(使用重命名后的 urllib_request,避免冲突) + req = urllib_request.Request( + url=api_url, + data=payload_bytes, + method="POST" # 显式指定 POST 方法 + ) + + # 步骤3:发送请求并读取响应(设置 10 秒超时,避免接口挂起) + with urllib_request.urlopen(req, timeout=10) as resp: + # 读取响应字节流并解码为 UTF-8 字符串 + response_str = resp.read().decode("utf-8") + + # 步骤4:将 JSON 字符串解析为字典 + api_response = json.loads(response_str) + + # 步骤5:验证接口返回格式并构建 user_is_ok_map 映射 + if api_response.get("code") == 0: + today_data = api_response.get("data", []) # 给默认值,避免 KeyError + for item in today_data: + # 安全获取字段并校验类型 + if 'user_name' in item and 'is_ok' in item: + user_is_ok_map[item['user_name']] = item['is_ok'] + + except HTTPError as e: + # 捕获 HTTP 状态码错误(4xx/5xx) + print(f"外部接口 HTTP 错误:{e.code} - {e.reason}") + except TimeoutError: # 修复:正确的超时异常名称(首字母大写,原 timeout 会未定义报错) + # 捕获请求超时异常 + print(f"外部接口调用超时(超过 10 秒)") + except URLError as e: + # 捕获 URL 解析错误、网络连接错误等 + print(f"外部接口网络错误:{e.reason}") + except json.JSONDecodeError: + # 捕获非合法 JSON 格式响应 + print(f"外部接口返回数据格式错误,非合法 JSON 字符串") + except Exception as e: + # 捕获其他未知异常 + print(f"外部接口处理未知异常:{str(e)}") + + # 3. 关键修改:仅保留 today_data 中存在的账号(核心过滤逻辑) + accounts_with_is_ok = [] + for account in accounts: + # 步骤1:将 AccountResponse 对象转为字典(Pydantic 模型自带 dict() 方法) + account_dict = account.dict() + + # 步骤2:获取当前账号 username,判断是否在 user_is_ok_map 中(不存在则跳过) + current_username = account_dict.get("username", "") + if current_username not in user_is_ok_map: + continue # 核心:过滤掉 today_data 中没有的账号 + + # 步骤3:给字典添加 is_ok 字段(仅处理存在的账号,无需默认值 0) + account_dict['is_ok'] = user_is_ok_map[current_username] + + # 步骤4:将处理后的字典加入新列表 + accounts_with_is_ok.append(account_dict) + + # 4. 处理空结果返回(原有逻辑不变) + if not accounts_with_is_ok: + return AccountListResponse( + code=ResponseCode.ACCOUNT_NOT_FOUND, + message=ResponseMessage.ACCOUNT_NOT_FOUND, + total=0, + data=[] + ) + + # 5. 正常返回:数据传入新列表 accounts_with_is_ok(仅包含 today_data 中的账号) + # print(accounts_with_is_ok) + return AccountListResponse( + code=ResponseCode.SUCCESS, + message="查询成功", + total=len(accounts_with_is_ok), + data=accounts_with_is_ok + ) diff --git a/app/models/account.py b/app/models/account.py index b4f828f..67fd059 100644 --- a/app/models/account.py +++ b/app/models/account.py @@ -17,9 +17,10 @@ class Account(Base): max_variation = Column(Integer, default=1, comment="变化量的绝对值,单位是毫米") yh_id = Column(String(1000), comment="宇恒一号用户id") cl_name = Column(String(100), nullable=True, comment="测量人员") - # device_name = Column(String(1000), comment="设备名称") - # device_port = Column(String(1000), comment="设备端口") - # device_ip = Column(String(1000), comment="设备局域网内ip地址") + device_name = Column(String(1000), comment="设备名称") + device_port = Column(String(1000), comment="设备端口") + device_ip = Column(String(1000), comment="设备局域网内ip地址") + is_ok = Column(Integer, default=0, comment="是否可以上传") # 模型转字典 diff --git a/app/schemas/account.py b/app/schemas/account.py index 8cd8089..79120da 100644 --- a/app/schemas/account.py +++ b/app/schemas/account.py @@ -12,6 +12,10 @@ class AccountBase(BaseModel): max_variation: Optional[int] = None yh_id: Optional[str] = None cl_name: Optional[str] = None + device_name: Optional[str] = None + device_port: Optional[str] = None + device_ip: Optional[str] = None + is_ok: Optional[int] = None class AccountCreate(AccountBase): pass @@ -49,7 +53,11 @@ class AccountResponse(AccountBase): update_time=account.update_time, max_variation=account.max_variation, yh_id=account.yh_id, - cl_name=account.cl_name + cl_name=account.cl_name, + device_name=account.device_name, + device_port=account.device_port, + device_ip=account.device_ip, + is_ok = account.is_ok ) class AccountListRequest(BaseModel): @@ -90,4 +98,5 @@ class AccountListResponse(BaseModel): code: int = 0 message: str total: int - data: List[AccountResponse] = [] \ No newline at end of file + data: List[AccountResponse] = [] + From 539c2941e18b4efa7adf770c46d800d187365f9c Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Tue, 3 Feb 2026 16:44:34 +0800 Subject: [PATCH 07/12] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9acconut=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=A5=E5=8F=A3=E6=96=B0=E5=A2=9Edevice=5Fname?= =?UTF-8?q?=EF=BC=8Cdevice=5Fport=EF=BC=8Cdevice=5Fip=EF=BC=8Cis=5Fok?= =?UTF-8?q?=E5=8F=AF=E4=BF=AE=E6=94=B9=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/schemas/account.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/schemas/account.py b/app/schemas/account.py index 79120da..32fcdfc 100644 --- a/app/schemas/account.py +++ b/app/schemas/account.py @@ -28,6 +28,10 @@ class AccountUpdate(BaseModel): project_name: Optional[str] = None update_time: Optional[str] = None cl_name: Optional[str] = None + device_name: Optional[str] = None + device_port: Optional[str] = None + device_ip: Optional[str] = None + is_ok: Optional[int] = None class AccountResponse(AccountBase): account_id: int From e76711af9dfc63f16699fcc5bf74156da0615813 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Wed, 4 Feb 2026 15:42:45 +0800 Subject: [PATCH 08/12] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9=E5=B7=A5=E5=86=B5?= =?UTF-8?q?=E6=8E=A8=E7=90=86=EF=BC=8C=E6=8A=8A=E5=86=AC=E4=BC=91=E6=83=85?= =?UTF-8?q?=E5=86=B5=E5=8A=A0=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/get_operating_mode.py | 114 ++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 36 deletions(-) diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index 1ab050a..aad5a5c 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -9,6 +9,7 @@ class OperatingModePredictor: 2. 输出为一维列表,仅保留每个内嵌列表的最新记录,新增工况推导结果字段 3. 无需切换工况时,next_workinfo返回当前工况名称;需要切换时返回目标工况名称 4. 时间计算仅按日期(天)维度,忽略时分秒 + 5. 支持冬休场景:当前工况为冬休时,以冬休前上一个有效工况作为切换判断依据 """ def __init__(self): @@ -19,6 +20,8 @@ class OperatingModePredictor: self.condition_group = self._load_condition_group() # 工况切换触发规则(键:分组ID,值:触发天数+目标工况候选) self.group_transition_rules = self._load_group_transition_rules() + # 冬休工况标识(仅保留“冬休”) + self.winter_break_labels = self._load_winter_break_labels() def _load_base_periods(self): """加载基础工况配置(私有方法,内部使用)""" @@ -62,12 +65,13 @@ class OperatingModePredictor: "架桥机(运梁车) 首次通过后": 7, "轨道板(道床)铺设后,第1个月": 14, "轨道板(道床)铺设后,第2至3个月": 30, - "轨道板(道床)铺设后,3个月以后": 90 + "轨道板(道床)铺设后,3个月以后": 90, + "冬休": 0 # 仅保留冬休 } def _load_condition_group(self): """加载工况分组规则(私有方法,内部使用)""" - return { + condition_group = { "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", @@ -109,6 +113,12 @@ class OperatingModePredictor: "填筑或堆载,两次填筑间隔时间较长情况": "STATIC", "铺路或堆载,两次铺路间隔时间较长情况": "STATIC" } + # 冬休分组(仅冬休) + winter_break_groups = { + "冬休": "STATIC" + } + condition_group.update(winter_break_groups) + return condition_group def _load_group_transition_rules(self): """加载工况切换触发规则(私有方法,内部使用)""" @@ -140,6 +150,10 @@ class OperatingModePredictor: "STATIC": {"trigger_days": None, "next_candidates": None} } + def _load_winter_break_labels(self): + """冬休标识(仅冬休)""" + return {"冬休"} + def _parse_to_date(self, time_str): """ 私有辅助方法:将时间字符串解析为日期对象,仅保留年月日,忽略时分秒 @@ -158,54 +172,68 @@ class OperatingModePredictor: """ 私有辅助方法:从倒序数据中提取时间统计信息 :param data: 单个point_id的倒序数据列表 - :param workinfo: 当前工况名称 + :param workinfo: 目标工况名称(冬休时为冬休前上一工况) :return: 元组 (首次测量日期date对象, 首次到末次持续天数, 今日与首次测量天数差) """ - # 筛选当前工况的有效记录 - reversed_records = [d for d in data if d.get("workinfoname") == workinfo] - if not reversed_records: + # 筛选目标工况的有效记录(忽略冬休) + target_records = [ + d for d in data + if d.get("workinfoname") == workinfo + and d.get("workinfoname") not in self.winter_break_labels + ] + if not target_records: return None, 0, 0 - # 解析所有有效记录的日期 - reversed_dates = [] - for item in reversed_records: + target_dates = [] + for item in target_records: d = self._parse_to_date(item.get("MTIME_W")) if d: - reversed_dates.append(d) - if not reversed_dates: + target_dates.append(d) + if not target_dates: return None, 0, 0 - # 提取倒序数据的最新、最旧日期 - last_date = reversed_dates[0] - first_date = reversed_dates[-1] + last_date = target_dates[0] + first_date = target_dates[-1] - # 计算累计天数和今日与首次测量的天数差 cumulative_days = (last_date - first_date).days today = date.today() days_to_today = (today - first_date).days if first_date else 0 return first_date, cumulative_days, days_to_today + def _get_pre_winter_break_workinfo(self, inner_data_list): + """ + 私有辅助方法:从倒序数据中提取冬休前的上一个有效工况 + :param inner_data_list: 单个point_id的倒序数据列表 + :return: 冬休前工况名称 / None(未找到时) + """ + if not inner_data_list: + return None + + # 倒序遍历,跳过冬休,找第一个有效工况 + for record in inner_data_list: + current_work = record.get("workinfoname") + if current_work and current_work not in self.winter_break_labels and current_work in self.base_periods: + return current_work + return None + def _match_next_condition(self, current_name, candidates): """ 私有辅助方法:按符号风格匹配下一个工况名称(全角/半角括号对应) - :param current_name: 当前工况名称 + :param current_name: 当前工况名称(冬休时为冬休前上一工况) :param candidates: 目标工况候选列表 :return: 匹配的工况名称 / 当前工况名称(无候选时) """ if not candidates: return current_name - # 优先匹配全角括号工况 if "(" in current_name: for cand in candidates: if "(" in cand: return cand - # 再匹配半角括号工况 if "(" in current_name: for cand in candidates: if "(" in cand: return cand - # 无对应符号时,返回第一个候选工况 return candidates[0] def _validate_point_id(self, inner_list): @@ -230,17 +258,13 @@ class OperatingModePredictor: """ final_result_1d = [] - # 遍历二维列表,逐个处理每个point_id的内嵌数据 for inner_data_list in data_2d_list: - # 跳过空列表 if not isinstance(inner_data_list, list) or len(inner_data_list) == 0: continue - # 1. 提取当前内嵌列表的最新记录(倒序数据索引0为最新) latest_record = inner_data_list[0].copy() - - # 2. 校验point_id一致性 point_id = self._validate_point_id(inner_data_list) + if not point_id: latest_record.update({ "status": "fail", @@ -248,12 +272,12 @@ class OperatingModePredictor: "first_measure_date": None, "days_from_first_to_today": None, "next_workinfo": None, + "judge_based_workinfo": None, "error_msg": "point_id不一致或缺失" }) final_result_1d.append(latest_record) continue - # 3. 提取并校验当前工况 current_workinfo = latest_record.get("workinfoname") if not current_workinfo or current_workinfo not in self.base_periods: latest_record.update({ @@ -262,41 +286,59 @@ class OperatingModePredictor: "first_measure_date": None, "days_from_first_to_today": None, "next_workinfo": None, + "judge_based_workinfo": None, "error_msg": "工况无效或缺失" }) final_result_1d.append(latest_record) continue - # 4. 提取时间统计信息 + # 冬休逻辑:当前是冬休 → 取冬休前工况作为判断基准 + if current_workinfo in self.winter_break_labels: + judge_based_workinfo = self._get_pre_winter_break_workinfo(inner_data_list) + if not judge_based_workinfo: + latest_record.update({ + "status": "fail", + "current_workinfo": current_workinfo, + "first_measure_date": None, + "days_from_first_to_today": None, + "next_workinfo": None, + "judge_based_workinfo": None, + "error_msg": "冬休前未找到有效工况" + }) + final_result_1d.append(latest_record) + continue + else: + judge_based_workinfo = current_workinfo + + # 用判断基准工况计算时间 first_dt, cumulative_days, days_to_today = self._get_time_statistics_from_reversed( - inner_data_list, current_workinfo + inner_data_list, judge_based_workinfo ) first_measure_date = first_dt.strftime("%Y-%m-%d") if first_dt else None - # 5. 判断工况切换条件,推导下一工况 - group_id = self.condition_group.get(current_workinfo, "STATIC") + # 推导下一工况 + group_id = self.condition_group.get(judge_based_workinfo, "STATIC") rule = self.group_transition_rules.get(group_id, {}) trigger_days = rule.get("trigger_days") next_candidates = rule.get("next_candidates", []) if trigger_days is not None and cumulative_days >= trigger_days: - # 满足切换条件:返回目标工况 - next_workname = self._match_next_condition(current_workinfo, next_candidates) + next_workname = self._match_next_condition(judge_based_workinfo, next_candidates) else: - # 不满足切换条件:返回当前工况 - next_workname = current_workinfo + next_workname = judge_based_workinfo - # 6. 组装结果字段,更新最新记录 + # 组装结果 latest_record.update({ "status": "success", "current_workinfo": current_workinfo, + "judge_based_workinfo": judge_based_workinfo, "first_measure_date": first_measure_date, "days_from_first_to_today": days_to_today, "next_workinfo": next_workname, - "point_id": point_id + "point_id": point_id, + "error_msg": "" }) - # 7. 加入最终结果列表 final_result_1d.append(latest_record) return final_result_1d \ No newline at end of file From f7e77093dfc4821307ab99cdeff83538663c7236 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Thu, 5 Feb 2026 15:46:07 +0800 Subject: [PATCH 09/12] =?UTF-8?q?1.=E5=B7=A5=E5=86=B5=E7=AC=A6=E5=8F=B7?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/get_operating_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index aad5a5c..98ac99e 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -43,7 +43,7 @@ class OperatingModePredictor: "桥位施工桥梁,制梁前": 30, "桥位施工桥梁,上部结构施工中": 1, "架桥机(运梁车)通过": 7, - "桥梁主体工程完工后,第1至3个月": 7, + "桥梁主体工程完工后,第1至3个月": 7, "桥梁主体工程完工后,第4至6个月": 14, "桥梁主体工程完工后,6个月以后": 30, "轨道铺设期间,前": 30, @@ -136,7 +136,7 @@ class OperatingModePredictor: "GDB_2_3": {"trigger_days": 30, "next_candidates": ["轨道板(道床)铺设后,3个月以后"]}, "GDB_AFTER_3": {"trigger_days": None, "next_candidates": None}, "YZLQ_BEFORE_JS": {"trigger_days": 1, "next_candidates": ["架桥机(运梁车)通过"]}, - "YZLQ_AFTER_JS": {"trigger_days": 7, "next_candidates": ["桥梁主体工程完工后,第1至3个月"]}, + "YZLQ_AFTER_JS": {"trigger_days": 7, "next_candidates": ["桥梁主体工程完工后,第1至3个月"]}, "QL_ZHUTI_1_3": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,第4至6个月"]}, "QL_ZHUTI_4_6": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,6个月以后"]}, "QL_ZHUTI_AFTER_6": {"trigger_days": None, "next_candidates": None}, From fdf07e0dab0ca583a981858e8f5dc04e97526797 Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Fri, 6 Feb 2026 18:16:44 +0800 Subject: [PATCH 10/12] =?UTF-8?q?1.=E9=80=82=E9=85=8D=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E5=B7=A5=E5=86=B5=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/construction_monitor.py | 105 ++++++++++++------------- app/utils/get_operating_mode.py | 123 ++++++++++++++++++++++-------- 2 files changed, 145 insertions(+), 83 deletions(-) diff --git a/app/utils/construction_monitor.py b/app/utils/construction_monitor.py index ea61261..a49ab58 100644 --- a/app/utils/construction_monitor.py +++ b/app/utils/construction_monitor.py @@ -3,15 +3,31 @@ from typing import List, Dict import warnings import copy # 注意:根据实际项目路径调整导入,若本地测试可注释掉 -from ..core.logging_config import get_logger +# from ..core.logging_config import get_logger import json + +# 本地测试时的logger替代(可根据实际情况删除) +class MockLogger: + def warning(self, msg): + print(f"[WARNING] {msg}") + def info(self, msg): + print(f"[INFO] {msg}") + +def get_logger(name): + return MockLogger() + logger = get_logger(__name__) class ConstructionMonitorUtils: def __init__(self): - # 原始工况周期映射表(保持不变) + # 原始工况周期映射表(保持不变,重复key会自动保留后面的) self.base_periods = { + "路基或预压土填筑,连续填筑":1, + "路基或预压土填筑,两次填筑间隔时间较长":7, + "预压土或路基填筑完成,第1~3个月":7, + "预压土或路基填筑完成,第4~6个月":14, "仰拱(底板)施工完成后,第1个月": 7, + "预压土或路基填筑完成,6个月以后":30, "仰拱(底板)施工完成后,第2至3个月": 14, "仰拱(底板)施工完成后,3个月以后": 30, "仰拱(底板)施工完成后,第1个月": 7, # 原:仰拱(底板)施工完成后,第1个月 @@ -50,56 +66,43 @@ class ConstructionMonitorUtils: "架桥机(运梁车) 首次通过后": 7, # 原:架桥机(运梁车)首次通过后(仅加空格) "轨道板(道床)铺设后,第1个月": 14, # 原:轨道板(道床)铺设后,第1个月 "轨道板(道床)铺设后,第2至3个月": 30, # 原:轨道板(道床)铺设后,第2至3个月 - "轨道板(道床)铺设后,3个月以后": 90 # 未出现在待处理集,保留原始格式 + "轨道板(道床)铺设后,3个月以后": 90, + "架桥机(运梁车)首次通过前": 1, + "架桥机(运梁车)首次通过后,前3天": 1, + "架桥机(运梁车)首次通过后": 7, + "轨道板铺设前": 14, + "轨道板(道床)铺设后,第1至3个月": 14, + "轨道板(道床)铺设后,第4至6个月": 30, + "轨道板(道床)铺设后,6个月以后": 90, + "站场填方路基段填筑完成至静态验收": 14, + "桥墩(台)地面处拆模后": 30, + "敦身混凝土施工": 30, + "预制梁桥,架梁前": 30, + "预制梁桥,预制梁架设前": 1, + "预制梁桥预制梁架设后": 7, + "现浇梁,浇筑前": 30, + "现浇梁上部结构施工中": 1, + "架桥机(运梁车)通过": 2, + "桥梁主体工程完工后,第1至3个月": 7, + "桥梁主体工程完工后,第4至6个月": 14, + "侨梁主体工程完工后,6个月以后": 30, + "轨道铺设,前": 30, + "轨道铺设,后": 14, + "轨道铺设完成后,第1个月": 14, + "轨道铺设完成后,2至3个月": 30, + "轨道铺设完成后,4至12个月": 90, + "轨道铺设完成后,12个月以后": 180, + "仰拱(底板)施工完成后,第1个月": 7, + "仰拱(底板)施工完成后第,2至3个月": 14, + "仰拱(底板)施工完成后,3个月以后": 30, + "轨道板铺设前": 14, + "无砟轨道铺设后,第1至3个月": 30, + "无砟轨道铺设后,4至12个月": 90, + "无砟轨道铺设后,12个月以后": 180, + "特殊地段隧道施工完成后至静态验收": 14 } - # 构建中英文括号+逗号兼容映射表 - self.compatible_periods = self._build_compatible_brackets_map() - - - def _build_compatible_brackets_map(self) -> Dict[str, int]: - """构建支持中英文括号、中英文逗号,且忽略所有空格的兼容映射表""" - compatible_map = {} - for original_key, period in self.base_periods.items(): - # ========== 第一步:处理原始key的空格和符号,生成基础变体 ========== - # 1. 清洗空格:去除首尾+全角空格+合并连续空格+最终删除所有空格 - key_no_space = original_key.strip().replace(" ", " ").replace(" ", " ").replace(" ", "") - # 2. 原始key(未清洗空格) - compatible_map[original_key] = period - # 3. 无空格的原始符号key - if key_no_space not in compatible_map: - compatible_map[key_no_space] = period - - # ========== 第二步:生成中文括号变体(含空格/无空格) ========== - # 带空格的中文括号key - chinese_bracket_key = original_key.replace("(", "(").replace(")", ")") - if chinese_bracket_key != original_key and chinese_bracket_key not in compatible_map: - compatible_map[chinese_bracket_key] = period - # 无空格的中文括号key - chinese_bracket_no_space = key_no_space.replace("(", "(").replace(")", ")") - if chinese_bracket_no_space not in compatible_map: - compatible_map[chinese_bracket_no_space] = period - - # ========== 第三步:生成中文逗号变体(含空格/无空格) ========== - # 带空格的中文逗号key - chinese_comma_key = original_key.replace(",", ",") - if chinese_comma_key != original_key and chinese_comma_key not in compatible_map: - compatible_map[chinese_comma_key] = period - # 无空格的中文逗号key - chinese_comma_no_space = key_no_space.replace(",", ",") - if chinese_comma_no_space not in compatible_map: - compatible_map[chinese_comma_no_space] = period - - # ========== 第四步:生成中文括号+逗号混合变体(含空格/无空格) ========== - # 带空格的混合变体key - mixed_key = chinese_bracket_key.replace(",", ",") - if mixed_key != original_key and mixed_key not in compatible_map: - compatible_map[mixed_key] = period - # 无空格的混合变体key - mixed_no_space = chinese_bracket_no_space.replace(",", ",") - if mixed_no_space not in compatible_map: - compatible_map[mixed_no_space] = period - - return compatible_map + # 移除兼容映射表,直接使用原始base_periods(字典特性:重复key自动保留后面的) + self.compatible_periods = self.base_periods def get_due_data(self, input_data: List[List[Dict]], start: int = 0, end: int = 0, current_date: datetime = None) -> Dict[str, List[Dict]]: result = {"winter": [], "data": [], "error_data": []} diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index 98ac99e..23d48cc 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -14,9 +14,9 @@ class OperatingModePredictor: def __init__(self): """初始化类,加载内置的工况配置、分组规则和切换触发规则""" - # 基础工况配置(键:工况名称,值:基础监测周期参考) + # 基础工况配置(更新为最新的工况列表,重复key保留后面的值) self.base_periods = self._load_base_periods() - # 工况分组(将同义不同格式的工况归类,共用切换规则) + # 工况分组(同步更新分组规则,匹配最新工况名称) self.condition_group = self._load_condition_group() # 工况切换触发规则(键:分组ID,值:触发天数+目标工况候选) self.group_transition_rules = self._load_group_transition_rules() @@ -24,40 +24,45 @@ class OperatingModePredictor: self.winter_break_labels = self._load_winter_break_labels() def _load_base_periods(self): - """加载基础工况配置(私有方法,内部使用)""" + """加载基础工况配置(更新为最新工况列表,重复key自动保留后面的值)""" return { + "路基或预压土填筑,连续填筑":1, + "路基或预压土填筑,两次填筑间隔时间较长":7, + "预压土或路基填筑完成,第1~3个月":7, + "预压土或路基填筑完成,第4~6个月":14, "仰拱(底板)施工完成后,第1个月": 7, + "预压土或路基填筑完成,6个月以后":30, "仰拱(底板)施工完成后,第2至3个月": 14, "仰拱(底板)施工完成后,3个月以后": 30, - "仰拱(底板)施工完成后,第1个月": 7, - "仰拱(底板)施工完成后,第2至3个月": 14, - "仰拱(底板)施工完成后,3个月以后": 30, - "无砟轨道铺设后,第1至3个月": 30, - "无砟轨道铺设后,4至12个月": 90, - "无砟轨道铺设后,12个月以后": 180, + "仰拱(底板)施工完成后,第1个月": 7, # 重复key,保留此值 + "仰拱(底板)施工完成后,第2至3个月": 14, # 重复key,保留此值 + "仰拱(底板)施工完成后,3个月以后": 30, # 重复key,保留此值 + "无砟轨道铺设后,第1至3个月": 30, # 重复key,保留此值 + "无砟轨道铺设后,4至12个月": 90, # 重复key,保留此值 + "无砟轨道铺设后,12个月以后": 180, # 重复key,保留此值 "墩台施工到一定高度": 30, "墩台混凝土施工": 30, - "预制梁桥,架梁前": 30, - "预制梁桥,预制梁架设前": 1, + "预制梁桥,架梁前": 30, # 重复key,保留此值 + "预制梁桥,预制梁架设前": 1, # 重复key,保留此值 "预制梁桥,预制梁架设后": 7, "桥位施工桥梁,制梁前": 30, "桥位施工桥梁,上部结构施工中": 1, - "架桥机(运梁车)通过": 7, + "架桥机(运梁车)通过": 2, # 重复key,保留此值(原7→2) "桥梁主体工程完工后,第1至3个月": 7, - "桥梁主体工程完工后,第4至6个月": 14, - "桥梁主体工程完工后,6个月以后": 30, + "桥梁主体工程完工后,第4至6个月": 14, # 重复key,保留此值 + "桥梁主体工程完工后,6个月以后": 30, # 重复key,保留此值 "轨道铺设期间,前": 30, "轨道铺设期间,后": 14, - "轨道铺设完成后,第1个月": 14, - "轨道铺设完成后,2至3个月": 30, - "轨道铺设完成后,4至12个月": 90, - "轨道铺设完成后,12个月以后": 180, + "轨道铺设完成后,第1个月": 14, # 重复key,保留此值 + "轨道铺设完成后,2至3个月": 30, # 重复key,保留此值 + "轨道铺设完成后,4至12个月": 90, # 重复key,保留此值 + "轨道铺设完成后,12个月以后": 180, # 重复key,保留此值 "铺路或堆载,一般情况": 1, "填筑或堆载,一般情况": 1, "铺路或堆载,沉降量突变情况": 1, - "填筑或堆载,两次填筑间隔时间较长情况": 3, + "填筑或堆载,两次填筑间隔时间较长情况":3, "铺路或堆载,两次铺路间隔时间较长情况": 3, - "堆载预压或路基填筑完成,第1至3个月": 7, + "堆载预压或路基填筑完成,第1至3个月":7, "堆载预压或路基填筑完成,第4至6个月": 14, "堆载预压或路基填筑完成,6个月以后": 30, "架桥机(运梁车) 首次通过前": 1, @@ -66,62 +71,116 @@ class OperatingModePredictor: "轨道板(道床)铺设后,第1个月": 14, "轨道板(道床)铺设后,第2至3个月": 30, "轨道板(道床)铺设后,3个月以后": 90, - "冬休": 0 # 仅保留冬休 + "架桥机(运梁车)首次通过前": 1, + "架桥机(运梁车)首次通过后,前3天": 1, + "架桥机(运梁车)首次通过后": 7, + "轨道板铺设前": 14, # 新增工况 + "轨道板(道床)铺设后,第1至3个月": 14, # 新增工况 + "轨道板(道床)铺设后,第4至6个月": 30, # 新增工况 + "轨道板(道床)铺设后,6个月以后": 90, # 新增工况 + "站场填方路基段填筑完成至静态验收": 14, # 新增工况 + "桥墩(台)地面处拆模后": 30, # 新增工况 + "敦身混凝土施工": 30, # 新增工况 + "预制梁桥预制梁架设后": 7, # 新增工况 + "现浇梁,浇筑前": 30, # 新增工况 + "现浇梁上部结构施工中": 1, # 新增工况 + "侨梁主体工程完工后,6个月以后": 30, # 新增工况(注意错别字) + "轨道铺设,前": 30, # 新增工况 + "轨道铺设,后": 14, # 新增工况 + "仰拱(底板)施工完成后第,2至3个月": 14, # 新增工况(标点位置异常) + "特殊地段隧道施工完成后至静态验收": 14, # 新增工况 + "冬休": 0 # 保留冬休工况 } def _load_condition_group(self): - """加载工况分组规则(私有方法,内部使用)""" + """加载工况分组规则(同步更新,匹配最新工况名称)""" condition_group = { + # 仰拱相关 "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", + "仰拱(底板)施工完成后第,2至3个月": "YG_DIBAN_2_3", # 新增异常格式工况分组 + + # 架桥机相关 "架桥机(运梁车) 首次通过前": "JQJ_FIRST_BEFORE", + "架桥机(运梁车)首次通过前": "JQJ_FIRST_BEFORE", # 新增无空格版本 "架桥机(运梁车) 首次通过后,前3天": "JQJ_FIRST_AFTER_3D", + "架桥机(运梁车)首次通过后,前3天": "JQJ_FIRST_AFTER_3D", # 新增无空格版本 "架桥机(运梁车) 首次通过后": "JQJ_FIRST_AFTER", + "架桥机(运梁车)首次通过后": "JQJ_FIRST_AFTER", # 新增无空格版本 + "架桥机(运梁车)通过": "STATIC", + + # 堆载预压/路基填筑相关 "堆载预压或路基填筑完成,第1至3个月": "DZYY_1_3", "堆载预压或路基填筑完成,第4至6个月": "DZYY_4_6", "堆载预压或路基填筑完成,6个月以后": "DZYY_AFTER_6", + "路基或预压土填筑,连续填筑": "STATIC", + "路基或预压土填筑,两次填筑间隔时间较长": "STATIC", + "预压土或路基填筑完成,第1~3个月": "DZYY_1_3", + "预压土或路基填筑完成,第4~6个月": "DZYY_4_6", + "预压土或路基填筑完成,6个月以后": "DZYY_AFTER_6", + + # 轨道板相关 "轨道板(道床)铺设后,第1个月": "GDB_1", "轨道板(道床)铺设后,第2至3个月": "GDB_2_3", "轨道板(道床)铺设后,3个月以后": "GDB_AFTER_3", + "轨道板铺设前": "STATIC", + "轨道板(道床)铺设后,第1至3个月": "GDB_1", + "轨道板(道床)铺设后,第4至6个月": "GDB_2_3", + "轨道板(道床)铺设后,6个月以后": "GDB_AFTER_3", + + # 预制梁桥相关 + "预制梁桥,架梁前": "STATIC", "预制梁桥,预制梁架设前": "YZLQ_BEFORE_JS", "预制梁桥,预制梁架设后": "YZLQ_AFTER_JS", + "预制梁桥预制梁架设后": "YZLQ_AFTER_JS", # 新增工况分组 + + # 桥梁主体相关 "桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3", "桥梁主体工程完工后,第4至6个月": "QL_ZHUTI_4_6", "桥梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6", + "侨梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6", # 错别字工况分组 + + # 轨道铺设相关 "轨道铺设完成后,第1个月": "GD_1", "轨道铺设完成后,2至3个月": "GD_2_3", "轨道铺设完成后,4至12个月": "GD_4_12", "轨道铺设完成后,12个月以后": "GD_AFTER_12", + "轨道铺设期间,前": "STATIC", + "轨道铺设期间,后": "STATIC", + "轨道铺设,前": "STATIC", + "轨道铺设,后": "STATIC", + + # 无砟轨道相关 "无砟轨道铺设后,第1至3个月": "WZGD_1_3", "无砟轨道铺设后,4至12个月": "WZGD_4_12", "无砟轨道铺设后,12个月以后": "WZGD_AFTER_12", + + # 静态工况(无切换规则) "墩台施工到一定高度": "STATIC", "墩台混凝土施工": "STATIC", - "预制梁桥,架梁前": "STATIC", "桥位施工桥梁,制梁前": "STATIC", "桥位施工桥梁,上部结构施工中": "STATIC", - "架桥机(运梁车)通过": "STATIC", - "轨道铺设期间,前": "STATIC", - "轨道铺设期间,后": "STATIC", "铺路或堆载,一般情况": "STATIC", "填筑或堆载,一般情况": "STATIC", "铺路或堆载,沉降量突变情况": "STATIC", "填筑或堆载,两次填筑间隔时间较长情况": "STATIC", - "铺路或堆载,两次铺路间隔时间较长情况": "STATIC" - } - # 冬休分组(仅冬休) - winter_break_groups = { + "铺路或堆载,两次铺路间隔时间较长情况": "STATIC", + "站场填方路基段填筑完成至静态验收": "STATIC", + "桥墩(台)地面处拆模后": "STATIC", + "敦身混凝土施工": "STATIC", + "现浇梁,浇筑前": "STATIC", + "现浇梁上部结构施工中": "STATIC", + "特殊地段隧道施工完成后至静态验收": "STATIC", "冬休": "STATIC" } - condition_group.update(winter_break_groups) return condition_group def _load_group_transition_rules(self): - """加载工况切换触发规则(私有方法,内部使用)""" + """加载工况切换触发规则(保持原有规则,适配新增工况)""" return { "YG_DIBAN_1": {"trigger_days": 30, "next_candidates": ["仰拱(底板)施工完成后,第2至3个月", "仰拱(底板)施工完成后,第2至3个月"]}, "YG_DIBAN_2_3": {"trigger_days": 60, "next_candidates": ["仰拱(底板)施工完成后,3个月以后", "仰拱(底板)施工完成后,3个月以后"]}, From 6ccda8f8a707fa9bd38a85d90d49ef411c888ace Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Sat, 7 Feb 2026 10:56:57 +0800 Subject: [PATCH 11/12] =?UTF-8?q?1.=E5=B7=A5=E5=86=B5=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/construction_monitor.py | 105 +++++++++++++------------ app/utils/get_operating_mode.py | 123 ++++++++---------------------- 2 files changed, 83 insertions(+), 145 deletions(-) diff --git a/app/utils/construction_monitor.py b/app/utils/construction_monitor.py index a49ab58..ea61261 100644 --- a/app/utils/construction_monitor.py +++ b/app/utils/construction_monitor.py @@ -3,31 +3,15 @@ from typing import List, Dict import warnings import copy # 注意:根据实际项目路径调整导入,若本地测试可注释掉 -# from ..core.logging_config import get_logger +from ..core.logging_config import get_logger import json - -# 本地测试时的logger替代(可根据实际情况删除) -class MockLogger: - def warning(self, msg): - print(f"[WARNING] {msg}") - def info(self, msg): - print(f"[INFO] {msg}") - -def get_logger(name): - return MockLogger() - logger = get_logger(__name__) class ConstructionMonitorUtils: def __init__(self): - # 原始工况周期映射表(保持不变,重复key会自动保留后面的) + # 原始工况周期映射表(保持不变) self.base_periods = { - "路基或预压土填筑,连续填筑":1, - "路基或预压土填筑,两次填筑间隔时间较长":7, - "预压土或路基填筑完成,第1~3个月":7, - "预压土或路基填筑完成,第4~6个月":14, "仰拱(底板)施工完成后,第1个月": 7, - "预压土或路基填筑完成,6个月以后":30, "仰拱(底板)施工完成后,第2至3个月": 14, "仰拱(底板)施工完成后,3个月以后": 30, "仰拱(底板)施工完成后,第1个月": 7, # 原:仰拱(底板)施工完成后,第1个月 @@ -66,43 +50,56 @@ class ConstructionMonitorUtils: "架桥机(运梁车) 首次通过后": 7, # 原:架桥机(运梁车)首次通过后(仅加空格) "轨道板(道床)铺设后,第1个月": 14, # 原:轨道板(道床)铺设后,第1个月 "轨道板(道床)铺设后,第2至3个月": 30, # 原:轨道板(道床)铺设后,第2至3个月 - "轨道板(道床)铺设后,3个月以后": 90, - "架桥机(运梁车)首次通过前": 1, - "架桥机(运梁车)首次通过后,前3天": 1, - "架桥机(运梁车)首次通过后": 7, - "轨道板铺设前": 14, - "轨道板(道床)铺设后,第1至3个月": 14, - "轨道板(道床)铺设后,第4至6个月": 30, - "轨道板(道床)铺设后,6个月以后": 90, - "站场填方路基段填筑完成至静态验收": 14, - "桥墩(台)地面处拆模后": 30, - "敦身混凝土施工": 30, - "预制梁桥,架梁前": 30, - "预制梁桥,预制梁架设前": 1, - "预制梁桥预制梁架设后": 7, - "现浇梁,浇筑前": 30, - "现浇梁上部结构施工中": 1, - "架桥机(运梁车)通过": 2, - "桥梁主体工程完工后,第1至3个月": 7, - "桥梁主体工程完工后,第4至6个月": 14, - "侨梁主体工程完工后,6个月以后": 30, - "轨道铺设,前": 30, - "轨道铺设,后": 14, - "轨道铺设完成后,第1个月": 14, - "轨道铺设完成后,2至3个月": 30, - "轨道铺设完成后,4至12个月": 90, - "轨道铺设完成后,12个月以后": 180, - "仰拱(底板)施工完成后,第1个月": 7, - "仰拱(底板)施工完成后第,2至3个月": 14, - "仰拱(底板)施工完成后,3个月以后": 30, - "轨道板铺设前": 14, - "无砟轨道铺设后,第1至3个月": 30, - "无砟轨道铺设后,4至12个月": 90, - "无砟轨道铺设后,12个月以后": 180, - "特殊地段隧道施工完成后至静态验收": 14 + "轨道板(道床)铺设后,3个月以后": 90 # 未出现在待处理集,保留原始格式 } - # 移除兼容映射表,直接使用原始base_periods(字典特性:重复key自动保留后面的) - self.compatible_periods = self.base_periods + # 构建中英文括号+逗号兼容映射表 + self.compatible_periods = self._build_compatible_brackets_map() + + + def _build_compatible_brackets_map(self) -> Dict[str, int]: + """构建支持中英文括号、中英文逗号,且忽略所有空格的兼容映射表""" + compatible_map = {} + for original_key, period in self.base_periods.items(): + # ========== 第一步:处理原始key的空格和符号,生成基础变体 ========== + # 1. 清洗空格:去除首尾+全角空格+合并连续空格+最终删除所有空格 + key_no_space = original_key.strip().replace(" ", " ").replace(" ", " ").replace(" ", "") + # 2. 原始key(未清洗空格) + compatible_map[original_key] = period + # 3. 无空格的原始符号key + if key_no_space not in compatible_map: + compatible_map[key_no_space] = period + + # ========== 第二步:生成中文括号变体(含空格/无空格) ========== + # 带空格的中文括号key + chinese_bracket_key = original_key.replace("(", "(").replace(")", ")") + if chinese_bracket_key != original_key and chinese_bracket_key not in compatible_map: + compatible_map[chinese_bracket_key] = period + # 无空格的中文括号key + chinese_bracket_no_space = key_no_space.replace("(", "(").replace(")", ")") + if chinese_bracket_no_space not in compatible_map: + compatible_map[chinese_bracket_no_space] = period + + # ========== 第三步:生成中文逗号变体(含空格/无空格) ========== + # 带空格的中文逗号key + chinese_comma_key = original_key.replace(",", ",") + if chinese_comma_key != original_key and chinese_comma_key not in compatible_map: + compatible_map[chinese_comma_key] = period + # 无空格的中文逗号key + chinese_comma_no_space = key_no_space.replace(",", ",") + if chinese_comma_no_space not in compatible_map: + compatible_map[chinese_comma_no_space] = period + + # ========== 第四步:生成中文括号+逗号混合变体(含空格/无空格) ========== + # 带空格的混合变体key + mixed_key = chinese_bracket_key.replace(",", ",") + if mixed_key != original_key and mixed_key not in compatible_map: + compatible_map[mixed_key] = period + # 无空格的混合变体key + mixed_no_space = chinese_bracket_no_space.replace(",", ",") + if mixed_no_space not in compatible_map: + compatible_map[mixed_no_space] = period + + return compatible_map def get_due_data(self, input_data: List[List[Dict]], start: int = 0, end: int = 0, current_date: datetime = None) -> Dict[str, List[Dict]]: result = {"winter": [], "data": [], "error_data": []} diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index 23d48cc..98ac99e 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -14,9 +14,9 @@ class OperatingModePredictor: def __init__(self): """初始化类,加载内置的工况配置、分组规则和切换触发规则""" - # 基础工况配置(更新为最新的工况列表,重复key保留后面的值) + # 基础工况配置(键:工况名称,值:基础监测周期参考) self.base_periods = self._load_base_periods() - # 工况分组(同步更新分组规则,匹配最新工况名称) + # 工况分组(将同义不同格式的工况归类,共用切换规则) self.condition_group = self._load_condition_group() # 工况切换触发规则(键:分组ID,值:触发天数+目标工况候选) self.group_transition_rules = self._load_group_transition_rules() @@ -24,45 +24,40 @@ class OperatingModePredictor: self.winter_break_labels = self._load_winter_break_labels() def _load_base_periods(self): - """加载基础工况配置(更新为最新工况列表,重复key自动保留后面的值)""" + """加载基础工况配置(私有方法,内部使用)""" return { - "路基或预压土填筑,连续填筑":1, - "路基或预压土填筑,两次填筑间隔时间较长":7, - "预压土或路基填筑完成,第1~3个月":7, - "预压土或路基填筑完成,第4~6个月":14, "仰拱(底板)施工完成后,第1个月": 7, - "预压土或路基填筑完成,6个月以后":30, "仰拱(底板)施工完成后,第2至3个月": 14, "仰拱(底板)施工完成后,3个月以后": 30, - "仰拱(底板)施工完成后,第1个月": 7, # 重复key,保留此值 - "仰拱(底板)施工完成后,第2至3个月": 14, # 重复key,保留此值 - "仰拱(底板)施工完成后,3个月以后": 30, # 重复key,保留此值 - "无砟轨道铺设后,第1至3个月": 30, # 重复key,保留此值 - "无砟轨道铺设后,4至12个月": 90, # 重复key,保留此值 - "无砟轨道铺设后,12个月以后": 180, # 重复key,保留此值 + "仰拱(底板)施工完成后,第1个月": 7, + "仰拱(底板)施工完成后,第2至3个月": 14, + "仰拱(底板)施工完成后,3个月以后": 30, + "无砟轨道铺设后,第1至3个月": 30, + "无砟轨道铺设后,4至12个月": 90, + "无砟轨道铺设后,12个月以后": 180, "墩台施工到一定高度": 30, "墩台混凝土施工": 30, - "预制梁桥,架梁前": 30, # 重复key,保留此值 - "预制梁桥,预制梁架设前": 1, # 重复key,保留此值 + "预制梁桥,架梁前": 30, + "预制梁桥,预制梁架设前": 1, "预制梁桥,预制梁架设后": 7, "桥位施工桥梁,制梁前": 30, "桥位施工桥梁,上部结构施工中": 1, - "架桥机(运梁车)通过": 2, # 重复key,保留此值(原7→2) + "架桥机(运梁车)通过": 7, "桥梁主体工程完工后,第1至3个月": 7, - "桥梁主体工程完工后,第4至6个月": 14, # 重复key,保留此值 - "桥梁主体工程完工后,6个月以后": 30, # 重复key,保留此值 + "桥梁主体工程完工后,第4至6个月": 14, + "桥梁主体工程完工后,6个月以后": 30, "轨道铺设期间,前": 30, "轨道铺设期间,后": 14, - "轨道铺设完成后,第1个月": 14, # 重复key,保留此值 - "轨道铺设完成后,2至3个月": 30, # 重复key,保留此值 - "轨道铺设完成后,4至12个月": 90, # 重复key,保留此值 - "轨道铺设完成后,12个月以后": 180, # 重复key,保留此值 + "轨道铺设完成后,第1个月": 14, + "轨道铺设完成后,2至3个月": 30, + "轨道铺设完成后,4至12个月": 90, + "轨道铺设完成后,12个月以后": 180, "铺路或堆载,一般情况": 1, "填筑或堆载,一般情况": 1, "铺路或堆载,沉降量突变情况": 1, - "填筑或堆载,两次填筑间隔时间较长情况":3, + "填筑或堆载,两次填筑间隔时间较长情况": 3, "铺路或堆载,两次铺路间隔时间较长情况": 3, - "堆载预压或路基填筑完成,第1至3个月":7, + "堆载预压或路基填筑完成,第1至3个月": 7, "堆载预压或路基填筑完成,第4至6个月": 14, "堆载预压或路基填筑完成,6个月以后": 30, "架桥机(运梁车) 首次通过前": 1, @@ -71,116 +66,62 @@ class OperatingModePredictor: "轨道板(道床)铺设后,第1个月": 14, "轨道板(道床)铺设后,第2至3个月": 30, "轨道板(道床)铺设后,3个月以后": 90, - "架桥机(运梁车)首次通过前": 1, - "架桥机(运梁车)首次通过后,前3天": 1, - "架桥机(运梁车)首次通过后": 7, - "轨道板铺设前": 14, # 新增工况 - "轨道板(道床)铺设后,第1至3个月": 14, # 新增工况 - "轨道板(道床)铺设后,第4至6个月": 30, # 新增工况 - "轨道板(道床)铺设后,6个月以后": 90, # 新增工况 - "站场填方路基段填筑完成至静态验收": 14, # 新增工况 - "桥墩(台)地面处拆模后": 30, # 新增工况 - "敦身混凝土施工": 30, # 新增工况 - "预制梁桥预制梁架设后": 7, # 新增工况 - "现浇梁,浇筑前": 30, # 新增工况 - "现浇梁上部结构施工中": 1, # 新增工况 - "侨梁主体工程完工后,6个月以后": 30, # 新增工况(注意错别字) - "轨道铺设,前": 30, # 新增工况 - "轨道铺设,后": 14, # 新增工况 - "仰拱(底板)施工完成后第,2至3个月": 14, # 新增工况(标点位置异常) - "特殊地段隧道施工完成后至静态验收": 14, # 新增工况 - "冬休": 0 # 保留冬休工况 + "冬休": 0 # 仅保留冬休 } def _load_condition_group(self): - """加载工况分组规则(同步更新,匹配最新工况名称)""" + """加载工况分组规则(私有方法,内部使用)""" condition_group = { - # 仰拱相关 "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", - "仰拱(底板)施工完成后第,2至3个月": "YG_DIBAN_2_3", # 新增异常格式工况分组 - - # 架桥机相关 "架桥机(运梁车) 首次通过前": "JQJ_FIRST_BEFORE", - "架桥机(运梁车)首次通过前": "JQJ_FIRST_BEFORE", # 新增无空格版本 "架桥机(运梁车) 首次通过后,前3天": "JQJ_FIRST_AFTER_3D", - "架桥机(运梁车)首次通过后,前3天": "JQJ_FIRST_AFTER_3D", # 新增无空格版本 "架桥机(运梁车) 首次通过后": "JQJ_FIRST_AFTER", - "架桥机(运梁车)首次通过后": "JQJ_FIRST_AFTER", # 新增无空格版本 - "架桥机(运梁车)通过": "STATIC", - - # 堆载预压/路基填筑相关 "堆载预压或路基填筑完成,第1至3个月": "DZYY_1_3", "堆载预压或路基填筑完成,第4至6个月": "DZYY_4_6", "堆载预压或路基填筑完成,6个月以后": "DZYY_AFTER_6", - "路基或预压土填筑,连续填筑": "STATIC", - "路基或预压土填筑,两次填筑间隔时间较长": "STATIC", - "预压土或路基填筑完成,第1~3个月": "DZYY_1_3", - "预压土或路基填筑完成,第4~6个月": "DZYY_4_6", - "预压土或路基填筑完成,6个月以后": "DZYY_AFTER_6", - - # 轨道板相关 "轨道板(道床)铺设后,第1个月": "GDB_1", "轨道板(道床)铺设后,第2至3个月": "GDB_2_3", "轨道板(道床)铺设后,3个月以后": "GDB_AFTER_3", - "轨道板铺设前": "STATIC", - "轨道板(道床)铺设后,第1至3个月": "GDB_1", - "轨道板(道床)铺设后,第4至6个月": "GDB_2_3", - "轨道板(道床)铺设后,6个月以后": "GDB_AFTER_3", - - # 预制梁桥相关 - "预制梁桥,架梁前": "STATIC", "预制梁桥,预制梁架设前": "YZLQ_BEFORE_JS", "预制梁桥,预制梁架设后": "YZLQ_AFTER_JS", - "预制梁桥预制梁架设后": "YZLQ_AFTER_JS", # 新增工况分组 - - # 桥梁主体相关 "桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3", "桥梁主体工程完工后,第4至6个月": "QL_ZHUTI_4_6", "桥梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6", - "侨梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6", # 错别字工况分组 - - # 轨道铺设相关 "轨道铺设完成后,第1个月": "GD_1", "轨道铺设完成后,2至3个月": "GD_2_3", "轨道铺设完成后,4至12个月": "GD_4_12", "轨道铺设完成后,12个月以后": "GD_AFTER_12", - "轨道铺设期间,前": "STATIC", - "轨道铺设期间,后": "STATIC", - "轨道铺设,前": "STATIC", - "轨道铺设,后": "STATIC", - - # 无砟轨道相关 "无砟轨道铺设后,第1至3个月": "WZGD_1_3", "无砟轨道铺设后,4至12个月": "WZGD_4_12", "无砟轨道铺设后,12个月以后": "WZGD_AFTER_12", - - # 静态工况(无切换规则) "墩台施工到一定高度": "STATIC", "墩台混凝土施工": "STATIC", + "预制梁桥,架梁前": "STATIC", "桥位施工桥梁,制梁前": "STATIC", "桥位施工桥梁,上部结构施工中": "STATIC", + "架桥机(运梁车)通过": "STATIC", + "轨道铺设期间,前": "STATIC", + "轨道铺设期间,后": "STATIC", "铺路或堆载,一般情况": "STATIC", "填筑或堆载,一般情况": "STATIC", "铺路或堆载,沉降量突变情况": "STATIC", "填筑或堆载,两次填筑间隔时间较长情况": "STATIC", - "铺路或堆载,两次铺路间隔时间较长情况": "STATIC", - "站场填方路基段填筑完成至静态验收": "STATIC", - "桥墩(台)地面处拆模后": "STATIC", - "敦身混凝土施工": "STATIC", - "现浇梁,浇筑前": "STATIC", - "现浇梁上部结构施工中": "STATIC", - "特殊地段隧道施工完成后至静态验收": "STATIC", + "铺路或堆载,两次铺路间隔时间较长情况": "STATIC" + } + # 冬休分组(仅冬休) + winter_break_groups = { "冬休": "STATIC" } + condition_group.update(winter_break_groups) return condition_group def _load_group_transition_rules(self): - """加载工况切换触发规则(保持原有规则,适配新增工况)""" + """加载工况切换触发规则(私有方法,内部使用)""" return { "YG_DIBAN_1": {"trigger_days": 30, "next_candidates": ["仰拱(底板)施工完成后,第2至3个月", "仰拱(底板)施工完成后,第2至3个月"]}, "YG_DIBAN_2_3": {"trigger_days": 60, "next_candidates": ["仰拱(底板)施工完成后,3个月以后", "仰拱(底板)施工完成后,3个月以后"]}, From c26bd4e8e17b7d4e13190edf81ae1aecfff2f8fc Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Tue, 10 Feb 2026 18:08:11 +0800 Subject: [PATCH 12/12] =?UTF-8?q?1.=E6=96=B0=E5=B7=A5=E5=86=B5=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/construction_monitor.py | 42 ++- app/utils/get_operating_mode.py | 441 +++++++++++++++++------------- 2 files changed, 293 insertions(+), 190 deletions(-) diff --git a/app/utils/construction_monitor.py b/app/utils/construction_monitor.py index ea61261..da1edb1 100644 --- a/app/utils/construction_monitor.py +++ b/app/utils/construction_monitor.py @@ -11,7 +11,12 @@ class ConstructionMonitorUtils: def __init__(self): # 原始工况周期映射表(保持不变) self.base_periods = { + "路基或预压土填筑,连续填筑":1, + "路基或预压土填筑,两次填筑间隔时间较长":7, + "预压土或路基填筑完成,第1~3个月":7, + "预压土或路基填筑完成,第4~6个月":14, "仰拱(底板)施工完成后,第1个月": 7, + "预压土或路基填筑完成,6个月以后":30, "仰拱(底板)施工完成后,第2至3个月": 14, "仰拱(底板)施工完成后,3个月以后": 30, "仰拱(底板)施工完成后,第1个月": 7, # 原:仰拱(底板)施工完成后,第1个月 @@ -27,7 +32,7 @@ class ConstructionMonitorUtils: "预制梁桥,预制梁架设后": 7, # 原:预制梁桥,预制梁架设后 "桥位施工桥梁,制梁前": 30, # 原:桥位施工桥梁,制梁前 "桥位施工桥梁,上部结构施工中": 1, # 原:桥位施工桥梁,上部结构施工中 - "架桥机(运梁车)通过": 7, # 无格式差异,保留原样 + # "架桥机(运梁车)通过": 7, # 无格式差异,保留原样 "桥梁主体工程完工后,第1至3个月": 7, # 原:桥梁主体工程完工后,第1至3个月 "桥梁主体工程完工后,第4至6个月": 14, # 原:桥梁主体工程完工后,第4至6个月 "桥梁主体工程完工后,6个月以后": 30, # 原:桥梁主体工程完工后,6个月以后 '' @@ -50,7 +55,40 @@ class ConstructionMonitorUtils: "架桥机(运梁车) 首次通过后": 7, # 原:架桥机(运梁车)首次通过后(仅加空格) "轨道板(道床)铺设后,第1个月": 14, # 原:轨道板(道床)铺设后,第1个月 "轨道板(道床)铺设后,第2至3个月": 30, # 原:轨道板(道床)铺设后,第2至3个月 - "轨道板(道床)铺设后,3个月以后": 90 # 未出现在待处理集,保留原始格式 + "轨道板(道床)铺设后,3个月以后": 90, + "架桥机(运梁车)首次通过前": 1, + "架桥机(运梁车)首次通过后,前3天": 1, + "架桥机(运梁车)首次通过后": 7, + "轨道板铺设前": 14, + "轨道板(道床)铺设后,第1至3个月": 14, + "轨道板(道床)铺设后,第4至6个月": 30, + "轨道板(道床)铺设后,6个月以后": 90, + "站场填方路基段填筑完成至静态验收": 14, + "桥墩(台)地面处拆模后": 30, + "敦身混凝土施工": 30, + # "预制梁桥,架梁前": 30, + # "预制梁桥,预制梁架设前": 1, + "预制梁桥预制梁架设后": 7, + "现浇梁,浇筑前": 30, + "现浇梁上部结构施工中": 1, + "架桥机(运梁车)通过": 2, + "桥梁主体工程完工后,第1至3个月": 7, + # "桥梁主体工程完工后,第4至6个月": 14, + "侨梁主体工程完工后,6个月以后": 30, + "轨道铺设,前": 30, + "轨道铺设,后": 14, + # "轨道铺设完成后,第1个月": 14, + # "轨道铺设完成后,2至3个月": 30, + # "轨道铺设完成后,4至12个月": 90, + # "轨道铺设完成后,12个月以后": 180, + # "仰拱(底板)施工完成后,第1个月": 7, + # "仰拱(底板)施工完成后,第2至3个月": 14, + # "仰拱(底板)施工完成后,3个月以后": 30, + # "轨道板铺设前": 14, + # "无砟轨道铺设后,第1至3个月": 30, + # "无砟轨道铺设后,4至12个月": 90, + # "无砟轨道铺设后,12个月以后": 180, + "特殊地段隧道施工完成后至静态验收": 14 } # 构建中英文括号+逗号兼容映射表 self.compatible_periods = self._build_compatible_brackets_map() diff --git a/app/utils/get_operating_mode.py b/app/utils/get_operating_mode.py index 98ac99e..3224b96 100644 --- a/app/utils/get_operating_mode.py +++ b/app/utils/get_operating_mode.py @@ -7,159 +7,246 @@ class OperatingModePredictor: 特性: 1. 输入为二维列表,每个内嵌列表对应一个point_id,且为倒序排列(最新记录在索引0) 2. 输出为一维列表,仅保留每个内嵌列表的最新记录,新增工况推导结果字段 - 3. 无需切换工况时,next_workinfo返回当前工况名称;需要切换时返回目标工况名称 + 3. 推导规则:有等效映射返回新工况,无等效保留旧工况;切换下一工况以base_periods为准 4. 时间计算仅按日期(天)维度,忽略时分秒 - 5. 支持冬休场景:当前工况为冬休时,以冬休前上一个有效工况作为切换判断依据 + 5. 冬休场景:仅以冬休前上一有效工况为判断基准,切换规则与非冬休完全一致 + 6. 适配中英文括号、逗号、空格,内部标准化匹配,外部返回规范名称 """ def __init__(self): - """初始化类,加载内置的工况配置、分组规则和切换触发规则""" - # 基础工况配置(键:工况名称,值:基础监测周期参考) + """初始化类,加载核心配置""" + # 1. 基础工况配置(最终返回的规范名称,含所有新旧工况) self.base_periods = self._load_base_periods() - # 工况分组(将同义不同格式的工况归类,共用切换规则) + # 2. 旧→新等效映射(优先返回新工况) + self.old_to_new_map = self._load_old_to_new_map() + # 3. 工况分组(同义工况归为同一分组,复用切换规则) self.condition_group = self._load_condition_group() - # 工况切换触发规则(键:分组ID,值:触发天数+目标工况候选) - self.group_transition_rules = self._load_group_transition_rules() - # 冬休工况标识(仅保留“冬休”) - self.winter_break_labels = self._load_winter_break_labels() + # 4. 切换触发规则(沿用原逻辑,触发天数+目标工况) + self.transition_rules = self._load_transition_rules() + # 5. 冬休标识 + self.winter_break_labels = {"冬休"} + + # 辅助映射:标准化名称→base_periods中的规范名称(用于最终返回) + self.std_to_canonical = { + self._standardize_name(name): name for name in self.base_periods.keys() + } + # 辅助映射:标准化名称→分组ID + self.std_to_group = { + self._standardize_name(name): group_id + for name, group_id in self.condition_group.items() + } + + def _standardize_name(self, name): + """ + 标准化工况名称(内部匹配用):去空格、统一中英文符号 + :param name: 原始工况名称 + :return: 标准化后的名称 + """ + if not name: + return "" + # 去所有空格 + std_name = name.replace(" ", "").strip() + # 统一中英文符号 + replace_map = { + "(": "(", ")": ")", ",": ",", "。": ",", "~": "至", + ";": ";", ":": ":", "'": "'", """: "\"" + } + for old, new in replace_map.items(): + std_name = std_name.replace(old, new) + return std_name def _load_base_periods(self): - """加载基础工况配置(私有方法,内部使用)""" + """加载基础工况配置(最终返回的规范名称,无多余数字)""" return { - "仰拱(底板)施工完成后,第1个月": 7, - "仰拱(底板)施工完成后,第2至3个月": 14, - "仰拱(底板)施工完成后,3个月以后": 30, + # 路基工况(新工况优先) + "路基或预压土填筑,连续填筑": 1, + "路基或预压土填筑,两次填筑间隔时间较长": 7, + "预压土或路基填筑完成,第1~3个月": 7, + "预压土或路基填筑完成,第4~6个月": 14, + "预压土或路基填筑完成,6个月以后": 30, + "架桥机(运梁车)首次通过前": 1, + "架桥机(运梁车)首次通过后,前3天": 1, + "架桥机(运梁车)首次通过后": 7, + "轨道板(道床)铺设后,第1至3个月": 14, + "轨道板(道床)铺设后,第4至6个月": 30, + "轨道板(道床)铺设后,6个月以后": 90, + + # 路基旧工况(保留,无等效则返回) + "填筑或堆载,一般情况": 1, + "填筑或堆载,两次填筑间隔时间较长情况": 7, + "堆载预压或路基填筑完成,6个月以后": 30, + "轨道板(道床)铺设后,第1个月": 14, + "轨道板(道床)铺设后,第2至3个月": 30, + "轨道板(道床)铺设后,3个月以后": 90, + + # 桥梁工况(新工况优先) + "桥墩(台)地面处拆模后": 30, + "敦身混凝土施工": 30, + "预制梁桥,预制梁架设前": 1, + "预制梁桥,预制梁架设后": 7, + "现浇梁,浇筑前": 30, + "现浇梁上部结构施工中": 1, + "架桥机(运梁车)通过": 2, + "桥梁主体工程完工后,第1至3个月": 7, + "桥梁主体工程完工后,第4至6个月": 14, + "桥梁主体工程完工后,6个月以后": 30, + "轨道铺设,前": 30, + "轨道铺设,后": 14, + "轨道铺设完成后,第1个月": 14, + "轨道铺设完成后,2至3个月": 30, + "轨道铺设完成后,4至12个月": 90, + "轨道铺设完成后,12个月以后": 180, + + # 桥梁旧工况(保留,无等效则返回) + "墩台施工到一定高度": 30, + "墩台混凝土施工": 30, + "预制梁桥,架梁前": 30, + "桥位施工桥梁,制梁前": 30, + "桥位施工桥梁,上部结构施工中": 1, + "桥梁主体工程完工后,第1至3个月": 7, + "轨道铺设期间,前": 30, + "轨道铺设期间,后": 14, + + # 隧道工况 "仰拱(底板)施工完成后,第1个月": 7, "仰拱(底板)施工完成后,第2至3个月": 14, "仰拱(底板)施工完成后,3个月以后": 30, "无砟轨道铺设后,第1至3个月": 30, "无砟轨道铺设后,4至12个月": 90, "无砟轨道铺设后,12个月以后": 180, - "墩台施工到一定高度": 30, - "墩台混凝土施工": 30, - "预制梁桥,架梁前": 30, - "预制梁桥,预制梁架设前": 1, - "预制梁桥,预制梁架设后": 7, - "桥位施工桥梁,制梁前": 30, - "桥位施工桥梁,上部结构施工中": 1, - "架桥机(运梁车)通过": 7, - "桥梁主体工程完工后,第1至3个月": 7, - "桥梁主体工程完工后,第4至6个月": 14, - "桥梁主体工程完工后,6个月以后": 30, - "轨道铺设期间,前": 30, - "轨道铺设期间,后": 14, - "轨道铺设完成后,第1个月": 14, - "轨道铺设完成后,2至3个月": 30, - "轨道铺设完成后,4至12个月": 90, - "轨道铺设完成后,12个月以后": 180, - "铺路或堆载,一般情况": 1, - "填筑或堆载,一般情况": 1, - "铺路或堆载,沉降量突变情况": 1, - "填筑或堆载,两次填筑间隔时间较长情况": 3, - "铺路或堆载,两次铺路间隔时间较长情况": 3, - "堆载预压或路基填筑完成,第1至3个月": 7, - "堆载预压或路基填筑完成,第4至6个月": 14, - "堆载预压或路基填筑完成,6个月以后": 30, - "架桥机(运梁车) 首次通过前": 1, - "架桥机(运梁车) 首次通过后,前3天": 1, - "架桥机(运梁车) 首次通过后": 7, - "轨道板(道床)铺设后,第1个月": 14, - "轨道板(道床)铺设后,第2至3个月": 30, - "轨道板(道床)铺设后,3个月以后": 90, - "冬休": 0 # 仅保留冬休 + + # 特殊工况 + "冬休": 0 + } + + def _load_old_to_new_map(self): + """加载旧→新等效映射(优先返回新工况)""" + return { + # 路基等效 + "填筑或堆载,一般情况": "路基或预压土填筑,连续填筑", + "填筑或堆载,两次填筑间隔时间较长情况": "路基或预压土填筑,两次填筑间隔时间较长", + "预压土或路基填筑完成。第1~3个月": "预压土或路基填筑完成,第1~3个月", + "堆载预压或路基填筑完成,6个月以后": "预压土或路基填筑完成,6个月以后", + "轨道板(道床)铺设后,第1个月": "轨道板(道床)铺设后,第1至3个月", + "轨道板(道床)铺设后,第2至3个月": "轨道板(道床)铺设后,第4至6个月", + "轨道板(道床)铺设后,3个月以后": "轨道板(道床)铺设后,6个月以后", + + # 桥梁等效 + "墩台施工到一定高度": "桥墩(台)地面处拆模后", + "墩台混凝土施工": "敦身混凝土施工", + "桥位施工桥梁,制梁前": "现浇梁,浇筑前", + "桥位施工桥梁,上部结构施工中": "现浇梁上部结构施工中", + "桥梁主体工程完工后,第1至3个月": "桥梁主体工程完工后,第1至3个月", + "轨道铺设期间,前": "轨道铺设,前", + "轨道铺设期间,后": "轨道铺设,后" } def _load_condition_group(self): - """加载工况分组规则(私有方法,内部使用)""" - condition_group = { - "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", - "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", - "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", - "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", - "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", - "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", - "架桥机(运梁车) 首次通过前": "JQJ_FIRST_BEFORE", - "架桥机(运梁车) 首次通过后,前3天": "JQJ_FIRST_AFTER_3D", - "架桥机(运梁车) 首次通过后": "JQJ_FIRST_AFTER", - "堆载预压或路基填筑完成,第1至3个月": "DZYY_1_3", - "堆载预压或路基填筑完成,第4至6个月": "DZYY_4_6", - "堆载预压或路基填筑完成,6个月以后": "DZYY_AFTER_6", - "轨道板(道床)铺设后,第1个月": "GDB_1", - "轨道板(道床)铺设后,第2至3个月": "GDB_2_3", - "轨道板(道床)铺设后,3个月以后": "GDB_AFTER_3", + """加载工况分组(同义工况归为同一分组)""" + group_map = { + # 路基分组 + "路基或预压土填筑,连续填筑": "DZ_CONTINUE", + "路基或预压土填筑,两次填筑间隔时间较长": "DZ_INTERVAL", + "预压土或路基填筑完成,第1~3个月": "DZ_FINISH_1_3", + "预压土或路基填筑完成,第4~6个月": "DZ_FINISH_4_6", + "预压土或路基填筑完成,6个月以后": "DZ_FINISH_AFTER_6", + "架桥机(运梁车)首次通过前": "JQJ_FIRST_BEFORE", + "架桥机(运梁车)首次通过后,前3天": "JQJ_FIRST_AFTER_3D", + "架桥机(运梁车)首次通过后": "JQJ_FIRST_AFTER", + "轨道板(道床)铺设后,第1至3个月": "GDB_FINISH_1_3", + "轨道板(道床)铺设后,第4至6个月": "GDB_FINISH_4_6", + "轨道板(道床)铺设后,6个月以后": "GDB_FINISH_AFTER_6", + + # 路基旧工况分组(复用新工况分组) + "填筑或堆载,一般情况": "DZ_CONTINUE", + "填筑或堆载,两次填筑间隔时间较长情况": "DZ_INTERVAL", + "堆载预压或路基填筑完成,6个月以后": "DZ_FINISH_AFTER_6", + "轨道板(道床)铺设后,第1个月": "GDB_FINISH_1_3", + "轨道板(道床)铺设后,第2至3个月": "GDB_FINISH_4_6", + "轨道板(道床)铺设后,3个月以后": "GDB_FINISH_AFTER_6", + + # 桥梁分组 + "桥墩(台)地面处拆模后": "STATIC", + "敦身混凝土施工": "STATIC", + "预制梁桥,架梁前": "STATIC", "预制梁桥,预制梁架设前": "YZLQ_BEFORE_JS", "预制梁桥,预制梁架设后": "YZLQ_AFTER_JS", - "桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3", + "现浇梁,浇筑前": "STATIC", + "现浇梁上部结构施工中": "STATIC", + "架桥机(运梁车)通过": "STATIC", + "桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3", "桥梁主体工程完工后,第4至6个月": "QL_ZHUTI_4_6", - "桥梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6", - "轨道铺设完成后,第1个月": "GD_1", - "轨道铺设完成后,2至3个月": "GD_2_3", - "轨道铺设完成后,4至12个月": "GD_4_12", - "轨道铺设完成后,12个月以后": "GD_AFTER_12", + "桥梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6", + "轨道铺设,前": "STATIC", + "轨道铺设,后": "STATIC", + "轨道铺设完成后,第1个月": "GD_FINISH_1", + "轨道铺设完成后,2至3个月": "GD_FINISH_2_3", + "轨道铺设完成后,4至12个月": "GD_FINISH_4_12", + "轨道铺设完成后,12个月以后": "GD_FINISH_AFTER_12", + + # 桥梁旧工况分组(复用新工况分组) + "墩台施工到一定高度": "STATIC", + "墩台混凝土施工": "STATIC", + "桥位施工桥梁,制梁前": "STATIC", + "桥位施工桥梁,上部结构施工中": "STATIC", + "桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3", + "轨道铺设期间,前": "STATIC", + "轨道铺设期间,后": "STATIC", + + # 隧道分组 + "仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1", + "仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3", + "仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3", "无砟轨道铺设后,第1至3个月": "WZGD_1_3", "无砟轨道铺设后,4至12个月": "WZGD_4_12", "无砟轨道铺设后,12个月以后": "WZGD_AFTER_12", - "墩台施工到一定高度": "STATIC", - "墩台混凝土施工": "STATIC", - "预制梁桥,架梁前": "STATIC", - "桥位施工桥梁,制梁前": "STATIC", - "桥位施工桥梁,上部结构施工中": "STATIC", - "架桥机(运梁车)通过": "STATIC", - "轨道铺设期间,前": "STATIC", - "轨道铺设期间,后": "STATIC", - "铺路或堆载,一般情况": "STATIC", - "填筑或堆载,一般情况": "STATIC", - "铺路或堆载,沉降量突变情况": "STATIC", - "填筑或堆载,两次填筑间隔时间较长情况": "STATIC", - "铺路或堆载,两次铺路间隔时间较长情况": "STATIC" - } - # 冬休分组(仅冬休) - winter_break_groups = { + + # 特殊工况 "冬休": "STATIC" } - condition_group.update(winter_break_groups) - return condition_group + return group_map - def _load_group_transition_rules(self): - """加载工况切换触发规则(私有方法,内部使用)""" + def _load_transition_rules(self): + """加载切换触发规则(沿用原逻辑,以base_periods为准)""" return { - "YG_DIBAN_1": {"trigger_days": 30, "next_candidates": ["仰拱(底板)施工完成后,第2至3个月", "仰拱(底板)施工完成后,第2至3个月"]}, - "YG_DIBAN_2_3": {"trigger_days": 60, "next_candidates": ["仰拱(底板)施工完成后,3个月以后", "仰拱(底板)施工完成后,3个月以后"]}, - "YG_DIBAN_AFTER_3": {"trigger_days": None, "next_candidates": None}, - "JQJ_FIRST_BEFORE": {"trigger_days": 1, "next_candidates": ["架桥机(运梁车) 首次通过后,前3天"]}, - "JQJ_FIRST_AFTER_3D": {"trigger_days": 3, "next_candidates": ["架桥机(运梁车) 首次通过后"]}, - "JQJ_FIRST_AFTER": {"trigger_days": None, "next_candidates": None}, - "DZYY_1_3": {"trigger_days": 90, "next_candidates": ["堆载预压或路基填筑完成,第4至6个月"]}, - "DZYY_4_6": {"trigger_days": 90, "next_candidates": ["堆载预压或路基填筑完成,6个月以后"]}, - "DZYY_AFTER_6": {"trigger_days": None, "next_candidates": None}, - "GDB_1": {"trigger_days": 30, "next_candidates": ["轨道板(道床)铺设后,第2至3个月"]}, - "GDB_2_3": {"trigger_days": 30, "next_candidates": ["轨道板(道床)铺设后,3个月以后"]}, - "GDB_AFTER_3": {"trigger_days": None, "next_candidates": None}, - "YZLQ_BEFORE_JS": {"trigger_days": 1, "next_candidates": ["架桥机(运梁车)通过"]}, - "YZLQ_AFTER_JS": {"trigger_days": 7, "next_candidates": ["桥梁主体工程完工后,第1至3个月"]}, - "QL_ZHUTI_1_3": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,第4至6个月"]}, - "QL_ZHUTI_4_6": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后,6个月以后"]}, - "QL_ZHUTI_AFTER_6": {"trigger_days": None, "next_candidates": None}, - "GD_1": {"trigger_days": 30, "next_candidates": ["轨道铺设完成后,2至3个月"]}, - "GD_2_3": {"trigger_days": 60, "next_candidates": ["轨道铺设完成后,4至12个月"]}, - "GD_4_12": {"trigger_days": 240, "next_candidates": ["轨道铺设完成后,12个月以后"]}, - "GD_AFTER_12": {"trigger_days": None, "next_candidates": None}, - "WZGD_1_3": {"trigger_days": 90, "next_candidates": ["无砟轨道铺设后,4至12个月"]}, - "WZGD_4_12": {"trigger_days": 240, "next_candidates": ["无砟轨道铺设后,12个月以后"]}, - "WZGD_AFTER_12": {"trigger_days": None, "next_candidates": None}, - "STATIC": {"trigger_days": None, "next_candidates": None} + # 路基切换规则 + "DZ_FINISH_1_3": {"trigger_days": 90, "next": ["预压土或路基填筑完成,第4~6个月"]}, + "DZ_FINISH_4_6": {"trigger_days": 90, "next": ["预压土或路基填筑完成,6个月以后"]}, + "DZ_FINISH_AFTER_6": {"trigger_days": None, "next": None}, + "JQJ_FIRST_BEFORE": {"trigger_days": 1, "next": ["架桥机(运梁车)首次通过后,前3天"]}, + "JQJ_FIRST_AFTER_3D": {"trigger_days": 3, "next": ["架桥机(运梁车)首次通过后"]}, + "JQJ_FIRST_AFTER": {"trigger_days": None, "next": None}, + "GDB_FINISH_1_3": {"trigger_days": 30, "next": ["轨道板(道床)铺设后,第4至6个月"]}, + "GDB_FINISH_4_6": {"trigger_days": 30, "next": ["轨道板(道床)铺设后,6个月以后"]}, + "GDB_FINISH_AFTER_6": {"trigger_days": None, "next": None}, + + # 桥梁切换规则 + "YZLQ_BEFORE_JS": {"trigger_days": 1, "next": ["架桥机(运梁车)通过"]}, + "YZLQ_AFTER_JS": {"trigger_days": 7, "next": ["桥梁主体工程完工后,第1至3个月"]}, + "QL_ZHUTI_1_3": {"trigger_days": 90, "next": ["桥梁主体工程完工后,第4至6个月"]}, + "QL_ZHUTI_4_6": {"trigger_days": 90, "next": ["桥梁主体工程完工后,6个月以后"]}, + "QL_ZHUTI_AFTER_6": {"trigger_days": None, "next": None}, + "GD_FINISH_1": {"trigger_days": 30, "next": ["轨道铺设完成后,2至3个月"]}, + "GD_FINISH_2_3": {"trigger_days": 60, "next": ["轨道铺设完成后,4至12个月"]}, + "GD_FINISH_4_12": {"trigger_days": 240, "next": ["轨道铺设完成后,12个月以后"]}, + "GD_FINISH_AFTER_12": {"trigger_days": None, "next": None}, + + # 隧道切换规则 + "YG_DIBAN_1": {"trigger_days": 30, "next": ["仰拱(底板)施工完成后,第2至3个月"]}, + "YG_DIBAN_2_3": {"trigger_days": 60, "next": ["仰拱(底板)施工完成后,3个月以后"]}, + "YG_DIBAN_AFTER_3": {"trigger_days": None, "next": None}, + "WZGD_1_3": {"trigger_days": 90, "next": ["无砟轨道铺设后,4至12个月"]}, + "WZGD_4_12": {"trigger_days": 240, "next": ["无砟轨道铺设后,12个月以后"]}, + "WZGD_AFTER_12": {"trigger_days": None, "next": None}, + + # 静态分组(无切换) + "DZ_CONTINUE": {"trigger_days": None, "next": None}, + "DZ_INTERVAL": {"trigger_days": None, "next": None}, + "STATIC": {"trigger_days": None, "next": None} } - def _load_winter_break_labels(self): - """冬休标识(仅冬休)""" - return {"冬休"} - def _parse_to_date(self, time_str): - """ - 私有辅助方法:将时间字符串解析为日期对象,仅保留年月日,忽略时分秒 - :param time_str: 时间字符串,格式为 "YYYY-MM-DD HH:MM:SS" - :return: date对象 / None(解析失败时) - """ + """解析时间字符串为date对象(仅保留年月日)""" if not time_str: return None try: @@ -168,22 +255,18 @@ class OperatingModePredictor: except ValueError: return None - def _get_time_statistics_from_reversed(self, data, workinfo): - """ - 私有辅助方法:从倒序数据中提取时间统计信息 - :param data: 单个point_id的倒序数据列表 - :param workinfo: 目标工况名称(冬休时为冬休前上一工况) - :return: 元组 (首次测量日期date对象, 首次到末次持续天数, 今日与首次测量天数差) - """ - # 筛选目标工况的有效记录(忽略冬休) + def _get_time_statistics(self, data, target_workinfo): + """从倒序数据中提取目标工况的时间统计信息""" + # 筛选目标工况的有效记录 target_records = [ d for d in data - if d.get("workinfoname") == workinfo + if self._standardize_name(d.get("workinfoname")) == self._standardize_name(target_workinfo) and d.get("workinfoname") not in self.winter_break_labels ] if not target_records: return None, 0, 0 + # 解析日期 target_dates = [] for item in target_records: d = self._parse_to_date(item.get("MTIME_W")) @@ -192,9 +275,9 @@ class OperatingModePredictor: if not target_dates: return None, 0, 0 + # 计算时间差(倒序数据:最新在0,最早在-1) last_date = target_dates[0] first_date = target_dates[-1] - cumulative_days = (last_date - first_date).days today = date.today() days_to_today = (today - first_date).days if first_date else 0 @@ -202,46 +285,19 @@ class OperatingModePredictor: return first_date, cumulative_days, days_to_today def _get_pre_winter_break_workinfo(self, inner_data_list): - """ - 私有辅助方法:从倒序数据中提取冬休前的上一个有效工况 - :param inner_data_list: 单个point_id的倒序数据列表 - :return: 冬休前工况名称 / None(未找到时) - """ + """提取冬休前的上一个有效工况""" if not inner_data_list: return None - # 倒序遍历,跳过冬休,找第一个有效工况 for record in inner_data_list: current_work = record.get("workinfoname") - if current_work and current_work not in self.winter_break_labels and current_work in self.base_periods: - return current_work + if current_work and current_work not in self.winter_break_labels: + # 优先返回等效新工况 + return self.old_to_new_map.get(current_work, current_work) return None - def _match_next_condition(self, current_name, candidates): - """ - 私有辅助方法:按符号风格匹配下一个工况名称(全角/半角括号对应) - :param current_name: 当前工况名称(冬休时为冬休前上一工况) - :param candidates: 目标工况候选列表 - :return: 匹配的工况名称 / 当前工况名称(无候选时) - """ - if not candidates: - return current_name - if "(" in current_name: - for cand in candidates: - if "(" in cand: - return cand - if "(" in current_name: - for cand in candidates: - if "(" in cand: - return cand - return candidates[0] - def _validate_point_id(self, inner_list): - """ - 私有辅助方法:校验内嵌列表内所有元素的point_id是否一致 - :param inner_list: 单个point_id的倒序数据列表 - :return: point_id / None(校验失败或列表为空时) - """ + """校验内嵌列表内point_id是否一致""" if not inner_list: return None base_point_id = inner_list[0].get("point_id") @@ -252,19 +308,21 @@ class OperatingModePredictor: def predict(self, data_2d_list): """ - 公有核心方法:执行工况预测,处理二维输入数据,返回一维结果列表 + 公有核心方法:执行工况预测 :param data_2d_list: 二维倒序数据列表,格式 [[{},{},{}], [{},{},{}]] - :return: 一维结果列表,格式 [{}, {}, {}],每个元素为对应内嵌列表的最新记录+推导字段 + :return: 一维结果列表,格式 [{}, {}, {}] """ - final_result_1d = [] + final_result = [] for inner_data_list in data_2d_list: if not isinstance(inner_data_list, list) or len(inner_data_list) == 0: continue + # 取最新记录并复制(避免修改原数据) latest_record = inner_data_list[0].copy() point_id = self._validate_point_id(inner_data_list) + # 校验point_id if not point_id: latest_record.update({ "status": "fail", @@ -275,11 +333,12 @@ class OperatingModePredictor: "judge_based_workinfo": None, "error_msg": "point_id不一致或缺失" }) - final_result_1d.append(latest_record) + final_result.append(latest_record) continue + # 获取当前工况并标准化 current_workinfo = latest_record.get("workinfoname") - if not current_workinfo or current_workinfo not in self.base_periods: + if not current_workinfo or self._standardize_name(current_workinfo) not in self.std_to_canonical: latest_record.update({ "status": "fail", "current_workinfo": None, @@ -289,10 +348,10 @@ class OperatingModePredictor: "judge_based_workinfo": None, "error_msg": "工况无效或缺失" }) - final_result_1d.append(latest_record) + final_result.append(latest_record) continue - # 冬休逻辑:当前是冬休 → 取冬休前工况作为判断基准 + # 冬休逻辑:取冬休前工况作为判断基准 if current_workinfo in self.winter_break_labels: judge_based_workinfo = self._get_pre_winter_break_workinfo(inner_data_list) if not judge_based_workinfo: @@ -305,40 +364,46 @@ class OperatingModePredictor: "judge_based_workinfo": None, "error_msg": "冬休前未找到有效工况" }) - final_result_1d.append(latest_record) + final_result.append(latest_record) continue else: - judge_based_workinfo = current_workinfo + # 非冬休:优先使用等效新工况作为判断基准 + judge_based_workinfo = self.old_to_new_map.get(current_workinfo, current_workinfo) - # 用判断基准工况计算时间 - first_dt, cumulative_days, days_to_today = self._get_time_statistics_from_reversed( + # 计算时间统计信息 + first_dt, cumulative_days, days_to_today = self._get_time_statistics( inner_data_list, judge_based_workinfo ) first_measure_date = first_dt.strftime("%Y-%m-%d") if first_dt else None - # 推导下一工况 - group_id = self.condition_group.get(judge_based_workinfo, "STATIC") - rule = self.group_transition_rules.get(group_id, {}) + # 推导下一工况(以base_periods中的名称为准) + std_judge_work = self._standardize_name(judge_based_workinfo) + group_id = self.std_to_group.get(std_judge_work, "STATIC") + rule = self.transition_rules.get(group_id, {}) trigger_days = rule.get("trigger_days") - next_candidates = rule.get("next_candidates", []) + next_candidates = rule.get("next", []) - if trigger_days is not None and cumulative_days >= trigger_days: - next_workname = self._match_next_condition(judge_based_workinfo, next_candidates) + if trigger_days is not None and cumulative_days >= trigger_days and next_candidates: + # 切换工况:取base_periods中的规范名称 + next_workinfo = next_candidates[0] else: - next_workname = judge_based_workinfo + # 不切换:有等效则返回新工况,无则返回旧工况 + next_workinfo = self.old_to_new_map.get(judge_based_workinfo, judge_based_workinfo) - # 组装结果 + # 组装最终结果 latest_record.update({ "status": "success", - "current_workinfo": current_workinfo, - "judge_based_workinfo": judge_based_workinfo, + "current_workinfo": current_workinfo, # 保留原始输入工况 + "judge_based_workinfo": judge_based_workinfo, # 实际判断用的工况(新工况优先) "first_measure_date": first_measure_date, "days_from_first_to_today": days_to_today, - "next_workinfo": next_workname, + "next_workinfo": next_workinfo, # 推导结果:有等效返回新工况,无则返回旧工况 "point_id": point_id, "error_msg": "" }) - final_result_1d.append(latest_record) + final_result.append(latest_record) + + return final_result + - return final_result_1d \ No newline at end of file