standard_service.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. """
  2. 标准库匹配规则服务 - 内存处理版本
  3. 实现施工方案审查-时效性审查的匹配逻辑
  4. 架构:
  5. - StandardRepository: 内存数据存储和索引
  6. - StandardMatcher: 匹配规则逻辑
  7. - StandardMatchingService: 对外服务接口
  8. """
  9. import sys
  10. import os
  11. current_dir = os.path.dirname(os.path.abspath(__file__))
  12. project_root = os.path.dirname(os.path.dirname(current_dir))
  13. if project_root not in sys.path:
  14. from typing import List, Dict, Optional, Set
  15. from dataclasses import dataclass, field
  16. from enum import Enum
  17. class ValidityStatus(Enum):
  18. """时效性状态"""
  19. CURRENT = "XH" # 现行
  20. TRIAL = "SX" # 试行
  21. ABOLISHED = "FZ" # 废止
  22. class MatchResultCode(Enum):
  23. """匹配结果状态码"""
  24. OK = "OK" # 正常
  25. SUBSTITUTED = "SUBSTITUTED" # 被替代
  26. ABOLISHED = "ABOLISHED" # 废止无现行
  27. MISMATCH = "MISMATCH" # 不匹配
  28. NOT_FOUND = "NOT_FOUND" # 标准库不存在
  29. @dataclass
  30. class StandardMatchResult:
  31. """标准匹配结果数据结构"""
  32. seq_no: int = 0 # 序号
  33. original_name: str = "" # 原始标准名称
  34. original_number: str = "" # 原始标准号
  35. substitute_number: Optional[str] = None # 替代标准号(如果有)
  36. substitute_name: Optional[str] = None # 替代标准名称(如果有)
  37. process_result: str = "" # 处理结果状态
  38. status_code: str = "" # 状态码
  39. final_result: str = "" # 最终结果消息
  40. @dataclass
  41. class StandardRecord:
  42. """标准记录数据结构"""
  43. id: int
  44. standard_name: str
  45. standard_number: str
  46. validity: str
  47. class StandardRepository:
  48. """
  49. 标准库内存数据仓库
  50. 负责加载和索引标准数据,支持快速查询
  51. """
  52. def __init__(self):
  53. # 原始数据列表
  54. self._records: List[StandardRecord] = []
  55. # 索引结构,加速查询
  56. self._number_index: Dict[str, StandardRecord] = {} # 标准号 -> 记录
  57. self._name_index: Dict[str, List[StandardRecord]] = {} # 名称 -> 记录列表
  58. self._current_records: List[StandardRecord] = [] # 现行/试行标准列表
  59. def load_data(self, raw_data: List[Dict]):
  60. """
  61. 加载原始数据到内存并建立索引
  62. Args:
  63. raw_data: 从数据库查询的原始标准数据列表
  64. """
  65. self._records = []
  66. self._number_index = {}
  67. self._name_index = {}
  68. self._current_records = []
  69. for item in raw_data:
  70. # 跳过无效数据
  71. standard_number = item.get("standard_number")
  72. standard_name = item.get("standard_name")
  73. if not standard_number or not standard_name:
  74. continue
  75. record = StandardRecord(
  76. id=item.get("id", 0),
  77. standard_name=standard_name,
  78. standard_number=standard_number,
  79. validity=item.get("validity", "")
  80. )
  81. self._records.append(record)
  82. # 建立标准号索引
  83. self._number_index[record.standard_number] = record
  84. # 建立名称索引(一个名称可能对应多个标准号)
  85. if record.standard_name not in self._name_index:
  86. self._name_index[record.standard_name] = []
  87. self._name_index[record.standard_name].append(record)
  88. # 收集现行/试行标准
  89. if record.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  90. self._current_records.append(record)
  91. # 对现行标准按标准号降序排序(用于找最新替代标准)
  92. # 处理可能的 None 值
  93. self._current_records.sort(
  94. key=lambda r: r.standard_number or "",
  95. reverse=True
  96. )
  97. print(f"self._records={len(self._records)}")
  98. def find_by_number_exact(self, standard_number: str) -> Optional[StandardRecord]:
  99. """精确匹配标准号"""
  100. return self._number_index.get(standard_number)
  101. def find_by_name_exact(self, standard_name: str) -> Optional[StandardRecord]:
  102. """精确匹配标准名称(返回第一个)"""
  103. records = self._name_index.get(standard_name, [])
  104. return records[0] if records else None
  105. def find_by_name_fuzzy(self, standard_name: str) -> List[StandardRecord]:
  106. """模糊匹配标准名称"""
  107. results = []
  108. for name, records in self._name_index.items():
  109. if standard_name in name or name in standard_name:
  110. results.extend(records)
  111. return results
  112. def find_by_number_fuzzy(self, standard_number: str) -> List[StandardRecord]:
  113. """模糊匹配标准号"""
  114. results = []
  115. # 提取前缀(如 GB/T 5972)
  116. parts = standard_number.split("-")
  117. prefix = parts[0] if parts else standard_number
  118. for number, record in self._number_index.items():
  119. # 前缀匹配
  120. if number.startswith(prefix):
  121. results.append(record)
  122. return results
  123. def find_current_by_name(self, standard_name: str) -> List[StandardRecord]:
  124. """查询指定名称的现行/试行标准(支持模糊匹配)"""
  125. results = []
  126. for record in self._current_records:
  127. # 精确匹配
  128. if record.standard_name == standard_name:
  129. results.append(record)
  130. # 模糊匹配(忽略空格、书名号等)
  131. elif self._is_name_fuzzy_match_for_repo(record.standard_name, standard_name):
  132. results.append(record)
  133. return results
  134. def _is_name_fuzzy_match_for_repo(self, name1: str, name2: str) -> bool:
  135. """判断两个标准名称是否模糊匹配"""
  136. clean1 = name1.replace("《", "").replace("》", "").replace(" ", "").replace(" ", "")
  137. clean2 = name2.replace("《", "").replace("》", "").replace(" ", "").replace(" ", "")
  138. return clean1 == clean2
  139. def get_all_records(self) -> List[StandardRecord]:
  140. """获取所有记录"""
  141. return self._records.copy()
  142. class StandardMatcher:
  143. """
  144. 标准匹配器
  145. 实现标准库匹配规则的核心逻辑
  146. """
  147. def __init__(self, repository: StandardRepository):
  148. self.repo = repository
  149. def match(self, seq_no: int, input_name: str, input_number: str) -> StandardMatchResult:
  150. """
  151. 执行标准匹配
  152. 匹配流程:
  153. 1. 标准号精确匹配
  154. 2. 根据匹配结果进入不同分支处理
  155. """
  156. # 去除前后空格
  157. input_name = input_name.strip() if input_name else input_name
  158. input_number = input_number.strip() if input_number else input_number
  159. # 清洗书名号和括号
  160. input_name = self._clean_brackets_and_booknames(input_name)
  161. input_number = self._clean_brackets_and_booknames(input_number)
  162. result = StandardMatchResult(
  163. seq_no=seq_no,
  164. original_name=input_name,
  165. original_number=input_number
  166. )
  167. # 步骤1: 精确匹配标准号
  168. match_by_number = self.repo.find_by_number_exact(input_number)
  169. if match_by_number:
  170. # 分支A: 标准号匹配成功
  171. return self._handle_number_matched(result, match_by_number, input_name)
  172. else:
  173. # 分支B: 标准号未匹配
  174. return self._handle_number_not_matched(result, input_name, input_number)
  175. def _handle_number_matched(
  176. self,
  177. result: StandardMatchResult,
  178. db_record: StandardRecord,
  179. input_name: str
  180. ) -> StandardMatchResult:
  181. """处理标准号匹配成功的情况"""
  182. # 检查名称是否匹配
  183. if db_record.standard_name == input_name:
  184. # 名称也匹配
  185. return self._handle_full_match(result, db_record)
  186. else:
  187. # 名称不匹配
  188. return self._handle_name_mismatch(result, db_record, input_name)
  189. def _handle_full_match(
  190. self,
  191. result: StandardMatchResult,
  192. db_record: StandardRecord
  193. ) -> StandardMatchResult:
  194. """处理名称和标准号都完全匹配的情况"""
  195. if db_record.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  196. # 情况1: 现行或试行 - 状态正常
  197. return self._set_ok_result(result)
  198. else:
  199. # 废止状态 - 查找替代标准
  200. return self._handle_abolished(result, db_record)
  201. def _handle_name_mismatch(
  202. self,
  203. result: StandardMatchResult,
  204. db_record: StandardRecord,
  205. input_name: str
  206. ) -> StandardMatchResult:
  207. """处理标准号匹配但名称不匹配的情况"""
  208. # 首先检查是否是名称模糊匹配(忽略空格、书名号等)
  209. if self._is_name_fuzzy_match(db_record.standard_name, input_name):
  210. # 名称模糊匹配成功,按完全匹配处理
  211. return self._handle_full_match(result, db_record)
  212. # 尝试用输入的名称模糊匹配
  213. name_matches = self.repo.find_by_name_fuzzy(input_name)
  214. # 查找精确名称匹配
  215. exact_match = self._find_exact_name_match(name_matches, input_name)
  216. if exact_match:
  217. # 找到名称匹配的记录
  218. return self._handle_fuzzy_name_match(result, exact_match)
  219. # 尝试在模糊匹配结果中查找模糊名称匹配
  220. for match in name_matches:
  221. if self._is_name_fuzzy_match(match.standard_name, input_name):
  222. return self._handle_fuzzy_name_match(result, match)
  223. # 名称完全不匹配,但标准号已匹配成功
  224. # 说明该标准存在于库中,应返回不匹配而非不存在
  225. if db_record.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  226. return self._set_mismatch_result(result, db_record)
  227. elif db_record.validity == ValidityStatus.ABOLISHED.value:
  228. return self._handle_abolished(result, db_record)
  229. return self._set_not_found_result(result)
  230. def _handle_number_not_matched(
  231. self,
  232. result: StandardMatchResult,
  233. input_name: str,
  234. input_number: str
  235. ) -> StandardMatchResult:
  236. """处理标准号未匹配的情况"""
  237. # 尝试模糊匹配标准号
  238. fuzzy_number_matches = self.repo.find_by_number_fuzzy(input_number)
  239. if fuzzy_number_matches:
  240. # 检查名称是否匹配
  241. return self._check_name_in_records(result, fuzzy_number_matches, input_name)
  242. else:
  243. # 尝试直接按名称查询
  244. return self._search_by_name_only(result, input_name)
  245. def _check_name_in_records(
  246. self,
  247. result: StandardMatchResult,
  248. records: List[StandardRecord],
  249. input_name: str
  250. ) -> StandardMatchResult:
  251. """在一批记录中查找名称匹配"""
  252. # 首先尝试精确匹配
  253. for record in records:
  254. if record.standard_name == input_name:
  255. # 名称匹配,检查状态
  256. if record.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  257. return self._set_mismatch_result(result, record)
  258. elif record.validity == ValidityStatus.ABOLISHED.value:
  259. return self._handle_abolished(result, record)
  260. # 尝试模糊名称匹配(忽略空格和书名号)
  261. for record in records:
  262. if self._is_name_fuzzy_match(record.standard_name, input_name):
  263. # 名称模糊匹配成功
  264. if record.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  265. return self._set_mismatch_result(result, record)
  266. elif record.validity == ValidityStatus.ABOLISHED.value:
  267. return self._handle_abolished(result, record)
  268. # 名称不匹配
  269. return self._set_not_found_result(result)
  270. def _search_by_name_only(
  271. self,
  272. result: StandardMatchResult,
  273. input_name: str
  274. ) -> StandardMatchResult:
  275. """仅通过名称查询"""
  276. # 精确匹配名称
  277. name_match = self.repo.find_by_name_exact(input_name)
  278. if name_match:
  279. if name_match.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  280. return self._set_mismatch_result(result, name_match)
  281. elif name_match.validity == ValidityStatus.ABOLISHED.value:
  282. return self._set_not_found_result(result)
  283. # 模糊匹配名称
  284. fuzzy_matches = self.repo.find_by_name_fuzzy(input_name)
  285. # 首先尝试精确匹配
  286. exact_match = self._find_exact_name_match(fuzzy_matches, input_name)
  287. if exact_match:
  288. if exact_match.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  289. return self._set_mismatch_result(result, exact_match)
  290. # 尝试模糊名称匹配(忽略空格、书名号等)
  291. for match in fuzzy_matches:
  292. if self._is_name_fuzzy_match(match.standard_name, input_name):
  293. if match.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  294. return self._set_mismatch_result(result, match)
  295. elif match.validity == ValidityStatus.ABOLISHED.value:
  296. return self._handle_abolished(result, match)
  297. return self._set_not_found_result(result)
  298. def _handle_fuzzy_name_match(
  299. self,
  300. result: StandardMatchResult,
  301. match_record: StandardRecord
  302. ) -> StandardMatchResult:
  303. """处理模糊名称匹配成功的情况"""
  304. if match_record.validity in [ValidityStatus.CURRENT.value, ValidityStatus.TRIAL.value]:
  305. return self._set_mismatch_result(result, match_record)
  306. elif match_record.validity == ValidityStatus.ABOLISHED.value:
  307. return self._handle_abolished(result, match_record)
  308. return self._set_not_found_result(result)
  309. def _handle_abolished(
  310. self,
  311. result: StandardMatchResult,
  312. abolished_record: StandardRecord
  313. ) -> StandardMatchResult:
  314. """处理已废止标准的情况"""
  315. # 查询同名现行标准作为替代
  316. substitutes = self.repo.find_current_by_name(abolished_record.standard_name)
  317. if substitutes:
  318. # 有替代标准,取最新的(已按标准号降序)
  319. latest = substitutes[0]
  320. return self._set_substituted_result(result, latest)
  321. else:
  322. # 无替代标准
  323. return self._set_abolished_result(result)
  324. # ========== 格式化方法 ==========
  325. def _format_standard_name(self, name: str) -> str:
  326. """格式化标准名称,确保只有一个《》包裹"""
  327. if not name:
  328. return name
  329. name = name.strip()
  330. # 去除已有的书名号
  331. while name.startswith('《'):
  332. name = name[1:]
  333. while name.endswith('》'):
  334. name = name[:-1]
  335. return f"《{name}》"
  336. def _format_standard_number(self, number: str) -> str:
  337. """格式化标准编号,确保用()包裹"""
  338. if not number:
  339. return number
  340. number = number.strip()
  341. # 去除已有的括号
  342. if number.startswith('(') or number.startswith('('):
  343. number = number[1:]
  344. if number.endswith(')') or number.endswith(')'):
  345. number = number[:-1]
  346. return f"({number})"
  347. # ========== 结果设置方法(每个方法职责单一) ==========
  348. def _set_ok_result(self, result: StandardMatchResult) -> StandardMatchResult:
  349. """设置状态正常的结果"""
  350. result.process_result = "正常"
  351. result.status_code = MatchResultCode.OK.value
  352. result.final_result = "无问题"
  353. return result
  354. def _set_substituted_result(
  355. self,
  356. result: StandardMatchResult,
  357. substitute: StandardRecord
  358. ) -> StandardMatchResult:
  359. """设置被替代的结果"""
  360. result.substitute_name = self._format_standard_name(substitute.standard_name)
  361. result.substitute_number = self._format_standard_number(substitute.standard_number)
  362. result.process_result = "被替代"
  363. result.status_code = MatchResultCode.SUBSTITUTED.value
  364. result.final_result = (
  365. f"{self._format_standard_name(result.original_name)}"
  366. f"{self._format_standard_number(result.original_number)}已废止,"
  367. f"替代{self._format_standard_name(substitute.standard_name)}"
  368. f"{self._format_standard_number(substitute.standard_number)}"
  369. )
  370. return result
  371. def _set_abolished_result(self, result: StandardMatchResult) -> StandardMatchResult:
  372. """设置废止无替代的结果"""
  373. result.process_result = "废止无现行"
  374. result.status_code = MatchResultCode.ABOLISHED.value
  375. result.final_result = (
  376. f"{self._format_standard_name(result.original_name)}"
  377. f"{self._format_standard_number(result.original_number)}已废止,无现行状态"
  378. )
  379. return result
  380. def _set_mismatch_result(
  381. self,
  382. result: StandardMatchResult,
  383. actual: StandardRecord
  384. ) -> StandardMatchResult:
  385. """设置不匹配的结果"""
  386. result.substitute_name = self._format_standard_name(actual.standard_name)
  387. result.substitute_number = self._format_standard_number(actual.standard_number)
  388. result.process_result = "不匹配"
  389. result.status_code = MatchResultCode.MISMATCH.value
  390. result.final_result = (
  391. f"{self._format_standard_name(result.original_name)}"
  392. f"{self._format_standard_number(result.original_number)}"
  393. f"与实际{self._format_standard_name(actual.standard_name)}"
  394. f"{self._format_standard_number(actual.standard_number)}不匹配"
  395. )
  396. return result
  397. def _set_not_found_result(self, result: StandardMatchResult) -> StandardMatchResult:
  398. """设置不存在的结果"""
  399. result.process_result = "标准库不存在"
  400. result.status_code = MatchResultCode.NOT_FOUND.value
  401. result.final_result = (
  402. f"{self._format_standard_name(result.original_name)}"
  403. f"{self._format_standard_number(result.original_number)}标准库不存在,请确认"
  404. )
  405. return result
  406. # ========== 工具方法 ==========
  407. def _is_name_fuzzy_match(self, name1: str, name2: str) -> bool:
  408. """
  409. 判断两个标准名称是否模糊匹配
  410. 只去除书名号,保留中间空格(中间空格属于名称的一部分)
  411. """
  412. # 清理书名号,但保留中间空格
  413. clean1 = name1.replace("《", "").replace("》", "")
  414. clean2 = name2.replace("《", "").replace("》", "")
  415. return clean1 == clean2
  416. def _clean_brackets_and_booknames(self, text: str) -> str:
  417. """
  418. 清洗字符串前后的书名号和括号
  419. 包括:《》()()
  420. """
  421. if not text:
  422. return text
  423. # 循环去除前后的书名号和括号,直到没有变化
  424. changed = True
  425. while changed:
  426. changed = False
  427. original = text
  428. # 去除前导的书名号和括号
  429. if text.startswith("《"):
  430. text = text[1:]
  431. changed = True
  432. if text.startswith("》"):
  433. text = text[1:]
  434. changed = True
  435. if text.startswith("("):
  436. text = text[1:]
  437. changed = True
  438. if text.startswith(")"):
  439. text = text[1:]
  440. changed = True
  441. if text.startswith("("):
  442. text = text[1:]
  443. changed = True
  444. if text.startswith(")"):
  445. text = text[1:]
  446. changed = True
  447. # 去除尾随的书名号和括号
  448. if text.endswith("《"):
  449. text = text[:-1]
  450. changed = True
  451. if text.endswith("》"):
  452. text = text[:-1]
  453. changed = True
  454. if text.endswith("("):
  455. text = text[:-1]
  456. changed = True
  457. if text.endswith(")"):
  458. text = text[:-1]
  459. changed = True
  460. if text.endswith("("):
  461. text = text[:-1]
  462. changed = True
  463. if text.endswith(")"):
  464. text = text[:-1]
  465. changed = True
  466. # 如果文本变空了,停止循环
  467. if not text:
  468. break
  469. return text
  470. def _find_exact_name_match(
  471. self,
  472. records: List[StandardRecord],
  473. target_name: str
  474. ) -> Optional[StandardRecord]:
  475. """在记录列表中查找精确名称匹配"""
  476. for record in records:
  477. if record.standard_name == target_name:
  478. return record
  479. return None
  480. class StandardMatchingService:
  481. """
  482. 标准库匹配服务
  483. 对外暴露的统一接口
  484. """
  485. def __init__(self, db_pool=None):
  486. """
  487. 初始化服务
  488. Args:
  489. db_pool: 数据库连接池,如果为None则使用Mock数据
  490. """
  491. self.db_pool = db_pool
  492. self.repository = StandardRepository()
  493. self.matcher = StandardMatcher(self.repository)
  494. self._initialized = False
  495. async def initialize(self):
  496. """
  497. 初始化:从数据库加载数据到内存
  498. 只需要执行一次
  499. """
  500. if self._initialized:
  501. return
  502. if self.db_pool:
  503. # 从真实数据库加载
  504. from utils_test.standard_new_Test.standard_dao import StandardDAO
  505. dao = StandardDAO(self.db_pool)
  506. raw_data = await dao.load_all_standards()
  507. print(f"raw_data={len(raw_data)}")
  508. else:
  509. # 使用Mock数据
  510. raw_data = self._get_mock_data()
  511. self.repository.load_data(raw_data)
  512. self._initialized = True
  513. async def close(self):
  514. """关闭服务,清理资源"""
  515. if self.db_pool:
  516. await self.db_pool.close()
  517. self._initialized = False
  518. def check_standards(self, standards: List[Dict[str, str]]) -> List[StandardMatchResult]:
  519. """
  520. 批量检查标准列表
  521. Args:
  522. standards: 标准列表,每个元素包含:
  523. - standard_name: 标准名称(原始)
  524. - standard_number: 标准号(原始)
  525. Returns:
  526. List[StandardMatchResult]: 匹配结果列表
  527. """
  528. if not self._initialized:
  529. raise RuntimeError("服务未初始化,请先调用 initialize()")
  530. results = []
  531. for idx, std in enumerate(standards, start=1):
  532. result = self.matcher.match(
  533. seq_no=idx,
  534. input_name=std.get("standard_name", ""),
  535. input_number=std.get("standard_number", "")
  536. )
  537. results.append(result)
  538. return results
  539. def check_single(
  540. self,
  541. seq_no: int,
  542. standard_name: str,
  543. standard_number: str
  544. ) -> StandardMatchResult:
  545. """
  546. 检查单个标准
  547. Args:
  548. seq_no: 序号
  549. standard_name: 标准名称
  550. standard_number: 标准号
  551. Returns:
  552. StandardMatchResult: 匹配结果
  553. """
  554. if not self._initialized:
  555. raise RuntimeError("服务未初始化,请先调用 initialize()")
  556. return self.matcher.match(seq_no, standard_name, standard_number)
  557. def _get_mock_data(self) -> List[Dict]:
  558. """获取Mock数据 - 文档中的7个测试案例"""
  559. return [
  560. # 情况1: 正常现行标准
  561. {"id": 1, "standard_name": "铁路桥涵设计规范", "standard_number": "TB 10002-2017", "validity": "XH"},
  562. {"id": 2, "standard_name": "铁路工程抗震设计规范", "standard_number": "GB 50111-2006", "validity": "XH"},
  563. {"id": 3, "standard_name": "铁路混凝土工程施工质量验收标准", "standard_number": "TB 10424-2018", "validity": "XH"},
  564. # 情况4: 不匹配(年份错误)- 输入2023,实际2024
  565. {"id": 4, "standard_name": "公路水运危险性较大工程专项施工方案编制审查规程", "standard_number": "JT/T 1495-2024", "validity": "XH"},
  566. # 情况2: 被替代(废止+有现行替代)
  567. {"id": 5, "standard_name": "起重机 钢丝绳 保养、维护、检验和报废", "standard_number": "GB/T 5972-2016", "validity": "FZ"},
  568. {"id": 6, "standard_name": "起重机 钢丝绳 保养、维护、检验和报废", "standard_number": "GB/T 5972-2023", "validity": "XH"},
  569. # 情况3: 废止无替代
  570. {"id": 7, "standard_name": "缆索起重机", "standard_number": "GB/T 28756-2012", "validity": "FZ"},
  571. {"id": 8, "standard_name": "电力高处作业防坠器", "standard_number": "DL/T 1147-2009", "validity": "FZ"},
  572. ]