"""Full storage/span/split/codec audit, plus a stratified machine-review packet.""" import argparse from collections import Counter, defaultdict import hashlib import json from pathlib import Path import random import pickle import struct import sys import time ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "src")) import numpy as np from vimeml.posttraining.data import ConversionCodec, ConversionDataset, shifted from vimeml.posttraining.readings import DualReading, HIRAGANA from vimeml.training.data import file_sha, write_json def quarantine_known_ambiguity(directory): """Never repair an inferred reading. Remove known shared dictionary errors. Standalone 家 tagged ヤ/ケ can be a family suffix, but also mislabels the ordinary house noun in unpunctuated web sentences. Quarantine both rather than guessing from a pseudo-label. Compounds such as 家族 and readings カ are unaffected. No benchmark observations determine this train-review rule. """ manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8")) if manifest.get("quality_refinement", {}).get("version") == 2: return if not (directory / "annotation-manifest.json").exists(): write_json(directory / "annotation-manifest.json", manifest) prior_refinement = manifest.get("quality_refinement") previous_manifest_sha = file_sha(directory / "manifest.json") if prior_refinement: write_json(directory / "refinement-v1-manifest.json", manifest) for split in ("train", "validation"): previous = directory / f"{split}.quality-quarantine.jsonl" previous.rename(directory / f"{split}.quality-v1-quarantine.jsonl") validation_data = ConversionDataset(directory, "validation") validation_sequences = {hashlib.sha256(validation_data.ids[int(validation_data.offsets[i]):int(validation_data.offsets[i + 1])].tobytes()).digest() for i in range(len(validation_data))} validation_data.close() with (directory / "validation.records.jsonl").open(encoding="utf-8") as records: validation_cases = {json.loads(raw)["case_sha256"] for raw in records} reader = DualReading() excluded = {} retained_kanji = {} for split in ("train", "validation"): bad = {} kanji = 0 data = ConversionDataset(directory, split) with (ROOT / f"outputs/corpus-fast-v1/{split}.txt").open("rb") as original, \ (directory / f"{split}.records.jsonl").open(encoding="utf-8") as records: for index, raw in enumerate(records): ex = json.loads(raw) if split == "train": sequence = data.ids[int(data.offsets[index]):int(data.offsets[index + 1])].tobytes() if ex["case_sha256"] in validation_cases or hashlib.sha256(sequence).digest() in validation_sequences: bad[index] = "exact_conversion_validation_case" continue ambiguous = False if "家" in ex["output"]: original.seek(ex["byte_offset"]) text = original.readline().decode("utf-8").rstrip("\r\n") cursor = 0 for word in reader.mecab(text): start = cursor + len(word.white_space) end = start + len(word.surface) cursor = end if ex["span"][0] <= start and end <= ex["span"][1] and word.surface == "家" and word.feature.kana in ("ヤ", "ケ"): ambiguous = True break if ambiguous: bad[index] = "standalone_house_family_suffix_ambiguity" else: kanji += ex["has_kanji"] excluded[split], retained_kanji[split] = bad, kanji data.close() staging = directory / "quality-staging" staging.mkdir(exist_ok=True) new_stats = {} removed_counts = {} for split in ("train", "validation"): data = ConversionDataset(directory, split) stats, hist = Counter(), defaultdict(Counter) kana_limit = retained_kanji[split] // 4 with (directory / f"{split}.records.jsonl").open(encoding="utf-8") as source, \ (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}.quality-quarantine.jsonl").open("w", encoding="utf-8", newline="\n") as rejected: offsets.write(struct.pack("= kana_limit: reason = "retain_kana_only_cap_after_quarantine" if reason: rejected.write(json.dumps({"reason": reason, **ex}, ensure_ascii=False) + "\n") stats["excluded_" + reason] += 1 continue ids = data.ids[int(data.offsets[index]):int(data.offsets[index + 1])].tolist() records.write(raw) tokens.write(struct.pack(f"<{len(ids)}H", *ids)) starts.write(bytes([ex["first_output"]])) dropout.write(bytes([ex["reading_dropout_view"]])) stats["examples"] += 1 stats["stored_tokens"] += len(ids) stats["supervised_tokens_full_reading"] += len(ids) - ex["first_output"] stats["context_cropped"] += bool(ex["context_crop_characters"]) stats["kana_only"] += not ex["has_kanji"] stats["reading_dropout_view"] += ex["reading_dropout_view"] offsets.write(struct.pack("