// cli/src/ingest-scores.ts
// 构建"按省按年"的省考进面分/报名人数参考库：
//   cli/raw/scores/<slug>-cutoff.csv  逐岗进面分
//   cli/raw/scores/<slug>-apply.csv   逐岗报名人数/竞争比
//   cli/raw/scores/tier-lines.json    各省分档合格分数线
// →
//   cli/data/scores/<slug>-<year>.json.gz   每省每年一个文件(懒加载)
//   cli/data/scores/index.json               可用(省,年)目录 + 计数
//
// 进面分/报名人数是"历史参考"(各省年份/计分制不同)，供考生估难度，不与当年岗位强绑定。
// Usage: npx tsx src/ingest-scores.ts
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from "node:fs";
import { gzipSync } from "node:zlib";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const RAWDIR = join(__dirname, "..", "raw", "scores");
const OUTDIR = join(__dirname, "..", "data", "scores");
function parseCSV(content) {
    const rows = [];
    let cur = [];
    let field = "";
    let q = false;
    for (let i = 0; i < content.length; i++) {
        const ch = content[i];
        if (q) {
            if (ch === '"' && content[i + 1] === '"') {
                field += '"';
                i++;
            }
            else if (ch === '"')
                q = false;
            else
                field += ch;
        }
        else {
            if (ch === '"')
                q = true;
            else if (ch === ",") {
                cur.push(field);
                field = "";
            }
            else if (ch === "\n" || ch === "\r") {
                if (ch === "\r" && content[i + 1] === "\n")
                    i++;
                cur.push(field);
                if (cur.some((x) => x !== ""))
                    rows.push(cur);
                cur = [];
                field = "";
            }
            else
                field += ch;
        }
    }
    if (field !== "" || cur.length) {
        cur.push(field);
        if (cur.some((x) => x !== ""))
            rows.push(cur);
    }
    return rows;
}
function readCSV(fp) {
    if (!existsSync(fp))
        return [];
    const rows = parseCSV(readFileSync(fp, "utf-8"));
    if (rows.length < 2)
        return [];
    const h = rows[0].map((x) => x.replace(/^﻿/, "").trim());
    return rows.slice(1).map((r) => { const o = {}; h.forEach((k, i) => (o[k] = (r[i] ?? "").trim())); return o; });
}
const num = (v) => { const n = Number(v); return Number.isFinite(n) && n !== 0 ? n : null; };
function main() {
    mkdirSync(OUTDIR, { recursive: true });
    const tierPath = join(RAWDIR, "tier-lines.json");
    const tiers = existsSync(tierPath) ? JSON.parse(readFileSync(tierPath, "utf-8")) : {};
    // slug → province 中文名(从CSV首行取)，以及按(slug,year)聚合
    const files = new Map(); // key = slug|year
    const slugProvince = {};
    for (const f of readdirSync(RAWDIR)) {
        if (!f.endsWith("-cutoff.csv") && !f.endsWith("-apply.csv"))
            continue;
        const isCut = f.endsWith("-cutoff.csv");
        const slug = f.replace(/-(cutoff|apply)\.csv$/, "");
        for (const r of readCSV(join(RAWDIR, f))) {
            const prov = r.source_province || slug;
            slugProvince[slug] = prov;
            const year = Number(r.year) || 0;
            if (!year)
                continue;
            const key = `${slug}|${year}`;
            let sf = files.get(key);
            if (!sf) {
                sf = { province: prov, year, scoring_note: "", tier_lines: "", cutoffs: [], applicants: [] };
                files.set(key, sf);
            }
            const code = r.position_code || "";
            const dept = r.dept_name || "";
            const name = r.position_name || "";
            const loc = r.work_location || "";
            if (isCut) {
                const c = num(r.cutoff_min || r.cutoff_score || "");
                if (c === null || c <= 0 || c > 300)
                    continue; // 过滤无效分
                const e = { code, dept, name, loc, cutoff: c };
                const xc = num(r.xingce || "");
                if (xc)
                    e.xingce = xc;
                const sl = num(r.shenlun || "");
                if (sl)
                    e.shenlun = sl;
                sf.cutoffs.push(e);
            }
            else {
                const a = num(r.applicants || "");
                const ratio = (r.ratio || "").trim();
                if (a === null && !ratio)
                    continue;
                const e = { code, dept, name, loc };
                if (a !== null)
                    e.applicants = a;
                if (ratio)
                    e.ratio = ratio;
                sf.applicants.push(e);
            }
        }
    }
    // 填 tier_lines(按省名)
    for (const sf of files.values()) {
        const t = tiers[sf.province];
        if (t)
            sf.tier_lines = t.tier_lines;
    }
    // 数据来源/口径标注(核验 agent team 结论)：黑龙江来源为第三方华图聚合，数值经现场API复核一致，
    // 但单位名泛化、岗位代码为顺序号、不含行测/申论分项。
    const SOURCE_NOTES = {
        "黑龙江": "进面分来源为华图聚合(非官方一手)，cutoff已与华图现场API复核一致；单位名经泛化、岗位代码为顺序号、不含行测/申论分项。建议以官方进面名单为准。",
    };
    for (const sf of files.values()) {
        if (SOURCE_NOTES[sf.province])
            sf.scoring_note = SOURCE_NOTES[sf.province];
    }
    // 写出每省每年文件 + 索引
    const index = [];
    for (const [key, sf] of files) {
        const [slug, yearStr] = key.split("|");
        if (sf.cutoffs.length === 0 && sf.applicants.length === 0 && !sf.tier_lines)
            continue;
        const out = join(OUTDIR, `${slug}-${yearStr}.json.gz`);
        writeFileSync(out, gzipSync(JSON.stringify(sf)));
        index.push({ slug, province: sf.province, year: sf.year, cutoffs: sf.cutoffs.length, applicants: sf.applicants.length, has_tier: Boolean(sf.tier_lines) });
    }
    index.sort((a, b) => a.slug.localeCompare(b.slug) || b.year - a.year);
    writeFileSync(join(OUTDIR, "index.json"), JSON.stringify(index, null, 1));
    const totC = index.reduce((s, x) => s + x.cutoffs, 0);
    const totA = index.reduce((s, x) => s + x.applicants, 0);
    console.log(`写出 ${index.length} 个省-年文件到 data/scores/`);
    console.log(`进面分总行 ${totC}；报名总行 ${totA}；分档线省 ${Object.keys(tiers).length}`);
    console.log(`覆盖省(去重): ${new Set(index.map((x) => x.province)).size}`);
}
main();
