Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

139 stars

Watchers

0 watching

Forks

Repository files navigation

🍔 Git 提交与巨无霸热量对冲器

把今天写下的每一行代码,兑换成一顿刚刚好的麦当劳。

CI Node Dependencies Tests Coverage License

努力了一天,应该好好放松。 你今天的努力没有被记在 OKR 里,也没有出现在周报上——但它确实发生了。 我们把它从 Git 里挖出来,折算成一顿刚好配得上今天的麦当劳。

读取今天的 Git 提交 → 评估这一天的分量(L1~L4)→ 算出热量上限 → 从真实门店菜单与优惠里挑出一份「性价比最高 / 折扣力度最大」的套餐 → 打印一张《赛博治愈对冲账单》。

零依赖,node index.js --demo all 即可跑通全流程。


目录


一、技术摘要

给只想看结论的读者(评审、面试官、新贡献者):

  • 问题:开发者的日投入缺乏客观量尺,导致「过度补偿」与「不敢犒赏」两种坏结局。
  • 方法:用 Git 增删行数与提交次数构造工作量分数,映射到四个等级与固定热量预算,再在预算约束下对真实商品做多目标排序。
  • 模型:Score = 新增×1.0 + 删除×1.5 + 提交×30;等级阈值 100 / 500 / 1200;匹配权重 折扣 0.40 / 性价比 0.35 / 利用率 0.25。
  • 工程:零第三方依赖,纯函数内核 + 副作用隔离在接口层,24 个单元测试,src 层行覆盖率 92.3%,CI 覆盖 Node 18/20/22。
  • 可复现:结算读仓库内快照而非实时请求,无凭据、无网络也能得到完全一致的结论;快照带 capturedAt 与 traceId,可逐条对账。

深入阅读:架构说明 · 算法与实验


二、问题与定位

程序员的一天是高度非线性的。写了 13 个 commit 的那天和写了 2 个的那天,身体感受完全不同,但日历上看起来一模一样。

于是出现两种坏结局:

  • 过度补偿:今天只改了三行文档,晚上却点了全家桶,负罪感到凌晨两点。
  • 不敢犒赏:今天线上着火救了一整天,最后只吃了碗泡面,觉得"也没干什么"。

这两种情况是同一个问题:没有一个客观的量尺,去衡量"今天值多少放松"。

工时是自报的,会撒谎;Git 是副产品,不会。这个项目就用 Git 里的增删行数,给今天的努力定个价。

需要说明的是,行数只是可自动获取的最优代理指标,它区分量级而不区分难度。这既是这个模型的价值,也是它最大的局限——见第十一节。


三、快速开始

不需要 npm install,没有任何第三方依赖。

# 1. 看看今天(当前 Git 仓库,最近 24 小时)
node index.js

# 2. 没有提交也能看效果:跑一遍内置的四档演示
node index.js --demo all

# 3. 单独看某一档
node index.js --demo l4

# 4. 把 git 统计贴进来算(不用是 Git 仓库)
git log --shortstat -3 | node index.js --input -

# 5. 导出一份 Markdown 账单 / MCP 调用记录
node index.js --demo l3 --out report.md
node index.js --demo all --record workbuddy.md

想先确认环境没问题:

npm run verify      # 自检 + 24 个单元测试
npm run test:coverage

四、工作原理

flowchart LR
    A["Git 采集<br/>增 / 删 / 提交数"] --> B["工作量评分<br/>权重 1.0 / 1.5 / 30"]
    B --> C["L1–L4 定档<br/>阈值 100 / 500 / 1200"]
    C --> D["套餐匹配<br/>硬约束 + 三目标排序"]
    D --> E["赛博治愈对冲账单"]
Loading

4.1 四个层次:从微光到拯救世界

等级 名称 English 判定分数 热量上限 判词
L1 微光轻步 Chill Score < 100 400 kcal 今天轻松搞定,维持充沛状态。
L2 踏实前行 Normal 100 ≤ Score < 500 700 kcal 踏实推进业务,脑力平稳燃烧。
L3 破浪攻坚 Hardcore 500 ≤ Score < 1200 1100 kcal 攻克核心难点,身体急需碳水补充!
L4 拯救世界 Legendary Score ≥ 1200 2000 kcal(可用 --unlimited 切到不限) 拯救屎山代码,宇宙级功德!今晚热量彻底解禁!

区间左闭右开,额度是固定档位值——不做档内插值。判定只看分数落在哪一档,越简单越不容易解释不清。

4.2 分数怎么来的

Score = 新增行数 × 1.0  +  删除行数 × 1.5  +  提交次数 × 30

删除权重 1.5 高于新增 1.0:重构和消灭屎山比堆新代码更费心血。删掉 500 行祖传代码的人,值得比加了 500 行脚手架的人多吃一点。

为什么提交权重是 30,而不是规范建议的 50?

这是本项目对业务规范唯一一处数值调整,理由是档位可达性。按 50 标定,提交次数的得分会碾压行数:

提交次数 ×50 落入档位 ×30 落入档位
1 50 L1 30 L1
2 100 L2 60 L1
3 150 L2 90 L1
4 200 L2 120 L2

×50 时,2 次提交(哪怕每行都是空行)就直接进 L2,L1 实际只剩 0~1 次提交,几乎不可达。 而「今天只改了个 typo,正常下班」正是 L1 这个档位存在的唯一理由。

改量的临界点可以精确求出:L1 演示场景为 18 增 / 4 删 / 2 提交,s = 24 + 2·cw,s ≥ 100 ⟺ cw ≥ 38。取 30 保留了 8 分余量。权重常量在 src/effort-scorer.js 的 WEIGHTS 里,一改就生效,测试会立刻告诉你档位漂了没有。

完整的标定表与敏感性实验见 ALGORITHM.md §2。

三个信号不计分,只作展示口径

初版曾把「深夜提交 +12 / 周末 +10 / 热修 +25」算进分数。改版后全部移出评分,只在账单的补充说明里出现一句:

另:深夜提交 2 次、周末提交 13 次、热修/回滚 3 次——这些不计入得分,但今天确实更累。

原因很简单:计分会造成双重惩罚。热修那天通常行数本来就多,再叠一个 25 分/次的加成,会把 L4 推到几千分去,让 1200 这个门槛失去意义;而「深夜」取决于你的时区与作息习惯,把它算进客观分里是不公平的。把它降级为一句人话提示,信息没丢,标定反而干净了。

4.3 怎么挑套餐:不是最便宜,是刚刚好

拿到热量上限后,程序读 data/live/ 里的 mcd-mcp 真实快照(菜单价格、营养热量、套餐成分、可领优惠券),拼成一份可核算的商品目录,然后逐个打分:

综合评分 = 0.40 × 折扣力度          ← (原价 − 现价) / 原价,真的省钱
         + 0.35 × 性价比            ← kcal / 元,按候选集内最大值归一
         + 0.25 × 额度利用率        ← kcal / 热量上限

硬约束:总热量 ≤ 今日上限。 超了直接排除,并明确告诉你差多少(账单里的「差一点点」)。

  • 为什么留 25% 给额度利用率:纯按折扣排序会退化成「买最便宜的打折品」——L4 拿着 2000 kcal 的额度,被推荐一杯 9.9 元的冰美式,这是明显的产品事故。
  • 为什么要有「点两份」:门店最顶的套餐是 全明星双人分享餐八件套(2142 kcal),超出 L4 的 2000 上限;次一档只有 949 kcal,L4 的额度永远用不到一半。于是匹配器会给每个打折套餐额外生成一个 ×2 候选(热量、价格、优惠同倍放大,折扣率不变),只有放得进额度的才会进入候选集。实测 L4 从「949 kcal / 47%」变成「1842 kcal / 92%」,而 L3 装不下双份,结论稳定停在单份。
  • 热量是算出来的,不是估的:真实接口不给套餐热量,只给成分。query-meal-detail 取默认选配 → 逐项用商品名回查 list-nutrition-foods 的 energyKcal → 乘以份数累加。名字对不上就排除该商品,绝不猜一个数字。本轮有 20 个商品因此被排除,账单里会写明「已排除 20 个无法核算热量的商品,不做估算」。

五、系统设计

四层,依赖单向向下,领域层不碰 I/O:

层 模块 职责
接口层 index.js 参数解析、编排、导出
数据接入层 src/git-analyzer.js、src/catalog.js Git 子进程、快照读取与归一化
领域层 src/effort-scorer.js、src/levels.js、src/meal-matcher.js 评分、分档、约束下的选择(纯函数)
表现层 src/report.js、src/mcp-trace.js 账单与调用记录渲染(纯函数)

这个边界带来的直接收益:领域层可以用普通对象测试,不需要 mock 文件系统或 Git;「换数据源」只是实现一个返回 GitCommit[] 的函数,下游完全无感——项目现有的三个数据源(真实 Git / 演示场景 / 粘贴文本)就是同一契约的三次实现。

完整的模块清单、数据契约、系统不变式与 8 条设计决策记录见 docs/ARCHITECTURE.md。


六、工程质量

项 状态
第三方依赖 0(dependencies: {})
单元测试 24 个,覆盖评分 / 分级 / 数据 / 匹配 / 输出五层
src 层覆盖率 行 92.3% · 分支 70.5% · 函数 87.1%
CI GitHub Actions,Node 18.20 / 20 / 22 矩阵 + 端到端 smoke test
自检 npm run check:JS 语法、JSON 快照、Markdown 相对链接、包元数据
可复现 快照离线结算,同一份代码在任何机器上给出同一份结论
npm run verify          # 自检 + 测试
npm run test:coverage   # 覆盖率

测试按「风险」而不是「行数」分配,重点盯四个最容易出错的地方:

层 覆盖内容
评分 公式正确性、删除权重高于新增、两次提交不越过 L2、深夜/周末/热修不影响得分、空列表不崩
等级 100/500/1200 三个断点左闭右开、固定额度 400/700/1100/2000、--unlimited 只影响 L4
数据 真实快照能拼成目录、套餐热量 = 成分之和、别名匹配(新地 → 朱古力新地)、查不到返回 null 不估算、--numstat 解析、统计文本解析不丢行数
匹配 任意额度下推荐永不超支、额度极小返回空方案而非崩溃、双份只在放得下时出现且折扣率不变、四档推荐互不重复
输出 账单字段与顺序符合规范、多份展示写成 (A + B) × 2、L4 双份方案点明"为什么是两份"

七、关键取舍

取舍 选择 理由
数据源 Git 而非工时 工时自报会失真;Git 是副产品,不可伪造、有粒度、零侵入
评分口径 删除权重 > 新增 重构的认知负荷高于堆码;等权会奖励堆砌
额度 固定档位值 账单要说人话:一句「你落在 L3,上限 1100」胜过一条分段函数
主观信号 深夜/周末/热修不计分 避免双重惩罚;「深夜」依赖时区,缺乏跨用户可比性
热量缺失 排除而非估算 估一个「看起来合理」的数字,会让整张账单的可信度归零
数据时效 快照而非实时 可复现性优先,代价是价格可能过期(账单标注 capturedAt)
匹配策略 贪心排序而非背包 候选仅 34 个,组合优化可行,但多品组合无法用一句话解释
L4 上限 默认 2000 有硬约束才有可计算的匹配故事;--unlimited 保留另一种口径

完整决策记录(ADR-001 ~ ADR-008)见 ARCHITECTURE.md §7。


八、运行效果

终端输出长这样

### 🍔 赛博治愈对冲账单 (Cyber Healing Invoice)

* **今日工作量评估**:L4 拯救世界(Legendary)
* **代码变动详情**:新增 700 行 | 删除 320 行 | 共 13 次提交
* **工作量得分**:1570 分
* **建议热量上限**:2000 kcal

#### 💖 麦当劳 MCP 推荐治愈套餐:

- **套餐名称**:麦辣鸡腿汉堡三件套 ×2
- **包含单品**:(麦辣鸡腿汉堡 + 中薯条 + 可乐中杯)× 2
- **预估总热量**:1842 kcal(额度占用 92%)
- **优惠活动/价格**:¥67.0(原价 ¥92.0,省 ¥25.0,约 27.2% off)
- **治愈寄语**:屎山是你拦下的,功德是你赚的,这顿是你应得的。

账单之后跟一段补充说明:为什么是它 / 备选方案 / 差一点点 / 距下一档还差多少 / 哪些信号不计分 / 数据源与排除数。加 --no-extras 只输出账单主体。完整样例见 docs/sample-report.md。

四档演示的实测结果

场景 描述 新增/删除/提交 得分 等级 上限 推荐套餐 价格 热量
l1 周四 · 正常下班 18 / 4 / 2 84 L1 400 那么大鸡排(椒盐风味) ¥14.0 385(96%)
l2 周四 · 稳步推进 180 / 40 / 4 360 L2 700 麦乐鸡经典中套餐 ¥25.0 649(93%)
l3 周五 · 攻坚 + 加班 380 / 180 / 7 860 L3 1100 麦辣鸡腿汉堡三件套 ¥33.5 921(84%)
l4 周六 · 线上救火通宵 700 / 320 / 13 1570 L4 2000 麦辣鸡腿汉堡三件套 ×2 ¥67.0 1842(92%)

四档推荐互不重复,额度利用率都在 84% 以上——不是"努力了一天推荐你吃个甜筒"。

加 --unlimited 后 L4 会切到真正的无上限,此时答案是 全明星双人分享餐八件套(2142 kcal,56.4% off,¥49.9/原价 ¥114.5)——那份超出上限 142 kcal 的双人餐,正好是"宇宙级功德"该有的样子。


九、数据来源与可复现性

本项目调用的是真实的 mcd-mcp,工具以宿主原生形态注入。用到的 5 个接口:

工具 作用 本项目怎么用
query-nearby-stores 查附近门店 选定 storeCode=1450750(上海徐汇宜山路长兴园餐厅,444 m)
query-meals 查门店菜单 49 个商品的现价 / 划线价 → 折扣来源
query-meal-detail 查套餐成分 8 个套餐的默认选配 → 热量核算的输入
list-nutrition-foods 餐品营养表 160 项,取 41 项与菜单相关 → energyKcal
available-coupons 可领优惠券 4 张(不含 SKU 与价格,故不参与匹配,仅作展示)

调用过程逐条记录在 workbuddy.md(13 条真实调用,带服务端 traceId);协议细节、名称对齐策略与踩坑记录见 MCP_INTEGRATION.md。

为什么结算读快照而不是实时请求? 让任何人在没有 MCP 凭据的环境里 node index.js --demo all 也能跑出完全一样的结果。每份快照都带抓取时间与 traceId,可逐条对账。代价是价格可能过期,这一点在账单里明确标注,不做隐瞒。

隐私:全程本地执行 git log --numstat,只把「提交次数、行数」这类聚合数字用于计算,代码内容不出本机,运行时零网络请求。详见 SECURITY.md。


十、命令行参考

参数 说明
--demo [l1|l2|l3|l4|all] 使用内置演示场景(默认 l3)
--input <file|-> 解析贴进来的 git log --shortstat / git diff --stat 文本
--days <n> 统计最近 n 天,默认 1
--store <code> 门店编码(快照默认 1450750,不一致会明确报错而不是静默回退)
--unlimited L4 视为不限热量(默认 2000 kcal)
--no-double 关闭「同款点两份」候选
--out <file.md> 导出 Markdown 账单
--record <file.md> 导出 MCP 调用记录
--json [file] 输出机器可读 JSON(- 表示 stdout)
--no-extras 只输出账单主体
-v, --version 显示版本
-h, --help 帮助

十一、已知局限

写在这里,是为了让评价者不必自己去发现:

  1. 行数是难度的代理指标,不是难度本身。 100 行 YAML 与 100 行无锁并发代码同分。这是本模型最大的效度威胁。
  2. 匹配是贪心排序,不是全局最优。 候选仅 34 个,0/1 背包可解,但多品组合的解释成本过高,当前选择解释性优先。
  3. 同款叠加只到 ×2。 k 的上界是经验值,不是从数据推出的。
  4. 不建模用户偏好。 过敏原、口味、是否吃过都不在目标函数里。
  5. 快照有时效。 价格与供应状态以 capturedAt 为准,过期快照不会自动失效。
  6. 单人单仓库。 没有跨仓库聚合,也不处理多人协作的贡献切分。

进一步的分析与改进方向见 ALGORITHM.md §8。


十二、文档索引

文档 内容 适合谁
docs/ARCHITECTURE.md 系统上下文、分层、数据契约、设计决策、失败模式 想读代码的人
docs/ALGORITHM.md 评分与匹配的形式化定义、参数标定、敏感性实验、局限 关心方法的人
MCP_INTEGRATION.md mcd-mcp 协议细节、名称对齐策略、踩坑记录 想复用 MCP 的人
workbuddy.md 13 条真实调用记录(含 traceId) 想核对数据的人
docs/sample-report.md 四档完整账单样例 想先看效果的人
CONTRIBUTING.md 开发约定、参数变更流程、快照更新流程 想提 PR 的人
CHANGELOG.md 版本变更记录 所有人
SECURITY.md 威胁模型、隐私边界、漏洞报告方式 关心安全的人

声明

本项目为麦当劳程序员节创意开发大赛参赛作品,由参赛者独立开发,非麦当劳官方产品。项目输出仅供参考,不构成医疗、营养或其他专业建议;餐品信息、价格及供应状态以麦当劳官方渠道的实时结果为准。详见 CONTEST_DECLARATION.md。

License

MIT

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

139 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages