standard_service.py 25 KB

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