Spaces:
Sleeping
Sleeping
File size: 5,123 Bytes
300df0f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """
Data models for the segmentation pipeline.
All dataclasses are pure Python β no BeautifulSoup or Neo4j dependency.
This lets cross_reference/ and the application layer import them freely.
UID conventions (must match Neo4j schema from T1.4 / NgΖ°α»i A):
Article.uid = "doc_{doc_id}_dieu_{index}"
Clause.uid = "doc_{doc_id}_dieu_{dieu_idx}_khoan_{idx}"
Point.uid = "doc_{doc_id}_dieu_{dieu_idx}_khoan_{khoan_idx}_diem_{letter}"
Chapter has no uid β identified by (doc_id, roman_index)
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------
class HierarchyType(str, Enum):
"""Structural level of a parsed segment."""
PHAN = "PhαΊ§n" # Part β above ChΖ°Ζ‘ng, used in Bα» luαΊt (gap in spec)
CHUONG = "ChΖ°Ζ‘ng" # Chapter
MUC = "Mα»₯c" # Section β between ChΖ°Ζ‘ng and Δiα»u
DIEU = "Δiα»u" # Article
KHOAN = "KhoαΊ£n" # Clause
DIEM = "Δiα»m" # Point
UNKNOWN = "unknown"
class ConfidenceLevel(str, Enum):
HIGH = "high" # β₯ 0.9
MEDIUM = "medium" # 0.6 β 0.9
LOW = "low" # < 0.6
# ---------------------------------------------------------------------------
# Core segment dataclass
# ---------------------------------------------------------------------------
@dataclass
class Segment:
"""
One structural node produced by the parser.
This is the primary output unit of LegalDocumentParser and the
primary input unit of SegmentWriter and ArticleEmbedder.
Interface note for NgΖ°α»i A:
- doc_id must match Document.id already in Neo4j (from T1.4 / T1.5)
Interface note for cross_reference/ (NgΖ°α»i B, Phase 2):
- article_uid, clause_uid, point_uid are the stable IDs used in
InternalRef.source_article_uid / target_article_uid
"""
# Identity
doc_id: str
hierarchy_type: HierarchyType
index: any # position or label (int or str)
# Hierarchy path β human-readable, e.g. "ChΖ°Ζ‘ng I / Δiα»u 5 / KhoαΊ£n 2"
path: str = ""
# Content
text_content: str = "" # raw HTML of this segment
clean_text: str = "" # plain text, no HTML tags
# Parent linkage
parent_uid: Optional[str] = None # UID of parent segment; None for top-level
# Computed UIDs (set after parsing, before Neo4j write)
uid: Optional[str] = None # own stable UID
# Chapter-specific
roman_index: Optional[str] = None # "I", "II", "III"...
title: Optional[str] = None # heading text (ChΖ°Ζ‘ng/Δiα»u/Mα»₯c title)
section: Optional[str] = None # section context (Mα»₯c), stored as metadata instead of separate node
# Embedding (set by ArticleEmbedder, only for DIEU level)
embedding: Optional[list[float]] = None # 1024-dim vector
# Parser metadata
parse_notes: list[str] = field(default_factory=list) # warnings / edge cases
# ββ Computed properties ββββββββββββββββββββββββββββββββββββββββββββββββ
@property
def article_uid(self) -> Optional[str]:
"""Convenience accessor used by cross_reference/."""
return self.uid if self.hierarchy_type == HierarchyType.DIEU else None
@dataclass
class ParseResult:
"""
Full output for one document from LegalDocumentParser.
Consumed by:
- SegmentWriter (T1.5) β Neo4j ingest
- ArticleEmbedder (T1.6) β embedding generation
- ConfidenceScorer (T1.2) β quality check
- cross_reference/extractor.py (T2.1-T2.3) β reference extraction
"""
doc_id: str
segments: list[Segment] = field(default_factory=list)
# Confidence (set by ConfidenceScorer after parsing)
confidence_score: float = 0.0
confidence_level: ConfidenceLevel = ConfidenceLevel.LOW
confidence_notes: list[str] = field(default_factory=list)
# Parse statistics
chapter_count: int = 0
article_count: int = 0
clause_count: int = 0
point_count: int = 0
parse_errors: list[str] = field(default_factory=list)
# ββ Convenience accessors ββββββββββββββββββββββββββββββββββββββββββββββ
def articles(self) -> list[Segment]:
return [s for s in self.segments if s.hierarchy_type == HierarchyType.DIEU]
def clauses_of(self, article_uid: str) -> list[Segment]:
return [s for s in self.segments
if s.hierarchy_type == HierarchyType.KHOAN
and s.parent_uid == article_uid]
def points_of(self, clause_uid: str) -> list[Segment]:
return [s for s in self.segments
if s.hierarchy_type == HierarchyType.DIEM
and s.parent_uid == clause_uid]
|