career-ops calibrate 模式实战:用确定性校准报告验证评分体系是否真的预测了求职结果
career-ops calibrate 模式实战用确定性校准报告验证评分体系是否真的预测了求职结果【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops本篇基于 career-ops 仓库的 calibrate 模式定义 及其核心脚本 calibrate.mjs讲清这个学习闭环的完整工作机制如何运行node calibrate.mjs --json生成分数段score band× 面试率 / offer 率的校准报告如何解读 separating / flat / inverted / insufficient 四种判定以及脚本在证据优先级、小样本底线honest floors、在途申请剔除等细节上的确定性实现。读完你能独立完成一次自我校准知道报告的每个数字从哪来、哪些数字被刻意 withheld、以及这个模式为什么只报告证据、从不改规则。一、calibrate 模式在项目中的定位闭合评估 → 结果的学习闭环career-ops 的整体工作流是扫描职位 → 生成 A–H 结构化评估报告并给出 1–5 分 → 按分数决定投递 → 记录结果。calibrate 模式对应模式表中的这一行见 modes/README.mdcalibrate— Advisory report: do your evaluation scores predict your real outcomes? Reads/outcomedata; never changes scoring它解决的核心问题在 modes/calibrate.md 的标题里就直接写明了评分是否预测了你自己的结果Does the scoring predict YOUR outcomes?。此前评分只负责事前排序但从没有回头核对高分投递是否真的转化得更好。calibrate 闭合的正是这条学习闭环issue #1724由 /outcome 模式 记录到data/outcomes/的结果日志会被读回来与当初投递前的评估分数做对照分析。与 /outcome 模式 的分工是明确的outcome 是写入端对话式记录结果类型、阶段、逐字反馈并把档案归档到data/outcomes/{num}_{company_slug}_{role_slug}/calibrate 是读取端聚合这些日志与 tracker输出校准统计。写入端支持的结果类型与 CLI 参数interview_progress、offer_received、hired、offer_declined、rejected、no_response、interview_only等在 outcome 模式文档中有完整定义calibrate 报告中的每一个计数都以这些数据为原材料。二、三条不可协商的约束Non-negotiablesmodes/calibrate.md 把模式的边界浓缩为三条硬性约束这三条贯穿了脚本的全部实现Advisory only仅建议这个模式永不编辑评分规则、阈值、modes/_shared.md或任何配置。它报告证据用户自行决定如何使用。calibrate.mjs 的文件头注释同样声明Strictly advisory. This script never edits scoring rules, thresholds, modes/_shared.md, or any user file — it reads, aggregates, and reports.从源码看整个脚本只调用了readFileSync/readdirSync/existsSync没有任何写文件操作。Deterministic确定性所有数字来自calibrate.mjs的纯本地解析——无网络、无 API key、无 LLM 计算。Agent 呈现报告时不得重新计算、调整或优化脚本打印的任何比率。Honest floors诚实底线脚本把某个比率标记为(n too small)时就按原样呈现。绝不允许把3 个里成了 2 个的轶事包装成百分比。三、运行方式与完整参数模式文档给出的标准调用是node calibrate.mjs --json结合 calibrate.mjs 文件头的 Usage 说明与 CLI 入口实现完整的运行形态如下命令行为node calibrate.mjs输出人类可读报告renderHumannode calibrate.mjs --json向 stdout 输出完整 JSON供 Agent/模式流程消费node calibrate.mjs --min-band-n 5调整每段的样本量下限floor默认 5必须是 ≥1 的整数否则以退出码 2 报错node calibrate.mjs --self-test运行内置自测覆盖聚合逻辑的各分支不触碰文件系统CLI 入口的执行顺序见 calibrate.mjs 末尾的 main guard 部分若带--self-test先跑自测解析--min-band-n非法值直接报错退出通过resolveTrackerPath()定位 trackerdata/applications.md若 tracker 不存在打印No tracker found at ... — nothing to calibrate yet.并退出——模式文档要求此时如实告知用户并指向/outcome闭环需要先有记录的结果才谈得上任何结论不要用猜测去填补空白依次加载 tracker 行、结果日志journals、阶段台账ledger调用核心聚合computeCalibration()最后按--json与否输出。四、报告结构与呈现顺序模式文档规定呈现必须按以下顺序这也是renderHuman()的实际输出顺序判定句Verdict逐字呈现——它是报告的主标题。四种判定separating评分对你是有区分力的——高分段面试率显著高于低分段相信分数正在得到回报flat尚无明显区分样本积累后通常会朝某个方向收敛inverted信号反转——低分段转化得比高分段更好此时不应继续盲信下一个 4.5 分而应先回读高分被拒报告的共性insufficient已解决的结果不足以比较分数段提示继续用/outcome记录并重跑。分数段表score band × n × 面试率 × offer 率按脚本打印的原样呈现在途in-flight计数仍处于 Applied/Responded 且未记录结果的申请——这正是总数与 tracker 行数对不上的原因已记录的反馈信号如有把这些反馈当作关于你这次求职的数据来引用若多条反馈出现同一模式例如同一个技能缺口被两次点名用一句话指出它。一个renderHuman生成的报告骨架如下数字为示意 Calibration — your scores vs your real outcomes Resolved outcomes: 12 · in flight (not counted): 5 · unscored: 2 | Score band | n | Interviews | Offers | Interview rate | Offer rate | |---|---|---|---|---|---| | 3.5 | 3 | 0 | 0 | (n too small) | | | 3.5-3.9 | 5 | 2 | 0 | 40% | 0% | | 4.0-4.4 | 4 | 2 | 1 | 50% | 25% | | 4.5 | 0 | 0 | 0 | (n too small) | | Verdict: No meaningful separation yet: ... Signals from recorded feedback (latest 10): - #7 Acme: Strong system design, gaps in Go depth This report is advisory. It never changes scoring rules — that conversation belongs to you.五、源码纵深分数段、样本底线与判定阈值5.1 四个固定分数段分数段的划分硬编码在 calibrate.mjs 中const BANDS [ { key: 3.5, min: -Infinity, max: 3.5 }, { key: 3.5-3.9, min: 3.5, max: 4.0 }, { key: 4.0-4.4, min: 4.0, max: 4.5 }, { key: 4.5, min: 4.5, max: Infinity }, ];对应 career-ops 全局 1–5 分制评估报告的分数输出4.4/5这类单元格形式会被parseFloat正确解析——它在斜杠处停止空白或文本分数变成null而不是 0避免一行无分申请被静默落进最低分段。5.2 样本底线(n too small)的实现computeCalibration()对每个分数段的处理calibrate.mjs段内样本数n ≥ minBandN默认 5时输出四舍五入的百分比比率不足时计数仍会显示读者仍能看到原始轶事但比率被置为null渲染为(n too small)。这就是Honest floors约束的代码形态小样本下拒绝制造伪比率。5.3 判定逻辑±10 个百分点的判定带判定只比较通过样本底线的最高段与最低段calibrate.mjs不足两个可比段 →insufficient这不是比较就明说差值gap 高段面试率 − 低段面试率gap ≥ 10→separatinggap ≤ −10→inverted其余 →flat。--min-band-n因此是灵敏度旋钮样本少时调低它能看到更多比率代价是离诚实底线更近——这正是模式文档要求脚本 withheld 就照实呈现的原因。六、源码纵深三源证据的优先级这是 calibrate 最有信息量的部分。每一行申请的结果可能来自三个来源优先级固定calibrate.mjs结果日志journal——data/outcomes/{...}/outcome.md由outcome.mjs写入记录实际发生了什么优先级最高阶段台账ledger——data/status-log.tsv由set-status.mjs写入的状态迁移流水记录一行经过过哪些阶段。因此一个已拒 offer 现在停在 Discarded 的行仍计为达到过 offer一次以拒绝告终的面试仍计为达到过面试tracker 当前状态——只是工作流快照会丢失历史因此优先级最低。6.1 结果类型的两层语义日志侧的语义表JOURNAL_OUTCOMEScalibrate.mjs把七种规范结果映射到两个累积层级结果类型reachedInterview一级reachedOffer二级terminalhired/offer_received/offer_declined✓✓✓interview_progress✓✗✗interview_only✓✗✓rejected/no_response✗✗✓注意两个容易误读的点拒掉 offer 仍算二级达成offer_declined 证明评分选中了一个能匹配的角色interview_only面完无果仍算一级达成。tracker 侧的兜底表TRACKER_TERMINAL同理只有interview/offer/hired/rejected能独立定论applied/responded判定为在途evaluated/discarded等无阶段历史的行则完全在校准人群之外。6.2 日志解析最后一条 entry 才是真相parseOutcomeJournal()calibrate.mjs按## Entry:切分 append-only 日志关键契约是最后一条 entry 代表当前真相——一个先interview_progress后rejected的申请必须读作 rejected而不是它历史上最幸福的时刻。实现上只要存在 Outcome Type 字段就无条件覆盖latestType包括解析为 null 的情况防止最后一条无法识别的 entry 让上一条幸存无法识别时回落到 tracker 状态这才是诚实答案。反馈只收集引用的逐字内容跳过None recorded空标记。6.3 在途剔除为什么总数对不上 tracker 行数applied/responded且无日志结果的行被推入inFlight并从所有比率中排除。源码注释解释得很直白把它算作失败会惩罚最近的申请算作成功会美化一切——它既不是数据点也不该消失所以单独计数呈现。这正对应模式文档中呈现 in-flight count的固定步骤。6.4 日志目录的分裂与合并日志目录形如{num}_{company_slug}_{role_slug}。在早期版本中目录名由 tracker 行当时的文本派生两次记录之间编辑了 Role 单元格比如把 Senior Backend Engineer 规范成 Sr. Backend Engineer第二条 entry 就会落到新目录一个申请被劈成两个半份日志。现在由 lib/outcome-dir.mjs 的outcomeDirsFor()按行号前缀收集全部候选目录按 outcome.md 的 mtime 最新者优先排序calibrate.mjs读取dirs[0]若确实存在分裂dirs.length 1会在结果中标记splitAcross报告而非静默修复——修复意味着移动用户档案一个只读报告不该做这件事。回归测试 tests/outcome-journal-fork.test.mjs 用真实 CLI 复现了两次记录之间编辑 Role的场景断言目录数始终为 1 且 append-only 历史完整保留。七、单一结果词汇表防止读写两端漂移outcome.mjs接受的结果类型会原样写入日志。任何读取日志的一方必须接受写入端接受的全部拼写否则记录的结果对它就是隐形的。lib/outcome-types.mjs 是这一词汇表的唯一事实源OUTCOME_MAP共14 种接受拼写 → 各自的 tracker 状态与默认备注CANONICAL_OUTCOMES是其中7 种规范名outcome.mjs的 USAGE 行宣称的集合其余 7 种是别名offer→offer_received、declined→offer_declined、ghosted→no_response、accepted→hired、rejection→rejected、interview/stage_reached→interview_progress由canonicalOutcome()统一解析小写、-归一为_所以手改日志里写的Offer-Declined也能解析。这个设计有一个血泪背景lib/outcome-types.mjs 的注释与 tests/outcome-vocabulary-drift.test.mjs 都记录了calibrate 曾携带一份私有的 7 项词汇副本导致declined、ghosted在校准中彻底消失——而拒掉 offer恰恰是评分预测良好最有力的证据之一。现在该模块在加载时做覆盖率检查OUTCOME_MAP 里出现的新拼写若无规范含义会直接 throw漂移测试则双向断言写入端接受的每种拼写都必须能解析到 calibrate 的JOURNAL_OUTCOMES中的一个键且JOURNAL_OUTCOMES恰好等于规范集合不多不少。八、Tracker 列映射一个曾导致全体静默掉出人群的坑loadTrackerRows()依赖 tracker-parse.mjs 的resolveColumns()定位表头。该函数期望整行数组自己在其中找表头早期版本误传单个表头字符串导致逐字符迭代找不到表头静默回落到无 Via 列的 9 列旧版列映射。后果在带可选 Via 列位于 Company 与 Role 之间#1596的 tracker 上最典型Role 起所有字段左移一格读取status读到的是分数单元格4.5/5没有任何状态能匹配每一行都静默掉出人群——报告呈现0 resolved / 0 in-flight和一条虚假的数据不足判定用户却以为自己只是历史还不够。回归测试 tests/calibrate-tracker-colmap.test.mjs 同时覆盖带 Via 的 10 列 tracker 与旧版 9 列 tracker断言 status、score、company 均读到正确列。九、这个模式永远不做的事Must-never 清单modes/calibrate.md 末尾的禁止清单值得逐条内化因为它定义了 Agent 与校准数据的正确相处方式不建议编辑modes/_shared.md或任何评分规则。如果用户问那我是不是该改评分诚实的回答是全局评分体系保持不变——证据支持的是调整你自己的投递阈值与目标组合这是用户的决定。不把校准结果自动回灌到评估中。career-ops 中任何地方都没有 auto-tuning这个模式也不会引入它。同时inverted判定下的正确后续动作也写得很清楚读一读高分被拒报告的共性——这是一段对话可以逐份与用户过那些具体报告而不是自动重打分。十、验证与回归保障围绕 calibrate 的确定性契约仓库提供了三层验证内置自测node calibrate.mjs --self-test直接对内存数据驱动parseOutcomeJournal/computeCalibration覆盖最后一条 entry 胜出、separating/inverted/insufficient 判定、小样本 withheld、journal 优先于 tracker、在途与未投递行排除、ledger 证据解析等分支calibrate.mjs。列映射回归tests/calibrate-tracker-colmap.test.mjs 锁定loadTrackerRows对两种 tracker 形态的解析。词汇漂移防护tests/outcome-vocabulary-drift.test.mjs 从OUTCOME_MAP派生全量拼写普查新别名加入写入端的当天就会被覆盖到。CLI 入口还有一个工程细节--self-test放在 main guardlib/is-main-module.mjs 的isMainModule检查之内——否则任何导入本模块做单测的测试进程都会对真实 tracker 跑一遍完整 CLI。十一、实操建议什么时候跑 calibrate怎么用它结合文档与源码实用的使用节奏是前提trackerdata/applications.md存在且已用/outcome记录了若干结果。每段需 ≥5默认 floor个已解决的结果两个分数段达标才有可比性——也就是说积累到约 10 个已闭环申请后报告才开始有判定价值。每次收到新结果后重跑node calibrate.mjs。判定从insufficient→flat→separating/inverted的演进本身就是求职数据的累积过程。separating维持现有策略分数可作为投递优先级信号flat继续记录不做任何规则改动inverted最值得花时间——按模式文档的建议把高分被拒的报告拉出来与用户一起过找出高分假设与真实结果的系统性偏差如目标公司类型错配随后调整的是个人投递阈值与目标组合而非全局评分。需要牢记的是这个模式的性质它输出的是评分体系在你身上表现如何的证据快照是本地、确定、可复算的同一份 tracker 日志 台账任何时候重跑结果一致。career-ops 用读取、聚合、报告、绝不回写这一设计把校准从一条可能自我强化的自动化回路变成了一个人主导的反思工具。【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考