# -*- coding: utf-8 -*-
"""Исследование «Life path по 35 064 датам» (карточка t_d4e5c690, 18.10.2026) — страница /research/life-path-statistics/.
Каждая дата 1930-01-01 … 2025-12-31 считается один раз (равный вес; это не рождаемость).
Способы счёта:
  parts — месяц, день и год сводятся по отдельности (11/22/33 сохраняются), сумма сводится с сохранением 11/22/33
          (основной способ сайта, тот же код, что src/lib/lifepath.ts);
  sum   — сумма всех восьми цифр даты, сводится с сохранением 11/22/33;
  plain — то же без мастер-чисел (до 1–9).
Пишет src/data/lifepath-research.json и public/research/*.csv. Запуск: python scripts/lifepath_research.py
Цифры страницы — только отсюда. Версия данных меняется при любом изменении метода или окна."""
import csv, json, os
from collections import Counter, defaultdict
from datetime import date, timedelta

VERSION = "1.0"
GENERATED = "2026-10-18"
MASTER = {11, 22, 33}
NUMS = list(range(1, 10)) + [11, 22, 33]
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")


def ds(n):
    return sum(int(c) for c in str(n))


def red(n, keep=True):
    while n > 9 and not (keep and n in MASTER):
        n = ds(n)
    return n


def parts(d):
    M, D, Y = red(d.month), red(d.day), red(ds(d.year))
    return M, D, Y, M + D + Y, red(M + D + Y)


def by_sum(d):
    a = ds(d.month) + ds(d.day) + ds(d.year)
    return a, red(a)


def days(a, b):
    d = a
    while d <= b:
        yield d
        d += timedelta(days=1)


def pct(x, n, nd=1):
    return round(100 * x / n, nd)


def window(a, b):
    """33 и расхождение способов в любом окне — для таблицы «почему цифры в сети разные»."""
    n = c33p = c33s = diff = 0
    for d in days(a, b):
        n += 1
        t = parts(d)[4]
        s = by_sum(d)[1]
        c33p += t == 33
        c33s += s == 33
        diff += t != s
    return {"from": str(a), "to": str(b), "dates": n, "33_parts": c33p, "33_parts_pct": pct(c33p, n, 2),
            "33_sum": c33s, "33_sum_pct": pct(c33s, n, 2), "differ": diff, "differ_pct": pct(diff, n, 1)}


START, END = date(1930, 1, 1), date(2025, 12, 31)
total = 0
cp, cs, cplain = Counter(), Counter(), Counter()
pair = Counter()                 # (parts, sum) для дат, где способы расходятся
dec_p, dec_s, dec_n = defaultdict(Counter), defaultdict(Counter), Counter()
dec_diff = Counter()
month_p, month_n = defaultdict(Counter), Counter()
year_p, year_s, year_n, year_diff = defaultdict(Counter), defaultdict(Counter), Counter(), Counter()
routes33 = Counter()             # (M, D, Y) у дат с 33 по частям
compound = Counter()             # сумма до последнего шага (по частям), напр. 16 → 16/7
compound_total = defaultdict(Counter)
rows = []
for d in days(START, END):
    M, D, Y, s, t = parts(d)
    a, b = by_sum(d)
    p = red(a, keep=False)
    total += 1
    cp[t] += 1; cs[b] += 1; cplain[p] += 1
    dec = d.year // 10 * 10
    dec_p[dec][t] += 1; dec_s[dec][b] += 1; dec_n[dec] += 1
    month_p[d.month][t] += 1; month_n[d.month] += 1
    year_p[d.year][t] += 1; year_s[d.year][b] += 1; year_n[d.year] += 1
    if t != b:
        pair[(t, b)] += 1; dec_diff[dec] += 1; year_diff[d.year] += 1
    if t == 33:
        routes33[(M, D, Y)] += 1
    compound[s] += 1
    compound_total[t][s] += 1
    rows.append((d.isoformat(), d.month, d.day, d.year, M, D, Y, s, t, a, b, int(t != b)))

differ = sum(pair.values())
pair_out = {}
for (t, b), v in sorted(pair.items(), key=lambda x: -x[1]):
    pair_out[f"{t}->{b}"] = {"parts": t, "sum": b, "count": v, "pct_of_all": pct(v, total, 2), "pct_of_differ": pct(v, differ, 1)}
# Пары семей: 2/11, 4/22, 6/33 — в обе стороны
fam = {"2/11": 0, "4/22": 0, "6/33": 0}
for (t, b), v in pair.items():
    key = {frozenset((2, 11)): "2/11", frozenset((4, 22)): "4/22", frozenset((6, 33)): "6/33"}.get(frozenset((t, b)))
    if key is None:
        raise SystemExit(f"unexpected pair {t}/{b}")
    fam[key] += v

# Маршруты к 33: какая часть даты даёт мастер-число
route_family = Counter()
for (M, D, Y), v in routes33.items():
    if Y == 22:
        route_family["year digits add up to 22"] += v
    elif D == 22:
        route_family["day is the 22nd"] += v
    elif M == 11 and D == 11 and Y == 11:
        route_family["month, day and year are all 11"] += v
    else:
        route_family[f"other {M}+{D}+{Y}"] += v
years22 = [y for y in range(START.year, END.year + 1) if ds(y) == 22]
# 33 после 2000 года: какими путями (год 20xx не даёт сумму цифр 22)
routes33_since2000 = Counter()
max_all_since2000 = 0
for d in days(date(2000, 1, 1), END):
    M, D, Y, s, t = parts(d)
    max_all_since2000 = max(max_all_since2000, by_sum(d)[0])
    if t == 33:
        routes33_since2000["day is the 22nd" if D == 22 else ("month, day and year are all 11" if (M, D, Y) == (11, 11, 11) else f"{M}+{D}+{Y}")] += 1

impossible = {str(m): [k for k in (22, 33) if month_p[m][k] == 0] for m in range(1, 13)}

out = {
    "version": VERSION, "generated": GENERATED, "from": str(START), "to": str(END), "dates": total,
    "leap_days": sum(1 for d in days(START, END) if d.month == 2 and d.day == 29),
    "counts": {m: {str(k): c[k] for k in NUMS if c[k]} for m, c in (("parts", cp), ("sum", cs), ("plain", cplain))},
    "share": {m: {str(k): pct(c[k], total, 2) for k in NUMS if c[k]} for m, c in (("parts", cp), ("sum", cs), ("plain", cplain))},
    "differ": differ, "differ_pct": pct(differ, total, 1), "differ_pct2": pct(differ, total, 2),
    "differ_pairs": pair_out, "differ_families": {k: {"count": v, "pct_of_all": pct(v, total, 2), "pct_of_differ": pct(v, differ, 1)} for k, v in fam.items()},
    "agree_numbers": [k for k in NUMS if k not in MASTER and all(k not in key for key in pair)],
    "decades": {str(dec): {"dates": dec_n[dec], "33_parts": dec_p[dec][33], "33_parts_pct": pct(dec_p[dec][33], dec_n[dec], 2),
                           "33_sum": dec_s[dec][33], "33_sum_pct": pct(dec_s[dec][33], dec_n[dec], 2),
                           "22_parts_pct": pct(dec_p[dec][22], dec_n[dec], 2), "11_parts_pct": pct(dec_p[dec][11], dec_n[dec], 2),
                           "differ_pct": pct(dec_diff[dec], dec_n[dec], 1)} for dec in sorted(dec_n)},
    "months": {str(m): {str(k): pct(month_p[m][k], month_n[m], 2) for k in NUMS} for m in range(1, 13)},
    "months_impossible": impossible,
    "routes33": dict(route_family.most_common()),
    "routes33_since2000": dict(routes33_since2000.most_common()),
    "max_all_digits_since2000": max_all_since2000,
    "months_33": {str(m): month_p[m][33] for m in range(1, 13)},
    "all11_dates": sorted(r[0] for r in rows if (r[4], r[5], r[6]) == (11, 11, 11)),
    "year_2026_33": sum(1 for d in days(date(2026, 1, 1), date(2026, 12, 31)) if parts(d)[4] == 33),
    "year_2027_33": sum(1 for d in days(date(2027, 1, 1), date(2027, 12, 31)) if parts(d)[4] == 33),
    "year_2027_33_dates": [str(d) for d in days(date(2027, 1, 1), date(2027, 12, 31)) if parts(d)[4] == 33],
    "pre2000_routes33": {"year digits add up to 22": route_family["year digits add up to 22"],
                         "day is the 22nd": route_family["day is the 22nd"] - routes33_since2000["day is the 22nd"]},
    "example": (lambda d: {"date": str(d), "M": parts(d)[0], "D": parts(d)[1], "Y": parts(d)[2], "sum": parts(d)[3], "parts": parts(d)[4],
                           "all": by_sum(d)[0], "all_lp": by_sum(d)[1]})(date(1980, 4, 29)),
    "years_33": {str(y): year_p[y][33] for y in sorted(year_n)},
    # Для инструмента страницы: сколько дней каждого года дают каждое число (по частям), порядок — NUMS
    "nums": NUMS,
    "years_parts": {str(y): [year_p[y][k] for k in NUMS] for y in sorted(year_n)},
    "years_days": {str(y): year_n[y] for y in sorted(year_n)},
    "masters_parts_pct": pct(cp[11] + cp[22] + cp[33], total, 2),
    "masters_sum_pct": pct(cs[11] + cs[22] + cs[33], total, 2),
    # Доля результатов «все цифры = мастер-число», которые основной способ переводит в корень (6 вместо 33 и т. п.)
    "flip_from_sum": {str(m): {"sum_dates": cs[m], "parts_root": pair[(red(m, False), m)], "pct": pct(pair[(red(m, False), m)], cs[m], 1)} for m in (11, 22, 33)},
    "flip_from_parts": {str(m): {"parts_dates": cp[m], "sum_root": pair[(m, red(m, False))], "pct": pct(pair[(m, red(m, False))], cp[m], 1)} for m in (11, 22, 33)},
    "families": {str(r): {"root": cp[r], "master": cp[m], "pct": pct(cp[r] + cp[m], total, 2)} for r, m in ((2, 11), (4, 22), (6, 33))},
    # Даты, где мастер-число дают оба способа
    "master_both": {str(m): cp[m] - pair[(m, red(m, False))] for m in (11, 22, 33)},
    "max_year_digit_sum_since2000": max(ds(y) for y in range(2000, END.year + 1)),
    "next_year_digits_22": next(y for y in range(2000, 3000) if ds(y) == 22),
    "years_digits_22": years22,
    "years_33_zero": [y for y in sorted(year_n) if year_p[y][33] == 0],
    "years_33_max": max(sorted(year_n), key=lambda y: year_p[y][33]),
    "years_33_max_count": max(year_p[y][33] for y in year_n),
    "before2000_33_pct": pct(sum(year_p[y][33] for y in year_n if y < 2000), sum(year_n[y] for y in year_n if y < 2000), 2),
    "since2000_33_pct": pct(sum(year_p[y][33] for y in year_n if y >= 2000), sum(year_n[y] for y in year_n if y >= 2000), 2),
    "since2000_33_count": sum(year_p[y][33] for y in year_n if y >= 2000),
    "since2000_dates": sum(year_n[y] for y in year_n if y >= 2000),
    "compound": {str(k): {"count": v, "pct": pct(v, total, 2), "to": red(k)} for k, v in sorted(compound.items())},
    "windows": [window(date(1900, 1, 1), date(2025, 12, 31)), window(date(1900, 1, 1), date(2099, 12, 31)),
                window(START, END), window(date(2000, 1, 1), date(2025, 12, 31)), window(date(2000, 1, 1), date(2014, 12, 31))],
}
# Самая частая и самая редкая (по частям)
order = sorted(NUMS, key=lambda k: -cp[k])
out["most_common"] = [k for k in NUMS if cp[k] == cp[order[0]]]
out["top_tie"] = [k for k in NUMS if k not in MASTER and abs(cp[k] - cp[order[0]]) <= 6]
out["rarest"] = order[-1]
out["rank_parts"] = order
out["ratio_top_33"] = round(cp[order[0]] / cp[33], 1)

os.makedirs(os.path.join(ROOT, "public", "research"), exist_ok=True)
with open(os.path.join(ROOT, "src", "data", "lifepath-research.json"), "w", encoding="utf-8") as f:
    json.dump(out, f, ensure_ascii=False, indent=1)
with open(os.path.join(ROOT, "public", "research", "life-path-by-date-1930-2025.csv"), "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["date", "month", "day", "year", "month_reduced", "day_reduced", "year_reduced", "parts_total", "life_path_parts",
                "all_digits_total", "life_path_all_digits", "methods_differ"])
    w.writerows(rows)
with open(os.path.join(ROOT, "public", "research", "life-path-summary-1930-2025.csv"), "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["life_path", "dates_parts", "share_parts_pct", "dates_all_digits", "share_all_digits_pct"])
    for k in NUMS:
        w.writerow([k, cp[k], out["share"]["parts"].get(str(k), 0), cs[k], out["share"]["sum"].get(str(k), 0)])
with open(os.path.join(ROOT, "public", "research", "life-path-by-year-1930-2025.csv"), "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["year", "dates"] + [f"lp{k}_parts" for k in NUMS] + ["lp33_all_digits", "methods_differ"])
    for y in sorted(year_n):
        w.writerow([y, year_n[y]] + [year_p[y][k] for k in NUMS] + [year_s[y][33], year_diff[y]])
# Сам скрипт тоже выкладывается рядом с CSV: страница обещает, что расчёт можно повторить.
import shutil
shutil.copyfile(os.path.abspath(__file__), os.path.join(ROOT, "public", "research", "lifepath_research.py"))

print("dates", total, "differ", differ, out["differ_pct2"], fam)
print("parts", out["counts"]["parts"])
print("sum", out["counts"]["sum"])
print("most common", out["most_common"], "top tie", out["top_tie"], "rarest", out["rarest"], "ratio", out["ratio_top_33"])
print("33 before/after 2000", out["before2000_33_pct"], out["since2000_33_pct"], out["since2000_33_count"], out["since2000_dates"])
print("routes33", out["routes33"], "years22", years22, "since2000", out["routes33_since2000"], "max all-digit sum since 2000", max_all_since2000)
print("months 33", out["months_33"], "all11", out["all11_dates"], "2026/2027", out["year_2026_33"], out["year_2027_33"], "masters", out["masters_parts_pct"], out["masters_sum_pct"])
print("flip", out["flip_from_sum"], out["flip_from_parts"], "families", out["families"], "max year sum since 2000", out["max_year_digit_sum_since2000"])
print("impossible", {k: v for k, v in impossible.items() if v})
print("years 33 zero", out["years_33_zero"][:10], len(out["years_33_zero"]), "max", out["years_33_max"], out["years_33_max_count"])
for wnd in out["windows"]:
    print("window", wnd)
for dec, v in out["decades"].items():
    print(dec, v)
print("compound top", sorted(out["compound"].items(), key=lambda x: -x[1]["count"])[:5], "rare", sorted(out["compound"].items(), key=lambda x: x[1]["count"])[:5])
