# -*- coding: utf-8 -*-
r"""브리프 04 — 경기 31개 시군의 노령화지수가 100을 넘어선 해 (2011 → 2025)

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

무엇을 하나
  노령화지수 = 65세 이상 인구 ÷ 0~14세 인구 × 100.
  100을 넘으면 그 지역의 노인이 아이보다 많아진 것이다.
  기사는 「오산·과천·시흥도 역전」이라고 세 곳을 들었다. 31개 시군 전부에 대해
  **언제 넘었는지**를 15년치로 구한다.

왜 「역전 연도」인가 — 이 매체가 아직 안 쓴 축
  브리프 01·02·03 은 전부 두 시점(2015·2024)만 봤다. 그래서 「언제 갈렸나」를
  말할 수 없었다 (`03_분석_고도화_방안.md` 한계 #2, 사다리 L2).
  이 브리프는 전 구간을 받아 **사건이 도착한 시점**을 지역마다 잰다.

★ 지수는 분자·분모가 같이 움직인다
  노인이 안 늘어도 아이가 줄면 지수는 오른다. 그래서 배율을 따로 낸다:
      지수배율 = 노년배율 ÷ 유소년배율      (항등식이라 정확히 맞는다)
  브리프 03 의 분해와 같은 취지다 — 「늘었다」의 원인을 가른다.

★ 2011 년에 이미 100 을 넘은 곳은 역전 연도를 알 수 없다
  표가 2011 년부터라서다. 「2011 이전」으로만 적고, 그 해를 역전 연도로 세지 않는다.

자료
  통계청 KOSIS · 주민등록인구현황
  「행정구역(읍면동)별/5세별 주민등록인구(2011년~)」 DT_1B04005N · itmId=T2(총인구수)
  각 연 12월 기준 · 신(新) 행정구역 코드(경기도=41) — 코드는 kosis.py 가 이름으로 찾는다
"""
import csv, sys
from pathlib import Path

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

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
# ⛔ 그림 저장은 정본(`chartkit.save`)을 지난다 〔2026-09-21〕 — 그래야
#   ⑴ 폰용 그림이 같이 나가고(`analysis/폰/`) ⑵ 「폰에서 읽히나」 관문이 돈다.
#   `plt.savefig` 를 직접 부르면 그 둘이 통째로 꺼진다.
import chartkit as ck
import numpy as np

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

plt.rcParams["font.family"] = ["Malgun Gothic", "sans-serif"]
plt.rcParams["axes.unicode_minus"] = False

ORG, TBL, ITM = "101", "DT_1B04005N", "T2"
YEARS = [str(y) for y in range(2011, 2026)]
Y0, Y1 = YEARS[0], YEARS[-1]
GG = "경기도"

# 5세 구간 라벨. 코드(70=65~69세)와 한 칸 어긋나는 표라 **라벨로만** 다룬다.
YOUNG = ["0 - 4세", "5 - 9세", "10 - 14세"]
OLD = ["65 - 69세", "70 - 74세", "75 - 79세", "80 - 84세",
       "85 - 89세", "90 - 94세", "95 - 99세", "100+"]

SGG = ["수원시", "성남시", "의정부시", "안양시", "부천시", "광명시", "평택시", "동두천시",
       "안산시", "고양시", "과천시", "구리시", "남양주시", "오산시", "시흥시", "군포시",
       "의왕시", "하남시", "용인시", "파주시", "이천시", "안성시", "김포시", "화성시",
       "광주시", "양주시", "포천시", "여주시", "연천군", "가평군", "양평군"]
# ★ 여주시는 2013 년 승격이다. 2011~2012 는 「여주군」으로만 잡히고, 빠뜨리면
#   그 두 해만 지역이 30 개로 와서 조용히 어긋난다 (실제로 밟았다).
OLD_NAME = {"여주군": "여주시"}

C_MAIN, C_BACK, C_NONE = "#2f6fb5", "#c8482a", "#9aa3b2"


# ── 수집 ────────────────────────────────────────────────────────────
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": YOUNG + OLD},
                           prefix={"A": "41"}, itm=ITM, quiet=True)
    out = {}
    for r in rec:
        nm = OLD_NAME.get(r["A"], r["A"])
        cur = out.setdefault(nm, [0, 0])
        cur[0 if r["B"] in YOUNG else 1] += 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])}개", flush=True)

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

idx = {nm: {y: 100 * pop[y][nm][1] / pop[y][nm][0] for y in YEARS} for nm in pop[Y0]}


def crossed(nm):
    """지수가 처음 100 이상이 된 해. 2011 에 이미 넘었으면 '2011이전', 아직이면 ''."""
    if idx[nm][Y0] >= 100:
        return "2011이전"
    for y in YEARS:
        if idx[nm][y] >= 100:
            return y
    return ""


def bucket(c):
    if c == "2011이전":
        return "2011이전"
    if not c:
        return "미도달"
    return "2013~2019" if int(c) <= 2019 else "2020~2025"


# 중간 연도 — 과천의 되돌아간 경로가 본문에 들어간다. 대장이 원자료로 검산되려면
# 그 해들이 raw.csv 에 있어야 한다 (없으면 C2 가 통째로 '재계산 불가'가 된다).
MID = ["2018", "2020", "2024"]


def line(nm, c, group):
    d = {"시군": nm, "유형": group, "역전연도": c or "미도달", "역전": 0 if not c else 1,
         # 2019년 한 해에 몰린 것을 본문이 쓴다 → 세는 열을 원자료에 둔다
         "역전2019": 1 if c == "2019" else 0}
    for y in [Y0] + MID + [Y1]:
        d[f"지수_{y}"] = round(idx[nm][y], 1)
    d.update({f"노년_{Y0}": pop[Y0][nm][1], f"노년_{Y1}": pop[Y1][nm][1],
              f"유소년_{Y0}": pop[Y0][nm][0], f"유소년_{Y1}": pop[Y1][nm][0],
              "노년배율": round(pop[Y1][nm][1] / pop[Y0][nm][1], 3),
              "유소년배율": round(pop[Y1][nm][0] / pop[Y0][nm][0], 3)})
    # 아이가 는 시군이 있다는 것이 본문의 반전이다 → 세는 열을 원자료에 둔다
    d["유소년증가"] = 1 if d["유소년배율"] > 1 else 0
    return d


rows = [line(nm, crossed(nm), bucket(crossed(nm))) for nm in SGG]
rows.sort(key=lambda r: (r["역전연도"] == "미도달", r["역전연도"], -r[f"지수_{Y1}"]))

# 경기도 전체는 시군이 아니므로 유형을 따로 준다 — aggsum:역전:<유형> 에 섞이지 않는다
gg_row = line(GG, crossed(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])


# ── 되돌아간 곳 찾기 ─────────────────────────────────────────────────
def went_back(nm):
    s = [idx[nm][y] for y in YEARS]
    return any(a >= 100 > b for a, b in zip(s, s[1:]))


BACK = [nm for nm in SGG if went_back(nm)]
NONE = [r["시군"] for r in rows if not r["역전"]]

# ── 차트 ── 같은 사건이 지역마다 언제 도착했나. 점 하나 = 그 시군의 역전 연도
fig, ax = plt.subplots(figsize=(7.6, 8.8))
ys = np.arange(len(rows))[::-1]
XPRE, XNONE = 2009.6, 2026.8                      # 「2011 이전」·「아직」 칸
for i, r in enumerate(rows):
    nm, c = r["시군"], r["역전연도"]
    x = XPRE if c == "2011이전" else (XNONE if c == "미도달" else int(c))
    color = C_NONE if c == "미도달" else (C_BACK if nm in BACK else C_MAIN)
    if nm in BACK:                                # 되돌아간 구간을 선으로 드러낸다
        s = [idx[nm][y] for y in YEARS]
        off = [j for j, (a, b) in enumerate(zip(s, s[1:])) if a >= 100 > b][0]
        ax.plot([int(YEARS[off + 1]), int(YEARS[-1])], [ys[i], ys[i]],
                color=C_BACK, lw=1.4, ls=(0, (2, 2)), zorder=1)
        ax.scatter([int(YEARS[-1])], [ys[i]], s=52, color=C_BACK, zorder=3)
    ax.scatter([x], [ys[i]], s=52, color=color, zorder=3,
               marker="o" if c != "미도달" else "o",
               facecolor=color if c != "미도달" else "white",
               edgecolor=color, linewidths=1.6)
    ax.text(x + .28, ys[i], f"{r[f'지수_{Y1}']:,.0f}", va="center", fontsize=8.2,
            color=color, fontweight="bold" if nm in BACK or not r["역전"] else "normal")

gg_cross = crossed(GG)
ax.axvline(int(gg_cross), color="#6b7280", lw=1.1, ls="--", zorder=0)
ax.text(int(gg_cross) - .12, len(rows) - .4, f"경기도 전체 {gg_cross} ", color="#6b7280",
        fontsize=9, ha="right", va="bottom")
# 양 끝 두 칸은 연도가 아니라 범주다. 연도축과 이어 읽히면 「2010년에 넘었다」로 오독된다
for xline in (2010.4, 2026.1):
    ax.axvline(xline, color="#cfd4de", lw=.9)

# 색으로만 구분하지 않는다 — 범례를 항상 둔다
from matplotlib.lines import Line2D
def dot(c, fill, lab, **kw):
    return Line2D([], [], marker="o", ls="", ms=7.5, color=c,
                  markerfacecolor=fill, markeredgecolor=c, mew=1.6, label=lab, **kw)
ax.legend(handles=[
    dot(C_MAIN, C_MAIN, "역전한 뒤 되돌아오지 않음"),
    Line2D([], [], marker="o", ls=(0, (2, 2)), ms=7.5, lw=1.4, color=C_BACK,
           markerfacecolor=C_BACK, label="역전 → 100 아래로 → 재역전"),
    dot(C_NONE, "white", "아직 역전 전"),
], loc="upper left", bbox_to_anchor=(0, -.075), ncol=3, frameon=False, fontsize=8.8,
    handletextpad=.5, columnspacing=1.5)
# ★ 범례를 축 안(왼쪽 아래)에 두면 「2011 이전」 칸의 시흥·오산·화성 행에 얹혀
#   범례 마커가 그 시군의 데이터 점처럼 읽힌다. 실제로 그렇게 나왔다.

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["시군"] in BACK or not r["역전"]:
        lab.set_color(C_BACK if r["시군"] in BACK else C_NONE)
        lab.set_fontweight("bold")
ax.set_xticks([XPRE] + [y for y in range(2012, 2026, 2)] + [XNONE])
ax.set_xticklabels(["2011\n이전"] + [str(y) for y in range(2012, 2026, 2)] + ["아직"], fontsize=9)
ax.set_xlim(2008.9, 2028.1)
ax.set_ylim(-1, len(rows))
ax.set_xlabel("노령화지수가 100을 넘어선 해  (숫자는 2025년 지수)", fontsize=10, labelpad=9)
ax.tick_params(axis="y", length=0)
for s in ("top", "right", "left"):
    ax.spines[s].set_visible(False)
ax.spines["bottom"].set_color("#cfd4de")
ax.grid(axis="x", color="#eef0f4", lw=.8)
ax.set_axisbelow(True)
fig.tight_layout()
ck.save(fig, "chart.png", dpi=130)

# ── 카드용 차트 ── 폰에서는 31개 시군 이름이 안 읽힌다. 「몇 곳이 언제」만 남긴다
fig, ax = plt.subplots(figsize=(8.4, 6.0))
keys = ["2011이전"] + [str(y) for y in range(2013, 2026)]
cnt = [sum(1 for r in rows if r["역전연도"] == k) for k in keys]
xs = np.arange(len(keys))
ax.bar(xs, cnt, color=[C_MAIN] * len(keys), width=.68)
for x, c in zip(xs, cnt):
    if c:
        ax.text(x, c + .12, str(c), ha="center", fontsize=15, fontweight="bold", color=C_MAIN)
ax.bar([len(keys)], [len(NONE)], color=C_NONE, width=.68)
ax.text(len(keys), len(NONE) + .12, str(len(NONE)), ha="center", fontsize=15,
        fontweight="bold", color=C_NONE)
ax.set_xticks(list(xs) + [len(keys)])
ax.set_xticklabels(["2011\n이전"] + [k[2:] for k in keys[1:]] + ["아직"], fontsize=12.5)
ax.set_yticks([])
ax.set_ylim(0, max(cnt) + 1.2)
ax.set_xlabel("노인이 아이보다 많아진 해 · 경기 31개 시군", fontsize=15, labelpad=12)
ax.tick_params(axis="x", length=0)
for s in ("top", "right", "left", "bottom"):
    ax.spines[s].set_visible(False)
fig.tight_layout()
fig.savefig("chart_card.png", dpi=130)

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

# ── 대장에 옮길 값 ───────────────────────────────────────────────────
def g(y):
    return idx[GG][y]


hi, lo = rows[0], max(rows, key=lambda r: -r[f"지수_{Y1}"])
top = max(rows, key=lambda r: r[f"지수_{Y1}"])
bot = min(rows, key=lambda r: r[f"지수_{Y1}"])
n_cross = sum(r["역전"] for r in rows)

print("\n[대장] 수치대장.csv 에 옮길 값 ─────────────────")
print(f"  경기도 지수 {Y0}            {g(Y0):.1f}")
print(f"  경기도 지수 {Y1}            {g(Y1):.1f}   ({g(Y1)/g(Y0):.2f}배)")
print(f"  경기도 역전 연도            {gg_cross}")
print(f"  경기도 노년 {Y0}→{Y1}       {pop[Y0][GG][1]:,} → {pop[Y1][GG][1]:,}"
      f"  ({pop[Y1][GG][1]/pop[Y0][GG][1]:.3f}배)")
print(f"  경기도 유소년 {Y0}→{Y1}     {pop[Y0][GG][0]:,} → {pop[Y1][GG][0]:,}"
      f"  ({pop[Y1][GG][0]/pop[Y0][GG][0]:.3f}배)")
print(f"  ▸ 항등식 검산               {pop[Y1][GG][1]/pop[Y0][GG][1]:.3f} ÷ "
      f"{pop[Y1][GG][0]/pop[Y0][GG][0]:.3f} = "
      f"{(pop[Y1][GG][1]/pop[Y0][GG][1])/(pop[Y1][GG][0]/pop[Y0][GG][0]):.3f}배")
print(f"\n  역전한 시군                 {n_cross} / {len(rows)}")
for k in ("2011이전", "2013~2019", "2020~2025", "미도달"):
    ns = [r['시군'] for r in rows if r['유형'] == k]
    print(f"    {k:<10} {len(ns):>2}곳  {' '.join(ns)}")
print(f"  한 해에 가장 많이 역전       "
      + ", ".join(f"{k} {c}곳" for k, c in zip(keys, cnt) if c == max(cnt[1:])))
print(f"\n  {Y1} 최고                   {top['시군']} {top[f'지수_{Y1}']:.1f}")
print(f"  {Y1} 최저                   {bot['시군']} {bot[f'지수_{Y1}']:.1f}"
      f"   (최고÷최저 {top[f'지수_{Y1}']/bot[f'지수_{Y1}']:.2f}배)")
print(f"  아직 역전 안 한 곳           {', '.join(NONE) or '없음'}")
up = [r for r in rows if r["유소년증가"]]
print(f"  유소년이 늘어난 시군          {len(up)}곳  "
      + " ".join(f"{r['시군']} {r['유소년배율']}" for r in sorted(up, key=lambda r: -r['유소년배율'])))
print(f"\n  되돌아간 곳                 {', '.join(BACK) or '없음'}")
for nm in BACK:
    print(f"    {nm}: " + " ".join(f"{y[2:]}:{idx[nm][y]:.1f}" for y in YEARS))
