thinhtt
Deploy from local
300df0f
Raw
History Blame Contribute Delete
6.56 kB
"""
Data models for cross-reference extraction.
All dataclasses are pure Python (no Neo4j dependency) so they can be
used/tested independently by any team member without a running DB.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------
class RefType(str, Enum):
"""Top-level classification of a reference."""
INTERNAL = "INTERNAL" # same document, different Điều/Khoản/Điểm
EXTERNAL = "EXTERNAL" # cross-document reference
MODIFICATION = "MODIFICATION" # sửa đổi / bổ sung / bãi bỏ link
class ModAction(str, Enum):
"""The legal action performed by a modifying document."""
SUA_DOI = "sua_doi" # sửa đổi — replace content
BO_SUNG = "bo_sung" # bổ sung — insert new content
THAY_THE = "thay_the" # thay thế — replace a segment
BAI_BO = "bai_bo" # bãi bỏ — void/remove
HET_HIEU_LUC = "het_hieu_luc" # hết hiệu lực một phần
class DocType(str, Enum):
"""Loại văn bản supported by the system."""
LUAT = "Luật"
BO_LUAT = "Bộ luật"
NGHI_DINH = "Nghị định"
THONG_TU = "Thông tư"
TTLT = "Thông tư liên tịch"
QUYET_DINH = "Quyết định" # out-of-scope but kept for logging
UNKNOWN = "unknown"
# ---------------------------------------------------------------------------
# Reference dataclasses (plain data, no DB coupling)
# ---------------------------------------------------------------------------
@dataclass
class InternalRef:
"""
A reference from one structural element to another within the same document.
Produced by: T2.1 — extract_internal_references()
Written to Neo4j as: [:REFERENCES_INTERNAL]
"""
source_doc_id: str # Document node id
source_article_uid: str # Article.uid of the citing element
source_clause_uid: Optional[str] = None # Clause.uid if reference is inside a clause
source_point_uid: Optional[str] = None # Point.uid if reference is inside a point
# Target within the same document
target_article_index: int = 0 # Điều number (integer)
target_clause_index: Optional[int] = None # Khoản number
target_point_label: Optional[str] = None # Điểm letter, e.g. "a", "b"
# Resolved node UIDs (filled in after lookup; None = unresolved)
target_article_uid: Optional[str] = None
target_clause_uid: Optional[str] = None
target_point_uid: Optional[str] = None
context_text: str = "" # original phrase where the reference was found
confidence: float = 1.0 # 1.0 = exact regex match, <1.0 = fuzzy/ambiguous
start_char: int = 0 # Start position in the original fragment
end_char: int = 0 # End position in the original fragment
is_exception: bool = False # Phase 2: Đánh dấu quan hệ ngoại trừ
@dataclass
class ExternalRef:
"""
A reference from a provision in one document to a provision in another.
Produced by: T2.2 — extract_external_references()
Written to Neo4j as: [:REFERENCES_EXTERNAL]
"""
source_doc_id: str
source_article_uid: str
source_clause_uid: Optional[str] = None
source_point_uid: Optional[str] = None
# Raw parsed fields (before lookup)
raw_so_ky_hieu: str = "" # e.g. "46/2014/NĐ-CP"
normalized_so_ky_hieu: str = "" # e.g. "ND-046-2014"
target_doc_type: DocType = DocType.UNKNOWN
target_article_index: Optional[int] = None
target_clause_index: Optional[int] = None
target_point_label: Optional[str] = None
# Resolved IDs (filled in after lookup)
target_doc_id: Optional[str] = None
target_article_uid: Optional[str] = None
target_clause_uid: Optional[str] = None
target_point_uid: Optional[str] = None
context_text: str = ""
match_method: str = "exact" # "exact" | "fuzzy_levenshtein" | "fuzzy_substring"
confidence: float = 1.0
start_char: int = 0
end_char: int = 0
is_exception: bool = False # Phase 2: Đánh dấu nếu là quan hệ ngoại trừ (trừ trường hợp...)
@dataclass
class ModificationRef:
"""
An article-level modification link produced from a "sửa đổi/bổ sung" document.
Produced by: T2.3 — extract_modification_references()
Written to Neo4j as: [:MODIFIES]
"""
# The modifying document
source_doc_id: str
source_article_uid: str # Article in the modifying doc that contains the action
source_clause_index: Optional[str] = None # Clause number in the source document
# The action
action: ModAction = ModAction.SUA_DOI
# The target (resolved)
raw_target_so_ky_hieu: str = ""
target_doc_id: Optional[str] = None
target_article_index: Optional[int] = None
target_clause_index: Optional[int] = None
target_point_label: Optional[str] = None
target_doc_uid: Optional[str] = None
target_article_uid: Optional[str] = None
target_clause_uid: Optional[str] = None
target_point_uid: Optional[str] = None
new_text: Optional[str] = None # replacement/inserted text (for sửa đổi/bổ sung)
context_text: str = ""
confidence: float = 1.0
start_char: int = 0
end_char: int = 0
# T2.3 improvement: flag to indicate this ref needs target_doc_id/article from context
is_partial_ref: bool = False
# ---------------------------------------------------------------------------
# Aggregate result container
# ---------------------------------------------------------------------------
@dataclass
class ExtractionResult:
"""
Returned by CrossReferenceExtractor for a single document.
All lists contain only successfully *parsed* references
(resolution happens in a separate step).
"""
doc_id: str
internal_refs: list[InternalRef] = field(default_factory=list)
external_refs: list[ExternalRef] = field(default_factory=list)
modification_refs: list[ModificationRef] = field(default_factory=list)
# Counts for monitoring
parse_errors: list[str] = field(default_factory=list) # human-readable error strings
@property
def total(self) -> int:
return len(self.internal_refs) + len(self.external_refs) + len(self.modification_refs)