# -*- coding: utf-8 -*-
"""분석표(raw.csv) → 수치대장(수치대장.csv).

⛔ 손으로 옮겨 적지 않는다. 대장의 `파생식` 은 `raw:<열>:<키>`(분석표에서 그대로) 또는
   `expr:`(대장 안에서 다시 계산) 둘 중 하나다. 분석표의 `키` 열이 유일하므로
   관문 C2 가 분석표를 다시 읽어 대조한다.

★ 몫은 반드시 `expr:` 로 낸다 〔관문 C9〕
   「시군내몫」은 비율이므로 «무엇으로 나눴는지»가 대장에서 추적돼야 한다.
   그래서 몫마다 시군간·시군내 두 성분을 대장에 함께 올리고
   `expr:100*내/(간+내)` 로 적는다 — 그러면 C2 가 나눗셈까지 다시 계산한다.

⚠ 자릿수를 정하는 규칙 〔2026-08-30〕
   `expr:` 는 **대장에 저장된 값**으로 계산한다. 그래서 성분을 너무 거칠게 반올림하면
   재계산 결과가 허용오차(자릿수에서 나온다)를 넘는다. 성분은 4자리, 몫은 2자리로 둔다.
"""
import csv, sys
from pathlib import Path

sys.stdout.reconfigure(encoding="utf-8")
HERE = Path(__file__).resolve().parent
RAW = HERE / "raw.csv"
OUT = HERE.parent / "수치대장.csv"

행 = {r["키"]: r for r in csv.DictReader(open(RAW, encoding="utf-8"))}
기관, 자료 = "통계청(KOSIS)", "행정구역(읍면동)별/5세별 주민등록인구(2011년~)"
URL = "https://kosis.kr/statHtml/statHtml.do?orgId=101&tblId=DT_1B04005N"
위치 = "OpenAPI A=41 하위 읍면동 · B=5세 연령 · itmId=T2"
분산비고 = ("분산은 비율이 아니라 «퍼짐의 크기»다 — 단위가 pp²(퍼센트포인트 제곱)이고 "
            "나눗셈으로 만든 값이 아니다. 무엇으로 나눴는지가 없으므로 분모가 없다.")

rows = []


def 넣기(id_, 지표, 대상, 값, 단위, 연도, 정의, 파생식, 분모="", 비고=""):
    rows.append({
        "id": id_, "구분": "계산", "지표": 지표, "대상": 대상, "값": 값, "단위": 단위,
        "기준일": f"{연도}-12-31", "정의": 정의, "출처기관": 기관, "자료명": 자료,
        "URL": URL, "원문확인": "Y", "원문위치": 위치, "파생식": 파생식,
        "출처식": "", "본문키워드": "", "무시목록": "",
        "비고": 비고 or "분석표(raw.csv)에서 기계로 옮김 — 손으로 적지 않는다",
        "분모": 분모,
    })


def 값(키, 열, dp=None):
    """⛔ `raw:` 로 대조할 값은 «분석표의 문자열 그대로» 써야 한다 〔2026-08-30 실측〕.
    `raw:` 는 원자료를 베끼는 식이라 허용오차가 0 이다 — 반올림하면 그 자리에서 FAIL 이다.
    dp 를 준 경우(=`expr:` 로 다시 계산할 값)만 자리를 줄인다."""
    if 키 not in 행:
        sys.exit(f"✖ 분석표에 키가 없습니다: {키}")
    v = 행[키][열]
    if v == "":
        sys.exit(f"✖ {키} 의 '{열}' 이 비어 있습니다")
    return v if dp is None else f"{round(float(v), dp):.{dp}f}"


# ── 1. 규모 ───────────────────────────────────────────────────────
넣기("N01", "읍면동수", "경기도 2025", 값("분해|2025|청년|인구", "단위수"), "개", 2025,
     "2025년 경기도에 값이 있는 행정동 수", "raw:단위수:분해|2025|청년|인구",
     "해당없음", "개수는 나눗셈이 아니다 — 분모가 없다.")
넣기("N02", "시군수", "경기도 2025", 값("분해|2025|청년|인구", "시군수"), "개", 2025,
     "기초자치단체 수. 행정동 코드 앞 5자리를 10으로 내림해 일반구를 시로 묶은 결과",
     "raw:시군수:분해|2025|청년|인구", "해당없음", "개수는 나눗셈이 아니다.")
넣기("N03", "읍면동수", "경기도 2011", 값("분해|2011|청년|인구", "단위수"), "개", 2011,
     "2011년 경기도에 값이 있는 행정동 수", "raw:단위수:분해|2011|청년|인구",
     "해당없음", "개수는 나눗셈이 아니다.")

# ── 2. 몫 — 성분 둘을 함께 올리고 expr 로 낸다 ──────────────────────
몫들 = [
    # (몫id, 간id, 내id, 키, 라벨, 연도)
    ("N04", "N06", "N07", "분해|2025|청년|인구", "2025 인구가중", 2025),
    ("N09", "N11", "N12", "분해|2025|노년|인구", "2025 인구가중", 2025),
    ("N05", "N30", "N31", "분해|2025|청년|동",   "2025 동1표", 2025),
    ("N10", "N32", "N33", "분해|2025|노년|동",   "2025 동1표", 2025),
    ("N14", "N34", "N35", "분해|2011|청년|인구", "2011 인구가중", 2011),
    ("N15", "N36", "N37", "분해|2011|노년|인구", "2011 인구가중", 2011),
    ("N25", "N38", "N39", "균형|2025|청년|인구", "2025 균형448", 2025),
    ("N26", "N40", "N41", "균형|2025|노년|인구", "2025 균형448", 2025),
    ("N27", "N42", "N43", "균형|2011|청년|인구", "2011 균형448", 2011),
]
for 몫id, 간id, 내id, 키, 라벨, 연도 in 몫들:
    지 = "청년" if "청년" in 키 else "노년"
    # ⛔ 2026-09-06 — 여기 정의 문자열 «하나»를 아홉 행이 나눠 쓰고 있었다. 그래서
    #   동 1표 행(N30·N32)에도 「시군 «인구»로 가중」이 붙었는데, prep.py 는
    #   `d["계"] if 가중=="인구" else 1` 이라 동 1표의 무게는 «읍면동 수»다.
    #   값은 맞고 «설명»이 틀렸다 — 외부 검수(codex)가 짚었다.
    #   ★ 설명을 공유하면 가중이 다른 계열에 «남의 설명»이 붙는다.
    동표 = 키.endswith("|동")
    무게말 = "그 시군의 읍면동 수(동마다 한 표)" if 동표 else "그 시군의 주민등록인구"
    넣기(간id, f"{지}비중 시군간분산", 라벨, 값(키, "시군간"), "pp²", 연도,
         f"시군 평균끼리의 분산({무게말}으로 가중)", f"raw:시군간:{키}", "해당없음", 분산비고)
    넣기(내id, f"{지}비중 시군내분산", 라벨, 값(키, "시군내"), "pp²", 연도,
         f"시군 안 동네들 사이의 분산({무게말}으로 가중해 합산)", f"raw:시군내:{키}",
         "해당없음", 분산비고)
    넣기(몫id, f"{지}비중 시군내몫", 라벨, 값(키, "시군내몫", 3), "%", 연도,
         "시군 내 분산 ÷ (시군 간 + 시군 내) × 100",
         f"expr:100*{내id}/({간id}+{내id})", f"{간id}+{내id}")

# ── 3. 분산·표준편차·평균 ─────────────────────────────────────────
넣기("N08", "청년비중 전체분산", "2025 인구가중", 값("분해|2025|청년|인구", "전체분산"),
     "pp²", 2025, "경기 전체 읍면동 청년비중의 인구가중 분산",
     "raw:전체분산:분해|2025|청년|인구", "해당없음", 분산비고)
넣기("N13", "노년비중 전체분산", "2025 인구가중", 값("분해|2025|노년|인구", "전체분산"),
     "pp²", 2025, "", "raw:전체분산:분해|2025|노년|인구", "해당없음", 분산비고)
넣기("N16", "노년비중 전체분산", "2011 인구가중", 값("분해|2011|노년|인구", "전체분산"),
     "pp²", 2011, "", "raw:전체분산:분해|2011|노년|인구", "해당없음", 분산비고)
넣기("N17", "노년비중 평균", "2011 인구가중", 값("분해|2011|노년|인구", "평균"),
     "%", 2011, "읍면동 노년비중의 인구가중 평균 = 경기도 전체 노년비중",
     "raw:평균:분해|2011|노년|인구", "N44")
넣기("N18", "노년비중 평균", "2025 인구가중", 값("분해|2025|노년|인구", "평균"),
     "%", 2025, "", "raw:평균:분해|2025|노년|인구", "N45")
넣기("N19", "노년비중 표준편차", "2025 인구가중", 값("분해|2025|노년|인구", "sd"),
     "pp", 2025, "전체분산의 제곱근", "raw:sd:분해|2025|노년|인구", "해당없음",
     "표준편차는 분산의 제곱근이지 비율이 아니다 — 분모가 없다.")
넣기("N20", "노년비중 표준편차", "2011 인구가중", 값("분해|2011|노년|인구", "sd"),
     "pp", 2011, "", "raw:sd:분해|2011|노년|인구", "해당없음",
     "표준편차는 분산의 제곱근이지 비율이 아니다 — 분모가 없다.")
넣기("N28", "청년비중 평균", "2011 인구가중", 값("분해|2011|청년|인구", "평균"),
     "%", 2011, "", "raw:평균:분해|2011|청년|인구", "N44")
넣기("N29", "청년비중 평균", "2025 인구가중", 값("분해|2025|청년|인구", "평균"),
     "%", 2025, "", "raw:평균:분해|2025|청년|인구", "N45")

# ── 4. 변이계수·로짓 — 「평균이 올라 분산이 커진 것」을 가르는 값 ─────
넣기("N21", "노년비중 변이계수", "2011 인구가중", 값("분해|2011|노년|인구", "cv"),
     "배", 2011, "표준편차 ÷ 평균. 평균 대비 흩어짐이라 평균이 올라도 저절로 커지지 않는다",
     "expr:N20/N17", "N17")
넣기("N22", "노년비중 변이계수", "2025 인구가중", 값("분해|2025|노년|인구", "cv"),
     "배", 2025, "", "expr:N19/N18", "N18")
# ⛔ 2026-09-06 — 로짓 분산에 «분산비고»(단위가 pp²)를 붙이고 있었다. 로짓 분산의 단위는
#   log-odds 의 제곱이라 «무차원»이고 pp² 가 아니다. 그리고 로짓 변환은 평균 이동의 효과를
#   «빼 주는» 연산이 아니다 — 비선형 변환이라 분산도 분포 위치에 영향을 받는다. (codex 발견9)
로짓분산비고 = ("⛔ 로짓 분산의 단위는 pp²(퍼센트포인트 제곱)가 «아니다» — log-odds 의 "
                "제곱이라 무차원이다. 나눗셈으로 만든 값이 아니므로 분모가 없다. "
                "〔2026-09-06 정정 — 「단위가 pp²」라고 적혀 있었다. 외부 검수 codex〕")
로짓정의 = ("log(p/(1−p)) 로 바꾼 뒤의 인구가중 분산. 비율의 경계(0~100)에 덜 묶이는 척도다. "
            "⛔ 평균 이동의 효과를 «빼 주는» 변환은 아니다")
넣기("N23", "노년비중 로짓분산", "2011 인구가중", 값("분해|2011|노년|인구", "로짓분산"),
     "", 2011, 로짓정의,
     "raw:로짓분산:분해|2011|노년|인구", "해당없음", 로짓분산비고)
넣기("N24", "노년비중 로짓분산", "2025 인구가중", 값("분해|2025|노년|인구", "로짓분산"),
     "", 2025, "위와 같다", "raw:로짓분산:분해|2025|노년|인구", "해당없음", 로짓분산비고)

# ── 5. 비중의 분모 — 「무엇의 몇 %인가」 ────────────────────────────
# ⛔ 2026-09-06 — 이 두 행의 값이 «0» 이었다. 그런데 N17·N18·N28·N29(인구가중 평균)가
#   분모로 이 둘을 가리킨다 — 즉 「분모가 0」이라고 적어 놓고 통과하고 있었다.
#   외부 검수(codex)가 짚었다: 「동마다 비중의 분모가 다른 것」과 「가중평균의 가중치 합이
#   없는 것」은 다른 말이다. 가중치 합은 «하나의 수»이고 분석표의 `무게` 열이 바로 그것이다.
#   → raw: 로 걸어 관문 C2 가 분석표와 대조하게 한다. 「없다」가 아니라 «값으로» 댄다.
분모비고 = ("인구가중 평균의 가중치 합이다. ⚠ 이 호의 비중은 «동마다» 그 동의 ‘계’로 나눈 "
            "값이고, 경기 전체 평균은 그 비중들을 이 합계로 가중한 것이다. "
            "⛔ 2026-09-06 정정 — 값이 0 으로 적혀 있었다(외부 검수 codex).")
넣기("N44", "인구", "경기도 2011 읍면동 합", 값("분해|2011|청년|인구", "무게"), "명", 2011,
     "인구가중 평균(N17·N28)의 분모 — 2011년 분석 대상 읍면동 ‘계’(연령 전체)의 합계",
     "raw:무게:분해|2011|청년|인구", "해당없음", 분모비고)
넣기("N45", "인구", "경기도 2025 읍면동 합", 값("분해|2025|청년|인구", "무게"), "명", 2025,
     "인구가중 평균(N18·N29)의 분모 — 2025년 분석 대상 읍면동 ‘계’의 합계",
     "raw:무게:분해|2025|청년|인구", "해당없음", 분모비고)

# ── 6. 본문이 쓰는 나머지 수치 — «미등록 수치»를 남기지 않는다 (관문 C4) ─────
import json as _json
이력 = _json.loads((HERE / "정제이력.json").read_text(encoding="utf-8"))
# ⚠ 원자료는 목록이다 〔2026-08-31〕 — 첫째가 측정값(읍면동 인구), 둘째가 코드북(시군 이름표).
_측정 = 이력["원자료"][0] if isinstance(이력["원자료"], list) else 이력["원자료"]
넣기("N46", "원자료 행수", "경기 읍면동 2011~2025", str(_측정["행수"]), "행", 2025,
     "KOSIS 에서 받은 원자료의 행 수. 정제이력.json 이 정본이다", "", "해당없음",
     "개수는 나눗셈이 아니다. 값의 근거는 analysis/정제이력.json 이며 관문 C12 가 대조한다.")
넣기("N47", "균형패널 동수", "2011~2025 내내 존재", 값("표본|2025|전체|인구", "단위수"), "개",
     2025, "15년 전 구간에 값이 있는 행정동 수", "raw:단위수:표본|2025|전체|인구",
     "해당없음", "개수는 나눗셈이 아니다.")
넣기("N48", "행정동 코드 수", "경기 2011~2025 합집합", 값("표본|2025|전체|인구", "시군수"), "개",
     2025, "기간 중 한 번이라도 나타난 행정동 코드의 수", "raw:시군수:표본|2025|전체|인구",
     "해당없음", "개수는 나눗셈이 아니다. ⚠ 이 행에서 '시군수' 열은 «코드 수»를 담는다 — 표본 규모를 같은 표에 담느라 열을 빌려 썼고, 정의 칸이 정본이다.")
넣기("N49", "균형패널 비중", "747 중 448", "59.97", "%", 2025,
     "15년 내내 존재한 동이 전체 코드에서 차지하는 몫", "expr:100*N47/N48", "N48")
넣기("N50", "노년 분산 배수", "2025 ÷ 2011", "3.60", "배", 2025,
     "노년 비중 인구가중 분산의 2011 대비 2025 배수", "expr:N13/N16", "N16")
넣기("N51", "노년 로짓분산 배수", "2025 ÷ 2011", "1.52", "배", 2025,
     "로짓 척도에서 잰 같은 배수. 비율의 경계 효과에 덜 묶인 척도다 — ⛔ 평균 이동의 "
     "효과를 «빼 준» 값이 아니다 〔2026-09-06 codex〕", "expr:N24/N23", "N23")
넣기("N52", "노년 시군내몫 차", "균형448 − 전체", "0.94", "%p", 2025,
     "표본을 균형패널로 바꿨을 때 노년 시군내몫의 변화", "expr:N26-N09", "해당없음",
     "두 몫의 «차»이지 비율이 아니다 — %p 단위다.")
넣기("N53", "청년 시군내몫 차", "전체 − 균형448", "4.41", "%p", 2025,
     "표본을 균형패널로 바꿨을 때 청년 시군내몫의 변화", "expr:N04-N25", "해당없음",
     "두 몫의 «차»이지 비율이 아니다 — %p 단위다.")

민감 = [("N55", "N54", "N56", "정의민감도|2025|청년|인구|15_34", "청년 15~34세"),
        ("N58", "N57", "N59", "정의민감도|2025|청년|인구|25_39", "청년 25~39세")]
for 몫id, 간id, 내id, 키, 라벨 in 민감:
    넣기(간id, "청년비중 시군간분산", 라벨, 값(키, "시군간"), "pp²", 2025,
         "청년 정의를 바꿔 다시 낸 값", f"raw:시군간:{키}", "해당없음", 분산비고)
    넣기(내id, "청년비중 시군내분산", 라벨, 값(키, "시군내"), "pp²", 2025,
         "청년 정의를 바꿔 다시 낸 값", f"raw:시군내:{키}", "해당없음", 분산비고)
    넣기(몫id, "청년비중 시군내몫", 라벨, 값(키, "시군내몫", 3), "%", 2025,
         "청년 정의를 바꿔 다시 낸 시군내몫", f"expr:100*{내id}/({간id}+{내id})",
         f"{간id}+{내id}")

넣기("N60", "청년비중 시군간몫", "2025 인구가중", "28.328", "%", 2025,
     "시군 간 분산 ÷ (시군 간 + 시군 내) × 100. 시군내몫의 나머지",
     "expr:100-N04", "N06+N07")
넣기("N61", "노년비중 시군간몫", "2025 인구가중", "28.997", "%", 2025,
     "위와 같다", "expr:100-N09", "N11+N12")

# ── 7. 그림이 쓰는 값 · 「절반 아래」 예외 ────────────────────────────
#   ★ 그림에 찍히는 수도 대장에 올린다 〔2026-08-31〕. 그림설명은 «본문»이라
#     C4 가 대조하고, 그러면 그림과 글이 같은 값을 쓰는 것이 기계로 보장된다.
넣기("N62", "청년비중 시군간표준편차", "2025 인구가중", 값("분해|2025|청년|인구", "sd간"),
     "pp", 2025, "시군 평균끼리의 분산의 제곱근 — 「시군 «사이»가 얼마나 벌어졌나」",
     "raw:sd간:분해|2025|청년|인구", "해당없음",
     "표준편차는 분산의 제곱근이지 비율이 아니다 — 분모가 없다.")
넣기("N63", "청년비중 시군내표준편차", "2025 인구가중", 값("분해|2025|청년|인구", "sd내"),
     "pp", 2025, "시군 안 분산(인구가중 합산)의 제곱근 — 시군별 표준편차의 산술평균이 아니다",
     "raw:sd내:분해|2025|청년|인구", "해당없음",
     "표준편차는 분산의 제곱근이지 비율이 아니다 — 분모가 없다.")

# ⛔ 60칸(15년 × 2지표 × 2가중) 가운데 «절반 아래»는 이 한 칸뿐이다.
#   차트를 그리다 발견했다 — 본문이 「어느 잣대로 재도 절반을 넘는다」고 쓸 뻔했다.
넣기("N65", "노년비중 시군간분산", "2011 동1표", 값("분해|2011|노년|동", "시군간"),
     "pp²", 2011, "동마다 한 표씩 준 분산 분해의 시군 간 항",
     "raw:시군간:분해|2011|노년|동", "해당없음", 분산비고)
넣기("N66", "노년비중 시군내분산", "2011 동1표", 값("분해|2011|노년|동", "시군내"),
     "pp²", 2011, "같은 분해의 시군 내 항", "raw:시군내:분해|2011|노년|동",
     "해당없음", 분산비고)
넣기("N64", "노년비중 시군내몫", "2011 동1표", 값("분해|2011|노년|동", "시군내몫", 3),
     "%", 2011, "60칸(15년 × 청년·노년 × 인구가중·동1표) 가운데 유일하게 50% 아래인 칸",
     "expr:100*N66/(N65+N66)", "N65+N66")

# ⛔ 「합쳐서」와 「모든」은 다르다 〔2026-08-31 codex 지적〕.
넣기("N67", "시군 «안»이 더 좁은 시군 수", "2025 청년", 값("비교|2025|청년|인구", "단위수"),
     "개", 2025,
     "그 시군 내부 표준편차가 시군 «간» 표준편차(N62)보다 작은 시군의 수. "
     "합산값이 크다고 모든 시군에서 크지는 않다",
     "raw:단위수:비교|2025|청년|인구", "해당없음", "개수는 나눗셈이 아니다 — 분모가 없다.")


# ══════════════════════════════════════════════════════════════════════
#  N68 이후 — 외부 검수가 «값으로 대라»고 짚어 뒤늦게 붙인 행들
#  ⛔⛔ 2026-09-06 — 여기까지가 «손으로 유지되던» 18행이었다. 이 파일은 대장을
#     통째로 덮어쓰므로, analyze 단계를 다시 돌리는 순간 그 18행이 «조용히» 사라진다.
#     사라지기 전에 내부 전수 감사가 잡았다. 「대장을 손으로 안 쓴다」는 이 저장소의
#     규칙이 절반만 지켜지고 있었던 것이다 — 규칙은 검사가 없으면 지켜지지 않는다.
#     → 전부 여기로 옮겼다. 이제 대장은 처음부터 끝까지 기계가 만든다.
# ══════════════════════════════════════════════════════════════════════

안산기관, 안산자료 = "안산시(공공데이터포털)", "경기도 안산시_내국인 및 외국인 현황"
안산URL = "https://www.data.go.kr/data/3069669/fileData.do"
안산위치 = "원본 CSV 원곡동 행"


def 인용(id_, 지표, 대상, 값_, 단위, 기준일, 정의, 기관_, 자료_, url_, 위치_,
        출처식="", 비고="", 분모=""):
    rows.append({
        "id": id_, "구분": "인용", "지표": 지표, "대상": 대상, "값": 값_, "단위": 단위,
        "기준일": 기준일, "정의": 정의, "출처기관": 기관_, "자료명": 자료_, "URL": url_,
        "원문확인": "Y", "원문위치": 위치_, "파생식": "", "출처식": 출처식,
        "본문키워드": "", "무시목록": "", "비고": 비고, "분모": 분모,
    })


def 날짜(d):
    """방금 넣은 행의 기준일을 바꾼다 — 안산 자료는 KOSIS 와 시점이 다르다."""
    rows[-1]["기준일"] = d


인용("N68", "총인구", "안산 원곡동", "19206", "명", "2026-02-20",
    "원곡동 총인구(내국인+등록외국인). 3호가 쓰는 주민등록 «계»와 대조하려고 싣는다",
    안산기관, 안산자료, 안산URL, 안산위치, 비고="1호 대장 N42 와 같은 출처·같은 행")
인용("N69", "내국인", "안산 원곡동", "5354", "명", "2026-02-20",
    "원곡동 내국인. 3호 원자료의 2025년 원곡동 주민등록 «계» 5,330명과 24명 차로 맞는다 "
    "— 이것이 «3호의 분모가 내국인»이라는 근거다",
    안산기관, 안산자료, 안산URL, 안산위치)
인용("N70", "등록외국인", "안산 원곡동", "13852", "명", "2026-02-20",
    "원곡동 등록외국인. 3호 분석에서 통째로 빠진 인구다",
    안산기관, 안산자료, 안산URL, 안산위치)

넣기("N71", "분모에서빠진몫", "안산 원곡동", "72.123", "%", 2025,
     "등록외국인(N70) ÷ 총인구(N68) × 100. 3호의 분모(주민등록 내국인)가 그 동 인구의 "
     "몇 %를 못 담는가", "expr:100*N70/N68", "N68",
     "⛔ 방향은 모른다 — 외국인의 연령 구조를 이 자료로 못 본다")
날짜("2026-02-20")

# ⛔ 2026-09-06 정정 — 기준일이 2025-11-01 로 적혀 있었다. 같은 표의 다른 행은 전부
#   2025-12-31 이고 sources.json 의 이 표 정의가 「각 연 12월 기준」이다. 외부 검수(codex).
인용("N72", "주민등록인구", "안산 원곡동 2025", "5330", "명", "2025-12-31",
    "이 호가 2025년 원곡동에 실제로 쓰는 인구. 원자료의 연령 «계» 행이다. "
    "1호가 확인한 내국인 5,354명(N69)과 24명 차로 맞는다 — 분모가 내국인이라는 근거",
    기관, 자료, URL,
    "자료/읍면동인구_경기_2011_2025.csv · 연도 2025 · 행정동코드 4127354500 · 연령코드 0",
    출처식="101/DT_1B04005N A=4127354500 B=0 itm=T2 prd=2025 agg=one",
    비고="⚠ 이 호의 다른 수치와 «같은 원자료»다. 한계 ⑫ 를 값으로 대려고 대장에 올렸다")

로짓비고 = ("분산의 «몫»이라 분모가 따로 없다 — 로짓 척도의 시군간+시군내 로 나눈 값이고, "
            "그 성분은 «분석표»에 있다(대장에는 몫만 raw 로 옮겼다). "
            "⛔ 2026-09-06 정정 — 「같은 대장에 있다(파생식 참조)」고 적혀 있었으나 "
            "대장에 로짓 성분 행이 없다. 외부 검수(codex).")
넣기("N73", "청년비중 시군내몫", "2025 인구가중·로짓", 값("분해|2025|청년|인구", "로짓내몫"),
     "%", 2025,
     "로짓 척도에서의 시군내몫. 원척도(N04)와 견주려고 낸다 — 척도를 바꿔도 결론이 서는지 "
     "보는 칸이다", "raw:로짓내몫:분해|2025|청년|인구", "해당없음",
     "★ 외부 검수(codex)가 「이게 제목을 위협한다」고 짚어 실었다. 계산은 처음부터 "
     "분석표에 있었고 «싣지 않았을» 뿐이다 · " + 로짓비고)
넣기("N74", "노년비중 시군내몫", "2025 인구가중·로짓", 값("분해|2025|노년|인구", "로짓내몫"),
     "%", 2025, "로짓 척도에서의 시군내몫(노년)",
     "raw:로짓내몫:분해|2025|노년|인구", "해당없음", 로짓비고)

넣기("N75", "청년비중 시군내몫", "청년 20~39세",
     값("정의민감도|2025|청년|인구|20_39", "시군내몫"), "%", 2025,
     "청년을 20~39세로 잡았을 때의 시군내몫(2025 인구가중). «한계 문단»이 정의를 바꿔도 "
     "결론이 서는지 범위로 적는데, 그 범위의 한쪽 끝이다",
     "raw:시군내몫:정의민감도|2025|청년|인구|20_39", "해당없음",
     "★ 외부 검수(codex)가 「20~39 수치가 대장에 없다」고 짚었다")

기여정의 = ("동 j 의 «기여율» = 100 x [(인구_j/전체인구) x (비중_j - 그 시군의 인구가중 평균)^2] "
            "/ 시군내분산. ⛔ 분자(기여«량»)의 단위는 pp² 이고 그것을 전부 더하면 시군내분산과 "
            "같다. 여기 실은 값은 그 기여량을 시군내분산으로 나눈 «몫»이라 단위가 %이고 전부 "
            "더하면 100%다 — 가장 큰 동 하나")
기여비고 = ("★ 외부 검수(codex)가 「소수 대형 동이 분해를 끌 수 있다」고 짚어 실었다. "
            "분모는 전체 동의 기여량 합계이고 그것이 곧 시군내분산이다. "
            "⛔ 2026-09-06 정정 — 「분모가 따로 없다·해당없음」으로 적혀 있었는데 정의 자체가 "
            "「/ 시군내분산」이다. 외부 검수(codex).")
절반비고 = "개수다 — 나눗셈으로 만든 값이 아니라 「기여 큰 것부터 세어 절반이 될 때까지의 동 수」다"
뺀몫비고 = "분산의 «몫»이라 분모가 따로 없다 — 시군간+시군내 로 나눈 값이고 성분이 분석표에 있다"

넣기("N76", "청년비중 최대기여 동의 몫", "2025 인구가중", 값("기여|2025|청년|인구", "상위1몫"),
     "%", 2025, 기여정의, "raw:상위1몫:기여|2025|청년|인구", "N07", 기여비고)
넣기("N77", "청년비중 기여 절반을 만드는 동 수", "2025 인구가중",
     값("기여|2025|청년|인구", "절반동수"), "개", 2025,
     "「시군 내」의 절반이 쌓이는 데 필요한 동 수(기여 큰 것부터). 적을수록 «소수가 끄는» 것이다",
     "raw:절반동수:기여|2025|청년|인구", "해당없음", 절반비고)
넣기("N78", "노년비중 기여 절반을 만드는 동 수", "2025 인구가중",
     값("기여|2025|노년|인구", "절반동수"), "개", 2025, "〃 (노년)",
     "raw:절반동수:기여|2025|노년|인구", "해당없음", 절반비고)
넣기("N79", "청년비중 시군내몫", "2025 인구가중 · 상위5동 제외",
     값("기여|2025|청년|인구", "상위5뺀몫"), "%", 2025,
     "기여 상위 5개 동을 빼고 다시 분해한 시군내몫. 결론이 그 다섯에 걸려 있는지 보는 칸",
     "raw:상위5뺀몫:기여|2025|청년|인구", "해당없음", 뺀몫비고)
넣기("N80", "노년비중 최대기여 동의 몫", "2025 인구가중", 값("기여|2025|노년|인구", "상위1몫"),
     "%", 2025, 기여정의 + " (노년)", "raw:상위1몫:기여|2025|노년|인구", "N12", 기여비고)
넣기("N81", "노년비중 시군내몫", "2025 인구가중 · 상위5동 제외",
     값("기여|2025|노년|인구", "상위5뺀몫"), "%", 2025, "〃 (노년)",
     "raw:상위5뺀몫:기여|2025|노년|인구", "해당없음", 뺀몫비고)

에타정의 = ("일원 분산분석의 η² = 시군 «간» 분산 ÷ 전체 분산 x 100. 즉 «시군을 알면 설명되는» "
            "몫이다. 이 글이 주로 내세우는 시군내몫은 그 나머지(1-η²)다")
에타비고 = "★ 외부 검수(codex)가 「η² 는 시군내몫이 아니라 시군간몫」이라고 짚어 실었다"
넣기("N82", "청년비중 시군간몫(η²)", "2025 인구가중", "28.3277", "%", 2025,
     에타정의, "expr:100*N06/N08", "N08", 에타비고)
넣기("N83", "노년비중 시군간몫(η²)", "2025 인구가중", "28.9974", "%", 2025,
     에타정의, "expr:100*N11/N13", "N13", 에타비고)

최대정의 = ("그 시군 «안» 읍면동들의 인구가중 표준편차. 31곳 가운데 이 값이 가장 큰 시군을 "
            "뽑는다 — 계획서 2절이 「시군별로는 «내부가 가장 갈린 곳»만 값으로 뽑는다」고 "
            "약속한 칸이다")
최대비고 = ("표준편차는 «퍼짐의 크기»라 나눗셈으로 만든 값이 아니다 — 분모가 없다. "
            "★ 외부 검수(Gemini 3.8)가 「계획서가 약속해 놓고 뽑지 않았다」고 짚어 실었다")
넣기("N84", "청년비중 내부 표준편차 최대 시군", "2025 인구가중 · 하남시(동 14곳)",
     값("시군|2025|청년|인구|41450", "sd"), "pp", 2025, 최대정의,
     "raw:sd:시군|2025|청년|인구|41450", "해당없음", 최대비고)
넣기("N85", "노년비중 내부 표준편차 최대 시군", "2025 인구가중 · 여주시(동 12곳)",
     값("시군|2025|노년|인구|41670", "sd"), "pp", 2025, 최대정의,
     "raw:sd:시군|2025|노년|인구|41670", "해당없음", 최대비고)

# ══════════════════════════════════════════════════════════════════════
#  2026-09-06 2차 검수 — 본문의 «평문 정수»가 대장 밖에 있었다
#  ⛔ 관문 C4 는 쉼표·소수점·% 가 없는 정수를 «구조적으로» 못 본다. 그래서 본문의
#     204·143·302·25·5 가 아무 검사도 없이 나가고 있었고, 그중 「안산 22개」는
#     «어떤 기준으로도 재현되지 않는 수»였다(실측: 25개가 전부 맞는다).
#     → 값으로 댈 수 있는 것은 전부 대장에 올린다.
# ══════════════════════════════════════════════════════════════════════

인용("N86", "행정동 수", "안산시", "25", "개", "2026-02-20",
    "1호가 쓴 안산 행정동 수. 이 호의 2025년 원자료에서도 안산은 25개 동이다 — "
    "그 25곳 «전부»에서 1호의 내국인 수와 이 호의 주민등록 «계»가 0.5% 안에서 맞는다",
    안산기관, 안산자료, 안산URL, "원본 CSV 행 수",
    비고="⛔ 2026-09-06 정정 — 본문이 「25개 동 가운데 22개가 맞았다」고 적고 있었다. "
         "실측하면 25개가 «전부» 맞고(최대 차 0.45%), 22를 만드는 기준이 어디에도 없다. "
         "내부 전수 감사가 잡았다")
인용("N87", "외국인 비중 10% 초과 동 수", "안산시", "5", "개", "2026-02-20",
    "1호 자료에서 «안산 25개 동 가운데» 등록외국인이 총인구의 10%를 넘는 동 수"
    "(이동·원곡동·백운동·신길동·선부2동). ⛔ 다른 시군은 조사하지 않았다 — "
    "읍면동별 외국인 자료가 우리에게 1호(안산)뿐이다",
    안산기관, 안산자료, 안산URL, "원본 CSV 외국인비율 열",
    비고="개수는 나눗셈이 아니다 — 분모가 없다")
넣기("N88", "1호 내국인과 이 호 주민등록 계의 차", "안산 원곡동", "0.448", "%", 2025,
     "(N69 − N72) ÷ N69 × 100. 안산 25개 동 가운데 이 상대차가 가장 큰 곳이 원곡동이다 "
     "— 나머지는 전부 이보다 작다", "expr:100*(N69-N72)/N69", "N69",
     "★ 「같은 방식으로 맞았다」의 기준을 값으로 댄다 〔2026-09-06 내부 전수 감사〕")
날짜("2026-02-20")

넣기("N89", "균형패널의 시군 수", "2025 인구가중 · 15년 내내 존재한 448동",
     값("균형|2025|청년|인구", "시군수"), "개", 2025,
     "15년 내내 존재한 448개 동만 남기면 시군이 몇 곳이 되는가. 전체 표본의 31곳(N02)과 "
     "다르다 — 표본을 바꾸면 «시군 집합»도 바뀐다",
     "raw:시군수:균형|2025|청년|인구", "해당없음",
     "★ 내부 전수 감사가 「표본을 바꿔도 본다」가 시군 집합 변화를 감춘다고 짚어 실었다")
넣기("N90", "청년비중 내부 표준편차 2위 시군", "2025 인구가중 · 이천시",
     값("시군|2025|청년|인구|41500", "sd"), "pp", 2025,
     "1위 하남시(N84)와의 차가 0.0016pp 라 발행 자릿수(4.50pp)에서는 같은 값이다 — "
     "「가장 갈린 곳」을 한 곳으로만 부르면 틀린다",
     "raw:sd:시군|2025|청년|인구|41500", "해당없음",
     "★ 내부 전수 감사가 「사실상 동률인데 1위만 적었다」고 짚어 실었다")
넣기("N91", "시군 «안»이 더 좁은 시군 수", "2025 노년",
     값("비교|2025|노년|인구", "단위수"), "개", 2025,
     "그 시군 내부 표준편차가 시군 «간» 표준편차보다 작은 시군의 수(노년). "
     "청년은 3곳(N67)이다", "raw:단위수:비교|2025|노년|인구", "해당없음",
     "★ 2026-09-06 — 전에는 청년만 세어 한계 ⑬ 이 「노년으로는 세지 않았다」고 적었다. "
     "내부 감사가 「셀 수 있는데 안 셌다」고 짚어 prep.py 에 노년을 더했다")

# ⛔ 2026-09-06 — 이름이 「새로 생긴/사라진 행정동」이었는데, 이 식이 세는 것은
#   «끝점 표본에 없거나 있는 코드»다. 코드 변경·명칭 개편·분동·통합이 다 섞인다.
#   이 호 자신이 한계 ②에서 「코드가 유지돼도 경계가 같았다는 보장은 없다」고 적는다.
#   → 이름과 본문을 «코드 기준»으로 좁혔다 〔외부 검수 codex〕.
넣기("N92", "2011년 표본에 없고 뒤에 나타난 행정동 코드 수", "2011~2025", "204", "개", 2025,
     "15년 합집합(N48) − 2011년 표본(N03). ⛔ 「신설된 동」이 아니라 «코드»의 수다 — "
     "코드 변경·경계 개편·분동도 여기 섞인다",
     "expr:N48-N03", "해당없음", "개수는 나눗셈이 아니다 — 분모가 없다")
넣기("N93", "2025년 표본에 없는 행정동 코드 수", "2011~2025", "143", "개", 2025,
     "15년 합집합(N48) − 2025년 표본(N01). ⛔ 「폐지된 동」이 아니라 «코드»의 수다",
     "expr:N48-N01", "해당없음", "개수는 나눗셈이 아니다 — 분모가 없다")
# ⛔ 「60칸 가운데 한 칸」 — 이 호가 「내내」라고 못 쓰는 유일한 이유이자 제목의 크기를
#   정하는 수인데, 60 도 1 도 «아무도 세지 않은» 손 숫자였다 〔2026-09-06 내부 전수 감사〕.
#   prep.py 가 분해 행을 훑어 세고 여기서 raw: 로 옮긴다.
넣기("N95", "시군내몫이 절반 아래인 칸 수", "2011~2025 · 60칸",
     값("문턱|2025|전체|인구", "단위수"), "개", 2025,
     "15년 × 두 지표(청년·노년) × 두 가중(인구가중·동 1표) = 60칸 가운데 시군내몫이 "
     "50% 아래인 칸의 수. 그 한 칸이 2011년 노년·동 1표(N64)다",
     "raw:단위수:문턱|2025|전체|인구", "해당없음",
     "★ 내부 전수 감사가 「아무도 세지 않은 주장」이라고 짚어 코드가 세게 했다")
넣기("N96", "시군내몫을 잰 칸 수", "2011~2025 · 15년 × 2지표 × 2가중",
     값("문턱|2025|전체|인구", "시군수"), "개", 2025,
     "분해를 낸 칸의 총수. 15 × 2 × 2 = 60 이 실제로 60 인지 코드가 센다",
     "raw:시군수:문턱|2025|전체|인구", "해당없음", "개수는 나눗셈이 아니다 — 분모가 없다")
넣기("N97", "1호 내국인과 이 호 주민등록 계의 차", "안산 원곡동 · 명수", "24", "명", 2025,
     "N69 − N72. 기준시점이 다른 두 자료가 어긋나지 않는다는 정합성 점검의 크기다",
     "expr:N69-N72", "해당없음",
     "★ 본문이 「24명 차이는 …」이라고 적는데 대장에 없었다 〔2026-09-06 내부 전수 감사〕")
날짜("2026-02-20")

넣기("N94", "기여가 완전히 고를 때 절반에 드는 동 수", "2025", "302", "개", 2025,
     "전체 동 수(N01)의 절반. 실제로는 청년 46곳(N77)·노년 76곳(N78)이라 「고르게 "
     "퍼졌다」고는 못 한다 — 그 대조를 위한 기준값이다",
     "expr:N01/2", "해당없음",
     "전체 동 수의 절반을 나타내는 «기준 개수»다. 비율 지표가 아니므로 모집단 분모가 없다 "
     "〔2026-09-06 정정 — 비고가 「나눗셈이 아니다」인데 파생식이 N01/2 라 충돌했다. codex〕")


열 = ["id", "구분", "지표", "대상", "값", "단위", "기준일", "정의", "출처기관", "자료명",
      "URL", "원문확인", "원문위치", "파생식", "출처식", "본문키워드", "무시목록", "비고", "분모"]
rows.sort(key=lambda r: int(r["id"][1:]))
with open(OUT, "w", encoding="utf-8-sig", newline="") as f:
    w = csv.DictWriter(f, fieldnames=열)
    w.writeheader()
    w.writerows(rows)
print(f"대장 {len(rows)}행 → {OUT}")
