| """Math Ink 0.6 ๋ฐ์ดํฐ ์ถ์ฒ์ ๋ฐ๊ฒฌยท๊ถ๋ฆฌยท์ค๋ณตยทํ์ต ์ค๋น ๋จ๊ณ๋ฅผ ๊ฒ์ฆํ๋ค.""" |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| import json |
| from pathlib import Path |
| from typing import Sequence |
|
|
|
|
| SOURCE_STAGES = ("discovered", "rights_review", "deduplicated", "approved") |
| TRAINING_ROLES = ( |
| "supervised_symbol", "geometry_pretrain", "raster_verifier", "raster_pseudo_stroke", "evaluation_only", |
| ) |
| VOCABULARY_POLICIES = ( |
| "exact_378", "intersection_378", "digits_latin_intersection", "geometry_only", "evaluation_only", |
| ) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class SourceRegistryEntry06: |
| """ํ์ ๋ณ์: ์ถ์ฒ ๊ถ๋ฆฌยท์ค๋ณตยท๋ก์ปฌ ์ํ. ์๋ ์๋ฆฌ: ๋ฐฐํฌ checkpoint ์ ์
์ฌ๋ถ๋ฅผ ๋ช
์์ ์ธ ํ ํ์ผ๋ก ํํํ๋ค.""" |
|
|
| source_id: str |
| stage: str |
| official_url: str |
| license_id: str | None |
| commercial_allowed: bool | None |
| allowed_tracks: tuple[str, ...] |
| independent_source_group: str | None |
| deployment_role: str | None |
| vocabulary_policy: str | None |
| local_materialized: bool |
| notes: str |
|
|
|
|
| def load_source_registry06(path: Path) -> tuple[SourceRegistryEntry06, ...]: |
| """ํ์ ๋ณ์: UTF-8 source registry ๊ฒฝ๋ก. ์๋ ์๋ฆฌ: fail-closed schema ๊ฒ์ฆ ํ ๋ถ๋ณ entry ๋ฌถ์์ ๋ฐํํ๋ค.""" |
|
|
| payload = json.loads(path.read_text(encoding="utf-8")) |
| if payload.get("schema_version") != "0.6": |
| raise ValueError("Math Ink source registry schema_version์ 0.6์ด์ด์ผ ํฉ๋๋ค.") |
| raw_sources = payload.get("sources") |
| if not isinstance(raw_sources, list): |
| raise ValueError("Math Ink source registry sources๊ฐ ๋ฐฐ์ด์ด ์๋๋๋ค.") |
| entries: list[SourceRegistryEntry06] = [] |
| seen: set[str] = set() |
| for raw in raw_sources: |
| source_id = str(raw.get("source_id") or "").strip() |
| if not source_id or source_id in seen: |
| raise ValueError(f"source_id๊ฐ ๋น์๊ฑฐ๋ ์ค๋ณต์
๋๋ค: {source_id!r}") |
| seen.add(source_id) |
| stage = str(raw.get("stage") or "") |
| if stage not in SOURCE_STAGES: |
| raise ValueError(f"{source_id}์ stage๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค: {stage}") |
| official_url = str(raw.get("official_url") or "") |
| if not official_url.startswith("https://"): |
| raise ValueError(f"{source_id}์ ๊ณต์ HTTPS URL์ด ์์ต๋๋ค.") |
| license_id = raw.get("license_id") |
| commercial_allowed = raw.get("commercial_allowed") |
| group = raw.get("independent_source_group") |
| role = raw.get("deployment_role") |
| vocabulary_policy = raw.get("vocabulary_policy") |
| if SOURCE_STAGES.index(stage) >= SOURCE_STAGES.index("rights_review"): |
| if not license_id or not isinstance(commercial_allowed, bool): |
| raise ValueError(f"{source_id}์ ๊ถ๋ฆฌ ๊ฒํ ๊ฒฐ๊ณผ๊ฐ ๋ถ์์ ํฉ๋๋ค.") |
| if SOURCE_STAGES.index(stage) >= SOURCE_STAGES.index("deduplicated") and not group: |
| raise ValueError(f"{source_id}์ independent_source_group์ด ์์ต๋๋ค.") |
| if stage == "approved": |
| if commercial_allowed is not True or "P" not in raw.get("allowed_tracks", []): |
| raise ValueError(f"{source_id}๋ ์์ฉ P-track ์น์ธ ์กฐ๊ฑด์ ์ถฉ์กฑํ์ง ๋ชปํ์ต๋๋ค.") |
| if role not in TRAINING_ROLES or vocabulary_policy not in VOCABULARY_POLICIES: |
| raise ValueError(f"{source_id}์ ๋ฐฐํฌ ์ญํ ๋๋ vocabulary ์ ์ฑ
์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.") |
| if not bool(raw.get("local_materialized", False)): |
| raise ValueError(f"{source_id}๋ content dedup ์ ์ด๋ผ approved๊ฐ ๋ ์ ์์ต๋๋ค.") |
| entries.append(SourceRegistryEntry06( |
| source_id=source_id, stage=stage, official_url=official_url, |
| license_id=str(license_id) if license_id else None, |
| commercial_allowed=commercial_allowed if isinstance(commercial_allowed, bool) else None, |
| allowed_tracks=tuple(str(value) for value in raw.get("allowed_tracks", [])), |
| independent_source_group=str(group) if group else None, |
| deployment_role=str(role) if role else None, |
| vocabulary_policy=str(vocabulary_policy) if vocabulary_policy else None, |
| local_materialized=bool(raw.get("local_materialized", False)), notes=str(raw.get("notes") or ""), |
| )) |
| return tuple(entries) |
|
|
|
|
| def approved_training_source_ids06( |
| entries: Sequence[SourceRegistryEntry06], *, role: str = "supervised_symbol", |
| ) -> tuple[str, ...]: |
| """ํ์ ๋ณ์: ๊ฒ์ฆ๋ entryยทํ์ต ์ญํ . ์๋ ์๋ฆฌ: ์น์ธยท์์ฉยท๋ก์ปฌ ์ค๋น ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑํ ์ถ์ฒ๋ง ๋ฐํํ๋ค.""" |
|
|
| if role not in TRAINING_ROLES: |
| raise ValueError(f"์ง์ํ์ง ์๋ deployment role์
๋๋ค: {role}") |
| return tuple(sorted( |
| entry.source_id for entry in entries |
| if entry.stage == "approved" and entry.commercial_allowed is True and "P" in entry.allowed_tracks |
| and entry.local_materialized and entry.deployment_role == role |
| )) |
|
|
|
|
| def source_registry_audit06( |
| entries: Sequence[SourceRegistryEntry06], |
| *, |
| required_discovered_sources: int = 200, |
| required_approved_groups: int = 30, |
| ) -> dict: |
| """ํ์ ๋ณ์: ๊ฒ์ฆ๋ ์ถ์ฒยท๋ชฉํ ์. ์๋ ์๋ฆฌ: ์กฐ์ฌ ์์ ๋ฏธ๋ฌ ์ ๊ฑฐ ๋
๋ฆฝ ๋ฐฐํฌ ๊ทธ๋ฃน์ ๋ณ๋ hard gate๋ก ๊ณ์ฐํ๋ค.""" |
|
|
| approved = [entry for entry in entries if entry.stage == "approved"] |
| approved_deployment = [ |
| entry for entry in approved |
| if entry.deployment_role != "evaluation_only" |
| ] |
| rights_cleared = [ |
| entry for entry in entries |
| if entry.commercial_allowed is True and entry.license_id is not None |
| ] |
| groups = {entry.independent_source_group for entry in approved if entry.independent_source_group} |
| deployment_groups = { |
| entry.independent_source_group |
| for entry in approved_deployment |
| if entry.independent_source_group |
| } |
| group_members: dict[str, list[str]] = {} |
| for entry in entries: |
| if entry.independent_source_group: |
| group_members.setdefault(entry.independent_source_group, []).append(entry.source_id) |
| ready = approved_training_source_ids06(entries) |
| materialized_by_role = { |
| role: sorted(entry.source_id for entry in approved if entry.local_materialized and entry.deployment_role == role) |
| for role in TRAINING_ROLES |
| } |
| return { |
| "discovered_sources": len(entries), |
| "required_discovered_sources": required_discovered_sources, |
| "discovery_gate_passed": len(entries) >= required_discovered_sources, |
| "stage_counts": {stage: sum(entry.stage == stage for entry in entries) for stage in SOURCE_STAGES}, |
| "approved_sources": len(approved), |
| "approved_independent_groups": len(groups), |
| "approved_deployment_sources": len(approved_deployment), |
| "approved_deployment_independent_groups": len(deployment_groups), |
| "required_approved_independent_groups": required_approved_groups, |
| "approved_group_gate_passed": len(deployment_groups) >= required_approved_groups, |
| "source_release_gate_passed": ( |
| len(entries) >= required_discovered_sources |
| and len(deployment_groups) >= required_approved_groups |
| ), |
| "shared_independent_groups": { |
| group: sorted(members) |
| for group, members in sorted(group_members.items()) |
| if len(members) > 1 |
| }, |
| "rights_cleared_sources": sorted(entry.source_id for entry in rights_cleared), |
| "rights_cleared_source_count": len(rights_cleared), |
| "rights_cleared_pending_materialization": sorted( |
| entry.source_id for entry in rights_cleared if entry.stage != "approved" |
| ), |
| "materialized_supervised_sources": list(ready), |
| "materialized_supervised_source_count": len(ready), |
| "materialized_by_role": materialized_by_role, |
| } |
|
|