Starting fresh.")
 return False

 async def save(self):
 async with self._lock:
 self.data["last_updated"] = datetime.now(timezone.utc).isoformat()
 async with aiofiles.open(self.path, "w", encoding="utf-8") as f:
 await f.write(json.dumps(self.data, indent=2, ensure_ascii=False))

 async def mark_section_done(self, section_id: str, n_pairs: int, law_name: str):
 async with self._lock:
 if section_id not in self.data["completed_section_ids"]:
 self.data["completed_section_ids"].append(section_id)
 self.data["total_pairs_generated"] += n_pairs
 self.data["pairs_per_law"][law_name] = (
 self.data["pairs_per_law"].get(law_name, 0) + n_pairs
 )

 async def mark_section_failed(self, section_id: str):
 async with self._lock:
 if section_id not in self.data["failed_section_ids"]:
 self.data["failed_section_ids"].append(section_id)

 def is_section_done(self, section_id: str) -> bool:
 return section_id in self.data["completed_section_ids"]

 @property
 def total_pairs(self) -> int:
 return self.data["total_pairs_generated"]

═══════════════════════════════════════════════════════
LEGAL-AWARE SECTION CHUNKER — unchanged from V3
═══════════════════════════════════════════════════════

_PAT_ARTICLE_EN = re.compile(r"^(Article\s+\d+)\.", re.MULTILINE)
_PAT_SECTION_EN = re.compile(r"^(Section\s+\d+)\s:", re.MULTILINE)
_PAT_DHARA_NE = re.compile(r"^(धारा\s+[०-९\d]+)\s:", re.MULTILINE)
_PAT_DAFA_NE = re.compile(r"^(दफा\s+[०-९\d]+)\s:", re.MULTILINE)
_SECTION_START_PATTERNS = [_PAT_ARTICLE_EN, _PAT_SECTION_EN, _PAT_DHARA_NE, _PAT_DAFA_NE]

_PART_PAT_EN = re.compile(r"^(Part[\s\-][\d]+)\s:\s", re.MULTILINE re.IGNORECASE)
_CHAPTER_PAT_EN = re.compile(r"^(Chapter[\s\-][\d]+)\s:\s", re.MULTILINE re.IGNORECASE)
_PART_PAT_NE = re.compile(r"^(भाग\s[०-९\d]+)\s:\s", re.MULTILINE)
_CHAPTER_PAT_NE = re.compile(r"^(परिच्छेद\s[०-९\d]+)\s:\s", re.MULTILINE)

_DEV = {"०":"0","१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9"}

def _dev_to_int(s):
 try:
 return int("".join(_DEV.get(c, c) for c in s))
 except ValueError:
 return None

def _extract_section_num(section_id):
 m = re.search(r"(\d+)", section_id)
 if m:
 return int(m.group(1))
 m = re.search(r"([०-९]+)", section_id)
 if m:
 return _dev_to_int(m.group(1))
 return None

def detect_language(text):
 devanagari = sum(1 for c in text if '\u0900' 0.15 else "en"

def _find_section_boundaries(text):
 raw = []
 for pat in _SECTION_START_PATTERNS:
 for m in pat.finditer(text):
 raw.append((m.start(), m.end(), m.group(1).strip()))
 raw.sort(key=lambda x: x[0])

 filtered = []
 for pos, end, section_id in raw:
 line_start = text.rfind("\n", 0, pos)
 line_start = line_start + 1 if line_start != -1 else 0
 line_end = text.find("\n", end)
 if line_end == -1:
 line_end = len(text)
 full_line = text[line_start:line_end]
 if re.search(r",\s(Sub-section Clause उपदफा खण्ड उपधारा)\s\(", full_line):
 continue
 rest_of_line = text[end:line_end]
 is_cross_ref = any(p.search(rest_of_line) for p in _SECTION_START_PATTERNS)
 if not is_cross_ref:
 rest_stripped = rest_of_line.lstrip()
 if rest_stripped and rest_stripped[0] == "(":
 is_cross_ref = True
 if not is_cross_ref:
 filtered.append((pos, section_id))

 seen, deduped = set(), []
 for pos, section_id in filtered:
 if section_id not in seen:
 seen.add(section_id)
 deduped.append((pos, section_id))
 return deduped

def _structural_context(text, position):
 ctx = {"part": "", "chapter": ""}
 for pat in (_PART_PAT_EN, _PART_PAT_NE):
 for m in pat.finditer(text):
 if m.start() = 2:
 break
 if title_parts:
 return " ".join(title_parts)
 return Path(source_file).stem if source_file else "Unknown Document"

def chunk_legal_text(text, law_name, source_file, cfg):
 language = detect_language(text)
 doc_title = _extract_doc_title(text, source_file)
 boundaries = _find_section_boundaries(text)

 if not boundaries:
 return _fallback_paragraph_chunk(text, law_name, language, doc_title, source_file)

 chunks = []
 if boundaries[0][0] > 0:
 preamble = text[:boundaries[0][0]].strip()
 if len(preamble) > 50:
 chunks.append({
 "law_name": law_name, "text": preamble, "language": language,
 "section_id": "Preamble", "chunk_id": f"{law_name}__Preamble",
 "doc_title": doc_title, "source_file": source_file,
 "section_num": 0, "part": "", "chapter": "", "chunk_type": "preamble",
 })

 for i, (pos, section_id) in enumerate(boundaries):
 end_pos = boundaries[i + 1][0] if i + 1 blockquotes for quotes or legal terms.