OPEN SOURCE
software/scripts/verify_aligned_release.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1#!/usr/bin/env python32"""Verify exported reference coverage, repair texts and provenance offline."""3import argparse4import hashlib5import json6from pathlib import Path7import re8import sys910ROOT = Path(__file__).resolve().parents[1]11sys.path.insert(0, str(ROOT))12from src.output.originals import load_site13from src.output.alignment import read_output141516def unique_object(pairs):17result = {}18for key, value in pairs:19if key in result:20raise ValueError(f'Duplicate JSON key: {key}')21result[key] = value22return result232425def read(path):26return json.loads(path.read_text(), object_pairs_hook=unique_object)272829def sha(path):30return hashlib.sha256(path.read_bytes()).hexdigest()313233def verify(release, audit_path, bundle_path, source):34manifest, audit, bundle = map(read, (release / 'manifest.json', audit_path, bundle_path))35assert manifest['audit_sha256'] == sha(audit_path), 'Audit hash mismatch'36assert audit['numbering_ready'], 'Unresolved numbering audit'37assert not any(audit[k] for k in ('missing_passages', 'conflicts', 'held_records'))38raw, files = read_output(source)39assert files == audit['input_files'], 'Raw source changed'40site, codes = load_site()41reverse = {code: book for book, code in codes.items()}42actual, headings = {}, 043assert set(manifest['files']) == {code + '.json' for code in codes.values()}44assert {p.name for p in release.iterdir()} == set(manifest['files']) | {'manifest.json'}45for filename, expected in manifest['files'].items():46path = release / filename47assert sha(path) == expected, f'Asset hash mismatch: {filename}'48book = read(path)49assert book['translation'] == '日常訳第1版' and book['schema'] == 150assert book['book'] == path.stem51for ch, chapter in book['chapters'].items():52headings += bool(chapter['heading'])53for verse, text in chapter['verses'].items():54key = (reverse[book['book']], int(ch), int(verse))55assert int(verse) > 0 and key not in actual56assert text.strip() and not re.search(r'[\x00-\x1f\x7f<>\ufffd\u0370-\u03ff\u0590-\u05ff]|```', text), key57actual[key] = text58assert actual.keys() == site.keys(), 'Missing or extra release references'59for key, text in actual.items():60ref = ':'.join(map(str, key))61expected = audit['aligned_changes'].get(ref, {}).get('text', raw.get(ref))62assert text == expected, f'Export differs from approved alignment: {ref}'63for decision in bundle['decisions']:64book, ch, verse = decision['target'].split(':')65assert actual[(book, int(ch), int(verse))] == decision['text'], decision['target']66counts = {'books': len({k[0] for k in actual}), 'chapters': len({k[:2] for k in actual}), 'verses': len(actual)}67assert all(manifest[k] == value for k, value in counts.items())68return {'status': 'pass', **counts, 'heading_blocks': headings, 'verified_repairs': len(bundle['decisions']),69'audit_sha256': sha(audit_path), 'bundle_sha256': sha(bundle_path), 'manifest_sha256': sha(release / 'manifest.json')}707172if __name__ == '__main__':73parser = argparse.ArgumentParser(description=__doc__)74parser.add_argument('--release', type=Path, default=ROOT / 'reports/releases/nichijo-v1')75parser.add_argument('--audit', type=Path, default=ROOT / 'reports/nichijo-v1-numbering-alignment.json')76parser.add_argument('--bundle', type=Path, default=ROOT / 'reports/numbering-repairs/repair-bundle.json')77parser.add_argument('--source', type=Path, default=ROOT / 'data/output')78parser.add_argument('--report', type=Path)79args = parser.parse_args()80try:81result = verify(args.release, args.audit, args.bundle, args.source)82except (AssertionError, ValueError, KeyError, OSError) as exc:83print(f'Release verification failed: {exc}', file=sys.stderr)84sys.exit(1)85output = json.dumps(result, ensure_ascii=False, indent=2) + '\n'86if args.report:87args.report.write_text(output)88print(output, end='')89