| from __future__ import annotations | |
| import re | |
| from typing import List | |
| class ReferenceExtractor: | |
| """ | |
| BNS Reference Extractor | |
| Detects: | |
| section 103 | |
| Section 103 | |
| sections 103 and 104 | |
| sections 103, 104, 105 | |
| section 103 of this Sanhita | |
| """ | |
| # ===================================================== | |
| # SECTION REFERENCES | |
| # ===================================================== | |
| SECTION_RE = re.compile( | |
| r"\bsection[s]?\s+([0-9,\sand]+)", | |
| re.IGNORECASE | |
| ) | |
| NUMBER_RE = re.compile( | |
| r"\d+" | |
| ) | |
| # ===================================================== | |
| # EXTRACT REFERENCES | |
| # ===================================================== | |
| def extract_references( | |
| self, | |
| text: str, | |
| source_section: str | |
| ) -> List[dict]: | |
| references = [] | |
| matches = ( | |
| self.SECTION_RE.finditer( | |
| text | |
| ) | |
| ) | |
| for match in matches: | |
| raw = match.group(1) | |
| targets = ( | |
| self.NUMBER_RE.findall( | |
| raw | |
| ) | |
| ) | |
| for target in targets: | |
| references.append( | |
| { | |
| "source_section": | |
| source_section, | |
| "target_section": | |
| target, | |
| "reference_type": | |
| "section" | |
| } | |
| ) | |
| return references | |
| # ===================================================== | |
| # UNIQUE REFERENCES | |
| # ===================================================== | |
| def unique_references( | |
| self, | |
| refs: List[dict] | |
| ) -> List[dict]: | |
| seen = set() | |
| unique = [] | |
| for ref in refs: | |
| key = ( | |
| ref["source_section"], | |
| ref["target_section"] | |
| ) | |
| if key in seen: | |
| continue | |
| seen.add(key) | |
| unique.append(ref) | |
| return unique | |
| # ===================================================== | |
| # VALIDATION | |
| # ===================================================== | |
| def validate_references( | |
| self, | |
| refs: List[dict] | |
| ) -> List[str]: | |
| errors = [] | |
| for ref in refs: | |
| # Skip malformed references | |
| if not isinstance(ref, dict): | |
| errors.append( | |
| "Invalid reference object" | |
| ) | |
| continue | |
| if ( | |
| "source_section" not in ref | |
| or | |
| "target_section" not in ref | |
| ): | |
| errors.append( | |
| "Reference missing source/target section" | |
| ) | |
| return errors | |
| # ========================================================= | |
| # TEST | |
| # ========================================================= | |
| if __name__ == "__main__": | |
| sample = """ | |
| Whoever commits murder under section 103 | |
| shall be punished. | |
| The provisions of sections 63 and 64 | |
| shall also apply. | |
| Nothing in section 111 shall affect | |
| the operation of this section. | |
| """ | |
| extractor = ( | |
| ReferenceExtractor() | |
| ) | |
| refs = ( | |
| extractor.extract_references( | |
| text=sample, | |
| source_section="104" | |
| ) | |
| ) | |
| refs = ( | |
| extractor.unique_references( | |
| refs | |
| ) | |
| ) | |
| print( | |
| "References:", | |
| len(refs) | |
| ) | |
| print() | |
| for r in refs: | |
| print(r) | |
| print() | |
| errors = ( | |
| extractor.validate_references( | |
| refs | |
| ) | |
| ) | |
| print( | |
| "Errors:", | |
| len(errors) | |
| ) |