"""Subset-only lexical ambiguity quarantine after a complete C1 data audit. Retained sequences/masks are copied unchanged from a fully checked parent. This inherits its span/codec/isolation checks without repeating all annotations. """ from collections import Counter, defaultdict import hashlib import json from pathlib import Path import random import shutil import struct import sys import time ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "src")) import fugashi from vimeml.posttraining.data import ConversionDataset from vimeml.posttraining.readings import KANJI from vimeml.training.data import file_sha, write_json def main(): directory = ROOT / "artifacts/posttrain-data/v4-c1" manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8")) if manifest.get("lexical_ambiguity_refinement"): print("Lexical refinement already complete; preserving frozen data", flush=True) return acceptance = json.loads((directory / "acceptance.json").read_text(encoding="utf-8")) assert acceptance["status"] == "pass" and acceptance["manifest_sha256"] == file_sha(directory / "manifest.json") assert acceptance["reader_code_sha256"] == file_sha(ROOT / "src/vimeml/posttraining/data.py") for name, expected in manifest["output_sha256"].items(): assert file_sha(directory / name) == expected, name for name in ("manifest.json", "acceptance.json", "stratified-review-200.json"): shutil.copyfile(directory / name, directory / ("pre-lexical-" + name)) began = time.monotonic() tagger = fugashi.Tagger() staging = directory / "lexical-staging" staging.mkdir(exist_ok=True) stats_by_split, hist_by_split = {}, {} buckets, seen, rng = defaultdict(list), Counter(), random.Random(42) for split in ("train", "validation"): data = ConversionDataset(directory, split) stats, hist = Counter(), defaultdict(Counter) with (directory / f"{split}.records.jsonl").open(encoding="utf-8") as source, \ (ROOT / f"outputs/corpus-fast-v1/{split}.txt").open("rb") as original, \ (staging / f"{split}.records.jsonl").open("w", encoding="utf-8", newline="\n") as records, \ (staging / f"{split}.tokens.bin").open("wb") as tokens, \ (staging / f"{split}.offsets.bin").open("wb") as offsets, \ (staging / f"{split}.out-start.bin").open("wb") as starts, \ (staging / f"{split}.reading-dropout.bin").open("wb") as dropout, \ (directory / f"{split}.lexical-quarantine.jsonl").open("w", encoding="utf-8", newline="\n") as rejected: offsets.write(struct.pack(" 0 and KANJI.fullmatch(text[start - 1])) or (end < len(text) and KANJI.fullmatch(text[end])))): issue = {"reason": "single_kanji_noun_adjacent_to_kanji", "surface": word.surface, "raw_kana": word.feature.kana, "span": [start, end]} break if issue: rejected.write(json.dumps({"issue": issue, **ex}, ensure_ascii=False, separators=(",", ":")) + "\n") stats["excluded_single_kanji_compound_boundary"] += 1 continue ids = data.ids[int(data.offsets[index]):int(data.offsets[index + 1])].tobytes() first = int(data.first[index]) flag = int(data.dropout[index]) assert first == ex["first_output"] and bool(flag) == ex["reading_dropout_view"] # No reencoding, label edits, new samples, or reordering. records.write(raw) tokens.write(ids) starts.write(bytes([first])) dropout.write(bytes([flag])) length = len(ids) // 2 stats["examples"] += 1 stats["stored_tokens"] += length stats["supervised_tokens_full_reading"] += length - first stats["context_cropped"] += bool(ex["context_crop_characters"]) stats["kana_only"] += not ex["has_kanji"] stats["reading_dropout_view"] += flag offsets.write(struct.pack(" limit: length = int(old_offsets[i + 1] - old_offsets[i]) stats["examples"] -= 1 stats["stored_tokens"] -= length stats["supervised_tokens_full_reading"] -= length - int(old_first[i]) stats["context_cropped"] -= bool(ex["context_crop_characters"]) stats["kana_only"] -= 1 stats["reading_dropout_view"] -= int(old_drop[i]) for name, value in (("reading_characters", len(ex["reading"])), ("output_characters", len(ex["output"])), ("context_characters", len(ex["context"])), ("source_id", ex["source_id"]), ("serialized_tokens", length)): hist_by_split[split][name][value] -= 1 continue encoded = old_ids[int(old_offsets[i]):int(old_offsets[i + 1])].tobytes() tokens.write(encoded) offset += len(encoded) // 2 offsets.write(struct.pack("