Classify a detection against the database. Returns: ('new', description) | ('update', diff) | ('known', '') | ('irrelevant', reason)
(self, category: str, title: str, link: str = "", date: str = "")
| 528 | self.db_stats["fda/guidance"] = {"count": len(self.fda_guidance_titles)} |
| 529 | |
| 530 | def classify(self, category: str, title: str, link: str = "", date: str = "") -> tuple[str, str]: |
| 531 | """Classify a detection against the database. |
| 532 | |
| 533 | Returns: ('new', description) | ('update', diff) | ('known', '') | ('irrelevant', reason) |
| 534 | """ |
| 535 | title_lower = title.strip().lower() |
| 536 | |
| 537 | if category == "eu_mdr/mdcg": |
| 538 | mdcg_match = re.search(r"MDCG\s+(20\d{2}[-/]\d+)", title, re.IGNORECASE) |
| 539 | if not mdcg_match: |
| 540 | return ("irrelevant", "No MDCG ID pattern in title") |
| 541 | mdcg_id = mdcg_match.group(1).replace("/", "-") |
| 542 | normalized = f"mdcg-{mdcg_id}" |
| 543 | |
| 544 | # Check for revision indicator attached to THIS MDCG document |
| 545 | # Only match rev.X that appears near the MDCG ID (within ~30 chars), |
| 546 | # NOT revision markers belonging to other referenced documents |
| 547 | # e.g. "MDCG 2021-24 Rev.1" -> rev.1 (correct) |
| 548 | # e.g. "...MDCG 2020-6 and MEDDEV 2.7/1 rev.4..." -> ignore rev.4 |
| 549 | mdcg_end = mdcg_match.end() |
| 550 | nearby_text = title[mdcg_end:mdcg_end + 30] |
| 551 | rev_match = re.search(r"^\s*[-–/]?\s*[Rr]ev[\.\s]*(\d+)", nearby_text) |
| 552 | new_rev = rev_match.group(0).strip() if rev_match else "" |
| 553 | |
| 554 | if normalized in self.mdcg_ids or f"MDCG {mdcg_id}" in self.mdcg_ids: |
| 555 | if new_rev: |
| 556 | # Search result mentions a revision -- check if our DB already has it |
| 557 | db_entry = next( |
| 558 | (e for e in self._mdcg_entries if e.get("id") == normalized), None |
| 559 | ) |
| 560 | db_rev = "" |
| 561 | if db_entry: |
| 562 | db_rev = db_entry.get("revision", "") |
| 563 | for lang in ("zh", "en"): |
| 564 | t = (db_entry.get("title") or {}).get(lang, "") |
| 565 | if t and not db_rev: |
| 566 | rm = re.search(r"[Rr]ev[\.\s]*(\d+)", t) |
| 567 | if rm: |
| 568 | db_rev = rm.group(0).strip() |
| 569 | if db_rev and db_rev.lower().replace(" ", "") == new_rev.lower().replace(" ", ""): |
| 570 | return ("known", "") |
| 571 | return ("update", f"Revision update: {normalized} has {new_rev} (DB: {db_rev or 'original'})") |
| 572 | return ("known", "") |
| 573 | return ("new", f"New MDCG document: {mdcg_match.group(0)}") |
| 574 | |
| 575 | elif category == "eu_mdr/standards": |
| 576 | cid_match = re.search(r"20\d{2}/\d+", title) |
| 577 | if cid_match: |
| 578 | cid = cid_match.group(0) |
| 579 | if self.standards_latest_amendment and cid > self.standards_latest_amendment: |
| 580 | return ("update", f"New amendment {cid} (DB latest: {self.standards_latest_amendment})") |
| 581 | return ("known", "") |
| 582 | return ("irrelevant", "No CID amendment number found") |
| 583 | |
| 584 | elif category == "nmpa/regulations": |
| 585 | for known in self.nmpa_reg_titles: |
| 586 | if known in title or title in known: |
| 587 | return ("known", "") |
no test coverage detected