# -*- coding: utf-8 -*-
r"""브리프 08 — 경기도 인구는 늘었다. 그 증가는 어디서 왔나 (2011 → 2025)

    py prep.py            (run.py 가 analysis/ 를 cwd로 잡고 실행한다)

무엇을 하나
  경기도 전체 주민등록인구는 2011~2025 사이 늘었다. 그런데 그 「늘었다」는
  31개 시군이 고르게 늘어서 생긴 값이 아니다. **늘어난 시군의 증가분에서
  줄어든 시군의 감소분을 뺀 나머지**다. 그 두 덩어리를 갈라 크기를 잰다.

왜 이 축인가 — 기사 프레임을 그대로 옮기지 않기 위해
  소재가 된 기사는 「화성·평택 웃고 수원·성남 울었다」로 네 곳을 들었다.
  네 곳을 되읽으면 순위표가 된다(그건 스탯카드다 — doc_wiki L011).
  여기서는 31개 시군 전부를 받아 **총량 증가가 몇 곳에 얼마나 몰려 있는지**를 낸다.
  기사는 실마리일 뿐이고 본문은 원자료로 새로 분석한다 (citation-log V013).

★ 「전체가 늘었다」와 「대부분이 늘었다」는 다른 말이다
  증가분 합·감소분 합·순증가를 각각 내고, 순증가 대비 상위 몇 곳의 몫을 본다.
  이건 항등식이라 검산이 정확히 맞는다:  순증가 = Σ증가 − Σ감소

★ 최근 5년을 따로 본다
  15년을 한 덩어리로 보면 「2010년대에 늘고 2020년대에 줄기 시작한 곳」이 지워진다.
  그래서 2011→2025 와 2020→2025 두 구간의 방향을 각 시군마다 함께 낸다.

자료
  통계청 KOSIS · 주민등록인구현황
  「행정구역(읍면동)별/5세별 주민등록인구(2011년~)」 DT_1B04005N · itmId=T2(총인구수)
  각 연 12월 기준 · 신(新) 행정구역 코드(경기도=41)
  ⚠ 내국인만 센다 — 외국인 비중이 큰 도시는 실제 거주 규모와 벌어진다 (sources.json 함정)
"""
import csv
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))   # 저장소 루트의 kosis.py
import kosis

import numpy as np

import chartkit as ck                 # 색·폰트·축은 저장소 한 곳에서 온다

try:
    sys.stdout.reconfigure(encoding="utf-8")
except Exception:
    pass

plt = ck.setup()                      # 한글 폰트가 없으면 여기서 죽는다 (□□□ 발행 방지)

ORG, TBL, ITM = "101", "DT_1B04005N", "T2"
YEARS = [str(y) for y in range(2011, 2026)]
Y0, Y1 = YEARS[0], YEARS[-1]
YMID = "2020"                     # 최근 구간의 시작
GG = "경기도"
ALL = "계"                        # B축 라벨. 연령 코드는 라벨과 어긋나므로 라벨로 다룬다

SGG = ["수원시", "성남시", "의정부시", "안양시", "부천시", "광명시", "평택시", "동두천시",
       "안산시", "고양시", "과천시", "구리시", "남양주시", "오산시", "시흥시", "군포시",
       "의왕시", "하남시", "용인시", "파주시", "이천시", "안성시", "김포시", "화성시",
       "광주시", "양주시", "포천시", "여주시", "연천군", "가평군", "양평군"]
# ★ 여주시는 2013 년 승격이다. 2011~2012 는 「여주군」으로만 잡힌다 (B04 에서 밟은 함정).
OLD_NAME = {"여주군": "여주시"}

# 색은 chartkit 에서 온다 — 이 파일에 다시 적지 않는다 〔2026-08-25〕.
# ★ 「방향바뀜」은 세 번째 «색»이 아니라 «모양»(◇)으로 가른다. 흰 배경에서
#   명도차 12 이상인 계열색은 두 개가 한계이기 때문이다 (chartkit 머리말).
C_UP, C_DOWN, C_TURN = ck.PRIMARY, ck.ACCENT, ck.HILITE


# ── 수집 ────────────────────────────────────────────────────────────
def year_rows(y):
    """그 해의 {시군: 총인구}. 지역 수가 안 맞으면 그 자리에서 죽는다."""
    want = [GG] + SGG + (["여주군"] if int(y) < 2013 else [])
    if int(y) < 2013:
        want = [w for w in want if w != "여주시"]
    rec = kosis.fetch_rows(ORG, TBL, y, sel={"A": want, "B": [ALL]},
                           prefix={"A": "41"}, itm=ITM, quiet=True)
    out = {}
    for r in rec:
        out[OLD_NAME.get(r["A"], r["A"])] = r["값"]
    if len(out) != len(SGG) + 1:
        sys.exit(f"✖ {y}년 지역 {len(out)}개 — {len(SGG)+1}개여야 합니다: {sorted(out)}")
    return out


pop = {}
for y in YEARS:
    pop[y] = year_rows(y)
    print(f"[수집] {y}  지역 {len(pop[y])}개  경기도 {pop[y][GG]:,}", flush=True)

print(f"\n[대장] 원문위치(지역) → {kosis.cite(ORG, TBL, 'A', [GG], '41')}")
print(f"[대장] 원문위치(연령) → {kosis.cite(ORG, TBL, 'B', [ALL], None)}")


# ── 계산 ────────────────────────────────────────────────────────────
def row(nm, 유형="시군"):
    a, b, m = pop[Y0][nm], pop[Y1][nm], pop[YMID][nm]
    d = {
        "시군": nm,
        # ★ 경기도 전체는 시군이 아니다 — 유형을 갈라 두지 않으면 대장의
        #   `aggsum:<열>:시군` 에 도 전체가 섞여 합계가 하나씩 어긋난다 (B04 에서 정립).
        "유형": 유형,
        f"인구_{Y0}": a,
        f"인구_{YMID}": m,
        f"인구_{Y1}": b,
        "증감": b - a,
        "증감률": round(100 * (b - a) / a, 2),
        "최근증감": b - m,                       # 2020 → 2025
        "최근증감률": round(100 * (b - m) / m, 2),
        "늘었나": 1 if b > a else 0,
        "줄었나": 1 if b < a else 0,
        # 증가분·감소분을 열로 둔다 — 그래야 대장이 «합계»를 원자료에서 재계산할 수 있다.
        "증가분": max(b - a, 0),
        "감소분": max(a - b, 0),
        "최근늘었나": 1 if b > m else 0,
    }
    # 15년 전체로는 늘었는데 최근 5년은 줄어든 곳 — 한 덩어리로 보면 지워지는 사실이다
    d["방향바뀜"] = 1 if (d["늘었나"] == 1 and d["최근늘었나"] == 0) else 0
    return d


rows = [row(nm) for nm in SGG]
rows.sort(key=lambda r: -r["증감"])

UP = [r for r in rows if r["증감"] > 0]
DOWN = [r for r in rows if r["증감"] < 0]
FLAT = [r for r in rows if r["증감"] == 0]
TURN = [r for r in rows if r["방향바뀜"]]

SUM_UP = sum(r["증감"] for r in UP)
SUM_DOWN = -sum(r["증감"] for r in DOWN)          # 양수로
NET = SUM_UP - SUM_DOWN
GG_NET = pop[Y1][GG] - pop[Y0][GG]

# 상위 몇 곳이 순증가를 다 만드는가 — 「고르게 늘었다」와 갈라내는 값
cum, TOPN = 0, 0
for r in UP:
    cum += r["증감"]
    TOPN += 1
    if cum >= NET:
        break
TOP3 = sum(r["증감"] for r in UP[:3])

gg_row = row(GG, 유형="도전체")

with open("raw.csv", "w", encoding="utf-8-sig", newline="") as f:
    w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
    w.writeheader()
    w.writerows(rows + [gg_row])


# ── 차트 ── 31개 시군의 증감을 한 줄씩. 0 을 기준으로 갈린다
fig, ax = plt.subplots(figsize=(7.6, 8.8))
ys = np.arange(len(rows))[::-1]
vals = [r["증감"] / 10000 for r in rows]          # 만 명
colors = [C_UP if v > 0 else C_DOWN for v in vals]
ax.barh(ys, vals, color=colors, height=.66, zorder=2)

for i, r in enumerate(rows):
    v = r["증감"] / 10000
    off = 1.2 if v >= 0 else -1.2
    ax.text(v + off, ys[i], f"{v:+,.1f}", va="center",
            ha="left" if v >= 0 else "right", fontsize=8.4,
            color=ck.글자색(C_UP if v > 0 else C_DOWN),   # 막대색 그대로 쓰면 글자 대비 미달
            fontweight="bold" if r["방향바뀜"] else "normal")
    # 최근 5년에 방향이 바뀐 곳은 표시를 남긴다 — 이게 이 브리프의 반전이다
    if r["방향바뀜"]:
        ax.scatter([0], [ys[i]], s=26, marker="D", color=C_TURN, zorder=4)

ck.zero_line(ax)

from matplotlib.patches import Patch
from matplotlib.lines import Line2D
ax.legend(handles=[
    Patch(color=C_UP, label=f"늘어난 시군 {len(UP)}곳"),
    Patch(color=C_DOWN, label=f"줄어든 시군 {len(DOWN)}곳"),
    Line2D([], [], marker="D", ls="", ms=6.5, color=C_TURN,
           label=f"15년 전체는 늘었지만 최근 5년은 줄어든 곳 {len(TURN)}곳"),
], loc="upper left", bbox_to_anchor=(0, -.062), ncol=2, frameon=False, fontsize=8.8,
    handletextpad=.6, columnspacing=1.4)

ax.set_yticks(ys)
ax.set_yticklabels([r["시군"] for r in rows], fontsize=9.5)
for lab, r in zip(ax.get_yticklabels(), rows):
    if r["방향바뀜"]:
        lab.set_color(C_TURN)
        lab.set_fontweight("bold")
ax.set_xlabel(f"주민등록인구 증감  {Y0} → {Y1}  (만 명)", fontsize=10, labelpad=9)
# ★ 값 라벨이 막대 «바깥»에 붙으므로 양 끝에 자리를 따로 만든다.
#   안 하면 가장 긴 음수 막대(부천)의 라벨이 왼쪽 시군 이름과 겹쳐 「부천사11.4」로 읽힌다.
lo, hi = min(vals), max(vals)
ax.set_xlim(lo - 6.5, hi + 6.5)
ax.set_ylim(-1, len(rows))
ck.tidy(ax, grid="x")
fig.tight_layout()
ck.save(fig, "chart.png", dpi=130)

# ── 보조 차트 ── 늘어난 덩어리와 줄어든 덩어리, 그리고 남은 것
fig, ax = plt.subplots(figsize=(8.0, 4.4))
bars = [SUM_UP / 10000, -SUM_DOWN / 10000, NET / 10000]
labs = [f"늘어난 {len(UP)}곳의 증가분", f"줄어든 {len(DOWN)}곳의 감소분", "경기도 순증가"]
# ⛔ 여기에 글자 전용색(`ck.NEUTRAL_INK`)을 칠하고 있었다 〔2026-08-25 수정〕 —
#   파랑과 명도차 8.1(필요 12)·채도 8(필요 35)로 규칙 두 개를 어겼다.
#   `검사한계열` 을 거치면 그런 목록은 그리기 전에 죽는다.
cols = ck.검사한계열(C_UP, C_DOWN, ck.THIRD)
xs = np.arange(3)
ax.bar(xs, bars, color=cols, width=.6)
for x, v in zip(xs, bars):
    ax.text(x, v + (3 if v >= 0 else -3), f"{v:+,.1f}만", ha="center",
            va="bottom" if v >= 0 else "top", fontsize=13, fontweight="bold",
            color=ck.글자색(cols[x]))
ck.zero_line(ax, 세로=False)
ax.set_xticks(xs)
ax.set_xticklabels(labs, fontsize=11)
ax.set_yticks([])
ax.set_ylim(min(bars) - 22, max(bars) + 22)
ck.tidy(ax, grid=None, 아래축=False)
fig.tight_layout()
ck.save(fig, "chart2.png", dpi=130)

print(f"[저장] raw.csv {len(rows)+1}행 · chart.png · chart2.png")


# ── 대장에 옮길 값 ───────────────────────────────────────────────────
print("\n[대장] 수치대장.csv 에 옮길 값 ─────────────────")
print(f"  경기도 인구 {Y0}          {pop[Y0][GG]:,}")
print(f"  경기도 인구 {Y1}          {pop[Y1][GG]:,}   ({GG_NET:+,})")
print(f"  경기도 증감률              {100*GG_NET/pop[Y0][GG]:+.2f}%")
print()
print(f"  늘어난 시군                {len(UP)} 곳   증가분 합 {SUM_UP:,}")
print(f"  줄어든 시군                {len(DOWN)} 곳   감소분 합 {SUM_DOWN:,}")
print(f"  변화 없음                  {len(FLAT)} 곳")
print(f"  ▸ 항등식 검산              {SUM_UP:,} − {SUM_DOWN:,} = {NET:,}")
print(f"  ▸ 시군 합 vs 도 전체        {NET:,} vs {GG_NET:,}  "
      f"(차 {GG_NET-NET:,} — 일반구·출장소 없이 시군만 더한 값)")
print()
print(f"  상위 {TOPN}곳이 순증가를 덮는다   "
      + ", ".join(f"{r['시군']} {r['증감']:+,}" for r in UP[:TOPN]))
print(f"  상위 3곳 증가분             {TOP3:,}  (순증가의 {100*TOP3/NET:.0f}%)")
print()
# ── 「총량이 무엇을 덮는가」를 **값으로** 낸다 〔2026-08-28, 관문 C8〕 ──────────
#   ⛔ 여태 이 브리프는 「상쇄된 48만이 사라진다」·「총량만 보면」이라고 **주장만** 하고
#     그 몫을 값으로 안 냈다. 크기 두 개를 나란히 적는 것과 몫을 내는 것은 다른 일이다.
#     C8 이 이 자리를 막는다(구성 주장 → `몫` · 분포 주장 → `퍼짐`).
#   ⚠ 넷 다 raw.csv 에서 다시 계산되므로 C2 가 검산한다 — 손계산이 아니다.
GROSS = SUM_UP + SUM_DOWN                     # 상쇄하지 않고 더한, 실제로 일어난 변화
증감들 = sorted(r["증감"] for r in rows)
_m = len(증감들)
MEDIAN = 증감들[_m // 2] if _m % 2 else (증감들[_m // 2 - 1] + 증감들[_m // 2]) / 2
MEAN = NET / len(rows)
print(f"  ▸ 총이동량(상쇄 안 함)      {GROSS:,}   = {SUM_UP:,} + {SUM_DOWN:,}")
print(f"  ▸ 총량이 보여주는 몫        {100*NET/GROSS:.2f}%   "
      f"(= 순증가 ÷ 총이동량) — 나머지 {100-100*NET/GROSS:.2f}% 는 총량에서 안 보인다")
print(f"  ▸ 시군 증감 평균 / 중앙값    {MEAN:,.0f} / {MEDIAN:,.0f}  "
      f"({MEAN/MEDIAN:.1f}배 — 평균이 덮고 있는 폭)")
print()
print(f"  가장 많이 는 곳             {rows[0]['시군']} {rows[0]['증감']:+,} "
      f"({rows[0]['증감률']:+.1f}%)")
print(f"  가장 많이 준 곳             {rows[-1]['시군']} {rows[-1]['증감']:+,} "
      f"({rows[-1]['증감률']:+.1f}%)")
print(f"  줄어든 곳                  " + ", ".join(
    f"{r['시군']} {r['증감']:+,}" for r in DOWN))
print()
print(f"  방향이 바뀐 곳 ({len(TURN)}곳)      "
      + (", ".join(f"{r['시군']} (15년 {r['증감']:+,} / 최근5년 {r['최근증감']:+,})"
                   for r in TURN) or "없음"))
