"""`usage_en` 이 **무엇을 이어받으라** 하나 (2026-08-27, 감사 1-E).

## 앞선 실측이 가리킨 자리

프롬프트 안 절 사이 충돌 실측(120쌍): 동시 등장 57%인데 **실제 충돌은 16%**,
그리고 **충돌의 74%가 `PREVIOUS STILL USAGE`** 였다. 전역 우선순위 줄을
넣는 안은 기각했다 — 충돌 없는 84% 를 건드리면서 19건 중 14건만 닫는다.

## 이번에 찾은 것 — **팩 안에서 자기모순**

`prompts/_base/shot_ref_classify/4.*/prev_system.md`

    (a) prev 를 **고를 때**
        「인물의 의상·자세 일관성은 **별도 장치가 담당하므로 고려하지
          마라**」

    (b) `usage_en` 을 **쓸 때**
        「TAKE: … **움직일 수 없는 인물의 자세·위치, 인물의 의상**」

그 「별도 장치」가 실제로 있다 — `still_recipe.build_prev_people_rule`
(`prev_people_rule_shared` / `prev_people_rule_none` 스템). 즉 같은 것을
**두 곳이 말한다**(1-H 의 마감 이중 소유와 같은 모양).

★그리고 `usage_en` 이 저작될 때 **이 샷의 카메라·구도를 모른다** —
 `shot_ref_classify` 의 `depends_on` 에 `shot_staging` 이 없다. 모르는 채로
 「자세·위치를 이어받아라」라고 쓰면 그건 추측이고, staging 의
 `FRAMING SCALE`·`FRAME LAYOUT` 과 정면으로 부딪친다.

## 이 도구가 재는 것

체크포인트의 `usage_en` 전수에서 **인물 축**(자세·위치·의상)과
**장소 축**(룩·재질·조명)이 각각 얼마나 나오는지. 무료.

★낱말표는 **바닥값**이다 — 정확한 비율은 글자로 못 센다. 여기서 정하는 것은
 「이 축이 실재하는가」이지 비율의 정밀값이 아니다.

    .venv/bin/python tools/prompt_measure/audit_prev_usage_scope.py
"""
from __future__ import annotations

import json
import pathlib
import re
import sys

ROOT = pathlib.Path(__file__).resolve().parents[3]

# 인물 축 — `prev_people_rule` 과 staging 이 담당하는 것
_PERSON = re.compile(
    r"\b(pose|posture|stance|gesture|position of (the )?(man|woman|person|"
    r"figure)|body position|standing|seated|sitting|kneeling|crouch\w*|"
    r"outfit|clothing|clothes|garment|jacket|coat|shirt|dress|uniform|"
    r"wardrobe|same person|the man|the woman|her |his )\b", re.I)
# 카메라 축 — staging 이 소유
_CAMERA = re.compile(
    r"\b(angle|framing|composition|camera|viewpoint|point of view|"
    r"crop|close-?up|wide shot|distance from)\b", re.I)
# 장소 축 — 이어받아도 안전한 것
_PLACE = re.compile(
    r"\b(wall|floor|ceiling|room|street|texture|material|surface|"
    r"palette|lighting|light|lamp|window|paint|tile|wood|concrete|"
    r"same (place|location|space|background))\b", re.I)


def main() -> None:
    vals: list[str] = []
    for cp in sorted((ROOT / "projects").glob(
            "*/checkpoints/episodes/*/shot_ref_classify/manifest*.json")):
        try:
            d = json.loads(cp.read_text(encoding="utf-8"))
        except Exception:
            continue
        for sh in _walk(d):
            v = str(sh.get("usage_en") or "").strip()
            if v:
                vals.append(v)

    if not vals:
        print("  `usage_en` 을 못 찾았다 — 경로/모양을 먼저 볼 것")
        return

    n = len(vals)
    person = sum(1 for v in vals if _PERSON.search(v))
    camera = sum(1 for v in vals if _CAMERA.search(v))
    place = sum(1 for v in vals if _PLACE.search(v))
    both = sum(1 for v in vals if _PERSON.search(v) and _PLACE.search(v))
    lens = [len(v) for v in vals]

    print(f"■ `usage_en` 실측 — {n:,} 칸")
    print(f"  평균 {sum(lens)//n}자 · 최장 {max(lens)}자\n")
    print(f"  ★인물 축(자세·위치·의상)  {person:5d}  ({person*100//n}%)"
          "   ← `prev_people_rule` 이 이미 담당")
    print(f"  ★카메라 축(각도·구도)     {camera:5d}  ({camera*100//n}%)"
          "   ← staging 이 소유, 저작 때 **모른다**")
    print(f"   장소 축(룩·재질·조명)    {place:5d}  ({place*100//n}%)"
          "   ← 이어받아도 안전")
    print(f"   인물+장소 함께           {both:5d}  ({both*100//n}%)")

    print("\n  ── 인물 축이 든 표본 ──")
    shown = 0
    for v in vals:
        if _PERSON.search(v):
            print(f"   · {v[:150]}")
            shown += 1
            if shown >= 6:
                break
    print("\n  ── 카메라 축이 든 표본 (staging 과 부딪히는 자리) ──")
    shown = 0
    for v in vals:
        if _CAMERA.search(v):
            print(f"   · {v[:150]}")
            shown += 1
            if shown >= 5:
                break

    print("\n★낱말표는 **바닥값**이다. 여기서 정한 것은 「이 축이 실재하는가」"
          "이지\n 비율의 정밀값이 아니다 — 의미 판단은 LLM 몫이다.")


def _walk(d):
    if isinstance(d, dict):
        if "usage_en" in d:
            yield d
        for v in d.values():
            yield from _walk(v)
    elif isinstance(d, list):
        for v in d:
            yield from _walk(v)


if __name__ == "__main__":
    sys.exit(main())
