Releases: magicapple123/ResumeForge
Release list
ResumeForge v0.11.0
变更
- 后端代码重组(无行为变化):
services/按域收进 resume/profile/job/jd/assistant/interview 子目录;assistant_tools.py、apply_service.py拆成子包/域模块,简历落库与版式解析收进resume/。 - API Key 静态加密:Windows 上改用系统 DPAPI 加密存储,密文绑定当前用户与机器;非 Windows 保持明文,旧库透明兼容。
- 设置页瘦身:抽出 skills/datasets hooks,页面组件从 705 行降到 523 行。
- 新增项目导航图与面试讲解指南文档。
校验
全量 2157 测试通过、ruff 全绿。
ResumeForge v0.10.1
修复
仓库改名之后「检查更新」直接报错。 项目从 Magicapple-Coder/ResumeForge 改名为 magicapple123/ResumeForge 后,GitHub 对旧地址返回 301,而应用获取新版本信息时不跟随重定向(这是刻意的,为了让"仓库不存在"这类情况被如实报出来,而不是被重定向悄悄带到别处),于是 0.10.0 的用户点「检查更新」看到的是 GitHub 返回了 HTTP 301。现在默认仓库地址改为新名字。
0.10.0 的用户需要手动下载 0.10.1 —— 坏掉的正是"获取新版本信息"这条路本身,没有别的补救途径。
同时修掉了 CI 上两个长期让 Actions 红多绿少的工程问题:后端 Windows job 因测试夹具的 fsync 开销撞满 30 分钟超时(实测 28 分 30 秒只跑完 318/2149 个用例,外推约 3.2 小时),现在 6 分 5 秒通过;前端「技能列表」用例在慢速 runner 上的异步竞态。这两项不影响应用功能。
变更
仓库地址同步为新名字(README、徽章、Issue 模板、更新检查、更新脚本)。
完整记录见 CHANGELOG.md。
ResumeForge v0.10.0
从上一版升级:双击 update.cmd(或 git pull --ff-only 后重装依赖)即可,data/ 与 .env 不受影响。
本次没有数据库迁移,回滚只需切回旧版本;升级前仍建议在「设置 → 数据集」里导出一份备份。
直接下载:ResumeForge-0.10.0.zip,解压后双击 start.cmd(首次运行会自动准备 Python / Node 环境)。
截图与完整说明见 README。
0.10.0 - 2026-09-21
Added
-
采集条件里多了「按招聘网站的条件筛」——把站点筛选栏整条搬了进来(求职类型 / 薪资待遇 / 工作经验 / 学历要求 / 公司行业 / 公司规模 / 融资阶段)。这些是站点自己的筛选参数,网站在搜索时就替你筛掉了,比"采回来再按岗位字段本地筛"更准、也少翻很多页;原来那几个「我的期望薪资 / 我的经验 / 我的学历」保留不动(语义不同:那几个筛的是"你的条件 vs 岗位要求",例如"我是本科";新增的这组筛的是"岗位要求本科"),两者可以同时用。
三项设计决定,各自都对应一类踩过的坑:
- 选项清单读自站点,不写死。写死的一份在站点改编码之后会静默筛错(用户以为按「本科」筛了,实际发出去的是另一个编码)。清单有三条来源并按可信度降级:你的登录会话(页面筛选栏,最准)→ 全网通用清单(免登录接口)→ 内置快照(离线兜底,只覆盖 6 个短清单)。来源在界面上如实标注。
- 「求职类型」必须读登录态。2026-09-21 实测:这个标签的选项因人而异——你的账号能看到「不限 / 全职 / 兼职 / 实习」,未登录时只能拿到「不限 / 全职 / 兼职」。写死一份就等于替所有用户决定了他们能选什么,所以浏览器在跑时优先读页面筛选栏,读不到才退回通用清单并明确告诉你"当前是通用清单"。
- 编码用之前必须校验。采集开始前拿当次读到的清单逐项核对,核对不过的不发给站点、并如实计入"未能生效"(
task.config.site_filter_unapplied,界面上单独提示)。旧编码在站点看来同样"合法",发出去会静默筛错,那比不筛更糟。
参数名与编码全部由真实点击得到,不是从 HTML 推的:埋点属性写的是
sel-job-rec-exp,而地址栏里的参数名是experience——照 HTML 抄就错了。逐条实测记录(2026-09-21,登录会话,点一下读一次地址栏):jobType=1901/1903/1902、salary=402–407、experience=101–108、degree=202–209、scale=301–306、stage=801–808、industry=1000xx。行业的编码只认接口那份:页面上的ka是渲染序号(sel-industry-23对应的真实编码是101407),照抄会发出一个"合法但不相干"的编码。实现落在
services/sites/boss_filters.py(纯函数:解析/合并/校验,可离线逐条测)、BossSearchMixin.prepare_collect_filters(解析与校验)与BossAdapter.build_search_url(拼参数)。新增GET /api/collect/filters下发清单;CollectConfigIn新增filters: dict[str,str](向后兼容:不给就是空,行为与改动前一致)。job_type与站点筛选撞车时以用户显式选择为准:否则同一个jobType参数会在地址里出现两次,站点取哪一个不确定——那正是"我选了实习却混进全职"的成因。 -
投递/采集批次「跳页不打断 + 完成全局通知」。批次由后端线程执行,此前切到其他页面只是看不到进度;现在全局布局挂了一个批次监听(
useTaskCompletionWatcher+TaskCompletionNotifier):只要本会话里见过某批次进行中、随后它进入终态,就弹应用内通知(成功/失败/停止各有文案与统计)并尽力发一条系统桌面通知(仅在已授予通知权限时,绝不主动弹权限框),点击通知跳回投递台。判定是 transition 语义:应用启动时早已结束的批次不会误报。 -
投递记录按批次分组展示。一次「开始投递」= 一个批次,一次投出的 N 个岗位在「投递记录」里折叠成一组:组头显示批次号、时间、状态与该批次自己的统计(成功/失败/跳过),点击展开看组内每条记录(重投/详情照旧)。新增
GET /api/apply/records/grouped:筛选(关键词/结果)作用在记录上、分页按批次计,没有命中记录的批次整体不出现;旧的扁平/records保留。 -
岗位类型真正参与筛选(此前仅入库标注)。2026-09-20 用真实登录会话实测校准:BOSS 官方「求职类型」参数为 全职=1901 / 兼职=1903 / 实习=1902(无校招档,校招是独立专区),接口响应的岗位类型编码为 0=全职(社招)、4=实习、5=校招、6=兼职。因此:实习/社招直接映射到站点官方参数(搜索时就由站点筛掉,省翻页),校招由采集后的本地筛选按接口编码判定;同时所有类型都保留本地二次校验——站点参数将来若失效(历史上
jobType=4就不是有效参数),本地仍能兜住。接口没给类型字段(DOM 兜底路径)的岗位保留并如实计入"未能判断",绝不凭标题猜类型。界面上岗位类型的标签随所选值变化(实习/社招=「站点筛选」、校招=「采集后筛选」),不再标「仅标注」。 -
卡片点一下就能看详情(内推 / 资料箱 / 面经 / 提醒 / 知识库 / 投递记录 / 投递队列)。这些列表与卡片此前只放得下摘要:内推看不到联系方式与内推码、资料箱看不到正文全文与附件、面经看不到完整问题清单、提醒看不到绑定对象与备注、投递队列看不到招呼语全文。现在统一为「点卡片 → 右侧详情抽屉」,字段与正文分区展示,抽屉里带「编辑」等后续动作。抽出一个
components/common/RecordDetail.tsx(DetailTrigger+RecordDetailDrawer)作为唯一实现,避免六处各写一套排版与关闭方式。可点击卡片用role="button"+tabIndex而不是<button>——卡片里有编辑/删除按钮,按钮套按钮是无效 HTML;点击时跳过内层可交互元素(isFromInnerControl),否则点「删除」会顺带打开详情。键盘 Enter / 空格同样可打开,并补了可见的焦点描边。 -
月历点任意一天看当天安排。提醒的月历视图(首页「近期提醒」与「求职进度 → 提醒」用的是同一个
CalendarView)此前点日期格没有任何反应。现在点一格就弹出那一天的完整安排:标题、类型、状态、时间与备注;没有安排时明确说「这一天没有安排」。明细内建在组件里而不是由各页面各接一次,这样首页与进度页的行为不会漂移;原有的onSelectDate回调保留,传了就由父组件接管、不弹内建明细。 -
投递台只接纳「来源在招聘网站内」的岗位(用户反馈:手动录入、来源指不到任何招聘网站的岗位以前也能加进队列,强行开始投递只得到一条「未知失败」)。原来的判据只在执行层体现——
registry.for_job()解析不出站点就抛FAILURE_UNKNOWN,于是用户看到的是含糊的「未知失败」,也说不清是谁的问题。现在把闸门提到入队与开始投递两处,执行层保留兜底:- 新增
SiteRegistry.resolve_for_job()(for_job的不抛异常版本)与apply_service.job_apply_site()作为唯一判据:岗位的source/source_url必须能落到已注册站点上。入队、开始投递、列表标记、读取模型四处共用它,不再各判一遍。 - 入队:不满足的岗位返回 409 +
site_unsupported,文案写明"当前支持哪些站点"与"请到对应网站手动投递",并保证不写进队列。注意判据是"来源/投递链接能否归属到站点",所以手动录入但贴了招聘网站投递链接的岗位照常允许——这比"必须用采集"更贴合实际用法。 - 开始投递:队列里若有来源不支持的条目(只可能是闸门上线前的历史数据),在建批次之前一次性拦下并点名是哪几个岗位、怎么处理(移出队列),不再留下一批注定失败的条目;勾选投递时同样受此约束。
- 失败分类新增
site_unsupported("岗位来源不支持自动投递"):万一真漏到执行层,记录里写的是真实原因而不是「未知失败」。它是前后端共用的一份取值,新增了一条镜像守卫测试逐字比对FAILURE_CATEGORIES/FAILURE_CATEGORY_LABELS与frontend/src/types/apply.ts,防止两处漂移后"记录里的分类在前端筛选里找不到"。 - 界面:
JobOut/ApplyQueueItemOut新增apply_supported(后端算好下发,前端不写死站点名或主机名)。岗位详情的「加入投递台」对这一类岗位禁用并把原因写在页面上(含"把投递链接改成该岗位在招聘网站上的地址即可自动投递"这条出路);队列里这类条目在「岗位」列标红「来源不支持」、勾选框禁用,队列顶部给出一次性说明与两种处理方式;后端拒收时弹提示而不给「仍然加入」按钮(这一类确认也没用)。
- 新增
-
启动器的失败提示改成了中文,并给了可操作的下一步(2026-09-21 在干净副本里真跑首次安装时核对)。首次安装本身是通的(建 venv → 装 38 个后端包 → 装 366 个前端包 → 两端 200),但失败时只说一句
Failed to install backend dependencies.,既不说是为什么、也不说下一步做什么——而"网络/代理导致依赖装不上"恰恰是全新机器最常见的失败。现在:- 全部用户可见提示改成中文(Python/Node 的获取与安装、端口占用、venv 创建、pip 与 npm 安装、启动就绪)。
- pip / npm / venv 失败各给出 3~4 步处置,含可直接粘贴的命令(如换国内镜像
-m pip install -i https://mirrors.aliyun.com/pypi/simple -r backend\requirements.txt)与"打不开这个地址就是网络被拦了"这类可自查的判据。 - 顶层捕获把
throw变成一段干净的话:此前 PowerShell 会先打印脚本路径、字偏移和出错的源码行,真正有用的建议被埋在噪音后面;现在只输出「启动失败。」+ 原因 + 怎么办 + 已停止,start.cmd的pause保证窗口不会一闪而过。 - 中文提示要求脚本带 UTF-8 BOM,这是必须的而不是风格问题:
start.cmd用的 PowerShell 5.1 会把无 BOM 的文件按 ANSI 码页解码,中文字符串会直接把脚本读崩(实测去掉 BOM 后 PS 5.1 报 33 处语法错误)。启动器测试新增一条字节级守卫:scripts/*.ps1里有非 ASCII 就必须有 BOM。(AGENTS.md原先写的"必须保持纯 ASCII"不准确——那条真正逐字节校验的是backend/requirements*.txt。) - 顺带修好一处既有隐患:
Update-ResumeForge.ps1里有中文注释却没有 BOM(只影响注释的可读性,未造成解析失败),现已补上 BOM。
-
数据集可以「导出全部」(2026-09-21 逐表验证备份覆盖情况时发现的缺口)。应用支持多份数据集、每份各占一个数据库文件,而原来的导出只带当前活动的那一份——列表里其余几份不在包里。一个用户"备份了"却丢了几份,通常要到需要恢复时才发现,那时原始文件可能早就不在(真实复测:本机那份「resumeforge-demo-data-20260915」就不在任何备份里)。现在列表里不止一份时会出现 导出全部数据集,把当前这份加上其余每一份装进同一个包。
- 格式号随内容变化:只有活动数据集时仍是格式 1(老版本照常可读、旧备份照常可导入);一旦带上其余数据集就标成 2,老版本会明确拒收("请先升级应用再导入")而不是安静地只恢复一份。老版本读不懂
datasets/这一段,放行就等于让用户以为恢复成功了。 - 随行的每一份都做同样的密钥剥离:不是只处理活动的那一份。这个包正是用户会长期保存或转发的东西,漏一份就是把那份数据集里的明文 API Key 一起送出去。
- 导入是"要么全成、要么不动":包里所有数据集先全部校验完再开始写盘。边写边验的下场是——后面某份不合法时前面几份已经落盘,用户看到一次失败、列表里却多出几份半截的数据集。(这条是被自己的测试抓出来的:第一版实现留下了半截数据。)
- 包内路径不可信:清单里的
file只认datasets/<合法 id>.db这一种形状,指向主库成员或带..的一律拒收——一个改过的包能靠这个字段让导入过程把别的东西当数据集写盘。 - 恢复出来的数据集用全新标识:沿用包里的 id 会覆盖本机早已存在的那一份(不可逆)。
- 格式号随内容变化:只有活动数据集时仍是格式 1(老版本照常可读、旧备份照常可导入);一旦带上其余数据集就标成 2,老版本会明确拒收("请先升级应用再导入")而不是安静地只恢复一份。老版本读不懂
Fixed
-
让后端与前端测试在 Linux CI 上也能全绿(此前只有 Windows 全绿)。三处原因、三处修法:
- 浏览器定位在非 Windows 上是坏的。
BrowserManager._existing_env_paths用Path(base) / r"Google\Chrome\Application\chrome.exe"拼接,而反斜杠在 Linux/macOS 上只是普通字符、不是路径分隔符,于是候选路径永远找不到,浏览器发现只能靠shutil.which兜底——在没装浏览器的 CI 上 7 个浏览器定位测试全部失败。现在按\\拆段后用Path.joinpath(*parts)拼接,跨平台一致;这也顺带修好了一处真实缺陷(Linux/macOS 用户以前无法靠PROGRAMFILES这类环境变量定位浏览器)。 - 服务端 PDF 生成需要中文字体,而 Linux CI 没装。
pdf_exporter已经认 Noto CJK(/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc),只是 runner 上没有这个字体,导致 14 个分享包用例在「生成 PDF」那一步报 409。修复是给 Linux job 加一步sudo apt-get install -y fonts-noto-cjk(分享包这个功能本来就是要测 PDF 的,不该为了迁就 CI 而跳过)。 - 两个前端用例在慢速 runner 上不稳定。
SettingsPage的「切换技能开关」在列表异步渲染完成前就用同步getByRole取开关(改成findByRole等它出现);AssistantPage的「流式回复隔离」用例给 10 秒上限太紧(放宽到 20 秒)。这两处都不是断言变松,只是给异步 UI 测试留足环境余量。
- 浏览器定位在非 Windows 上是坏的。
-
补齐发布包的必需文件清单,让 Windows 的「启动器 + 打包」测试重新变绿。
scripts/Build-Release.ps1的$RequiredFiles一度落后于backend/app/preflight.py的_REQUIRED_FILES22 项:app/data/ats_keywords.json、app/services/feature_catalog.py与 20 个提示词文件。后果只落在开发侧——scripts/tests/Test-Build-Release.ps1会抛「Build-Release.ps1 does not require 'backend/app/data/ats_keywords.json'」而让 Windows job 连续多轮失败,但实际分发的压缩包是完整的(git archive会带上全部受追踪文件,这份清单只是发布前的自检网,不是打包过滤)。修复是把清单补齐到与 preflight 一致(50 项),并留注释说明这条单方向守卫:往 preflight 加资源而不在这里登记,测试就会变红。对终端用户没有任何行为变化。 -
分享包换数据集/恢复备份之后「下载」必定 404(2026-09-21 核对备份覆盖情况时发现)。分享包的内容在数据库里(
snapshot列,随备份走),渲染出来的resume.html/resume.pdf/resume_snapshot.json/manifest.json落在磁盘目录里(不随备份走)。于是换数据集或从备份恢复到另一台机器后,列表、详情、备注全都正常,唯独点「下载」报「文件不存在」——看起来像功能坏了。现在把磁盘文件当成缓存而不是数据源:缺了就从库里的快照按同一套导出管线重新渲染,点「打开目录」也会先补齐,不会把一个空文件夹摊给用户。重新渲染的内容逐字一致;版式优先沿用原简历的模板与版式设置,原简历已被删除时退回默认版式(不去猜当初用的是哪套模板,那只会造出一个看起来对、其实对不上的东西)。comments.md刻意不补:那是收件人写进来、回传给用户的内容,数据库里没有第二份。补一个空白模板会在界面上假装"评论还在",比如实 404 更糟。- 顺带修掉一处同族隐患:分享包目录原本是
<库文件所在目录>/share_packages/<id>,而多份数据集共用data/datasets/这一个目录、分享包 id 又是每个库各自自增的——数据集 A 的第 1 份会和数据集 B 的第 1 份落到同一个目录里互相覆盖。现在非主数据额外带一层库文件名;主数据保持原路径不变(那里可能已经有用户的分享包,迁走等于让它们消失)。
-
「同公司只投一个岗位」是个空开关——投递逻辑从来没读过它(2026-09-21 用真实投递逐项验证设置时发现)。配置字段、设置界面的开关、前端类型镜像三处都在,唯独
services/apply/里一次都没出现。用户打开它、保存它,很自然会以为最多给一家公司发一条,实际队列里有几条就发几条。现在真的生效了,作用域取本批次内(与它旁边那几个选项同一粒度:每批上限 / 每日上限 / 间隔),语义与其余跳过路径一致:- 记的是"尝试过"而不是"成功过":投递失败可能发生在确认环节——招呼语其实已经发出去了,只是没读到对方回执。这时再给同一家公司发第二条,恰恰是这个开关要避免的事。所以只要动手了,这家公司就算数;方向始终是宁可少发一条,不可重发一家。
- 公司名要归一化(压掉空白、统一大小写):招聘网站上的公司名带前后空格、写法不一,不归一化就会把同一家当成两家,开关照样形同虚设。
- 跳过要说明原因:跳过的条目在
failure_detail里写明「按『同公司只投一个岗位』跳过:本批次已经投过「X」的岗位」。同一个字段有两种含义(失败诊断 / 跳过原因),所以进度面板的明细标签跟着状态走(失败=「诊断信息」、跳过=「跳过原因」),否则用户看到跳过条目会以为出错了。 - 没有公司名的条目不作同公司判断(判断不了就不做判断),不会被误吞。
-
多核机器上
npm test会在打印任何结果之前直接崩掉(JavaScript heap out of memory)。vitest 默认按 CPU 核数开 worker,而每个 worker 是一整个 Node 进程:一个 antd 重的页面文件导入起来就要一个 GB 上下,32 核机器上就是三十几个进程一起把堆撑爆——报错发生在测试跑起来之前,所以看起来像"测试跑不动"而不是"某个用例挂了"。vitest.config.ts现在把并发上限钉在 4。选 4 是实测出来的:4 与 8 的耗时分别是 ~217s 与 ~250s(瓶颈在逐文件的模块导入,不在 CPU),而 8 的时候一个自带 10 秒预算的流式用例会因争抢开始超时;核数少的 CI 本来就取不到 4 个 worker,这个上限只会在崩溃的那类机器上生效。验证:不带任何参数跑npm test,88 个文件 / 550 个用例全绿。 -
采集批次在「当前批次」里不可见,导致两件事直接失效(
GET /api/apply/tasks/current写死kind=apply)。后果不是少显示一行进度:①开始采集后切到别的页面再回投递台,进度面板是空的,看不到采集在跑;②全局完成通知靠这个接口判断"这个批次刚才在跑",采集批次从不出现,于是采集跑完永远不弹通知。两种批次本来就互斥(运行器同一时刻只跑一个),所以默认改成"不限类型",需要限定的调用方传kind即可。真实复测:采集跑起来后/tasks/current报collect #62→ 暂停 12 秒进度纹丝不动 → 继续后 0→2 正常推进 → 跑完 8/8 →current收敛为null(完成通知的跃迁信号)。 -
薪资本地筛选在真实数据下从不生效(2026-09-20 真实采集复测发现)。本地筛选代码期望招聘网站列表接口返回
lowSalary/highSalary数字字段,而真实接口只有salaryDesc展示文本("12-24K·14薪"、"500-1000元/天")——于是每个岗位的薪资都落进"未能判断"被保留,低于期望下限的岗位全部漏进来(真实复测:填了"最低薪资 20K"仍暂存 10-15K、7-11K 的岗位)。修复:新增parse_salary_text把展示文本解析成月薪区间(K/万/元·天 三种真实写法,年薪口径与"面议"如实返回"判断不了"),数字字段缺失时回退到文本解析,数字字段仍在时优先。真实复测:同一查询修复后筛掉原因出现「薪资」、"未能判断"计数从 2 归零;正向验证 ≥12K 的岗位正常保留(上限恰等于期望下限按"有交集"保留,与既有含端点口径一致)。 -
自动投递在浏览器窗口最小化/被遮挡时必然失败(2026-09-20...
ResumeForge v0.9.0
0.9.0 - 2026-09-20
Added
-
首页快捷入口可自定义 + 近期提醒默认列表。首页「快捷入口」卡片可点「编辑」自行增删与排序(从侧栏全部菜单里勾选,localStorage 持久化,至少保留一项);「近期提醒」卡片默认回到列表样式,卡片右上角可切「列表 / 月历」,偏好同样记住。
-
投递记录详情抽屉 + 投递队列右键菜单。投递记录的失败信息(含页面 URL / 标题 / 批次 / 第几次尝试)不再塞进表格单元格,点「详情」在右侧抽屉完整查看;表格去掉横向滚动条、长文本硬截断。投递队列支持右键呼出操作菜单(编辑 / 移出队列),操作按钮右移。
-
自动采集条件历史。保存采集条件后记一份快照(去重、上限 20),下次可从「历史条件」下拉一键回填,也可清空。
-
题库与面试复盘:生成自动保存历史 + 历史富还原。题库生成、面试复盘分析完成后自动写入历史(无需手点保存);点开历史记录用与刚生成时完全相同的交互还原——题库的「参考答案」、复盘的「反向优化简历」都能点,新生成的结果回写到同一条历史。旧版纯文本历史仅做兼容展示并提示"仅可查看",不崩。
-
简历版本对比改成字段级视图。不再把两份简历的 JSON 当源码逐行展示,改为按字段分区、只显示有变化的区块,字符串行内词级高亮、列表条目 +/- 标记、照片显示 [图片];「查看原始差异」折叠作为兜底。
-
开发调试:接入 code-inspector。本地
npm run dev时按住 Alt+Shift(Windows)点击页面元素,自动在 VS Code 里定位到对应源码行(仅开发环境生效)。 -
「匹配度参考分」接上界面(此前只有后端与文档,界面上看不到)。岗位详情 →「匹配度分析」→ 五类结论之后新增「匹配度参考分」:0–100 的圆环 + 5 个分项(技能覆盖 / 年限 / 项目相关度 / 硬性门槛 / JD 关键词覆盖)的横条,每条带一句可读依据(如「核心/加分项共 18 条,已匹配 6 条」)。三条口径约束写进代码与用例:① 它是纯本地规则现算的派生值,不调模型、不落库;② 不参与投递准入——能投 / 需确认 / 不投仍只看五类结论;③ 强制原样展示后端下发的免责文案("不代表真实 ATS 解析结果或投递成功概率")。分数用单一色相画、不按高低变色:后端没有定义档位,前端自己划"好/差"的线就是在编造一个它没有的判断。放在结论区之后而不是之前,也是同一个理由——不能让它盖过准入结论。
-
求职统计扩成四个主题区块,并补上"没有数据时不许画零"的诚实口径。此前这一页只有 4 张指标卡 + 漏斗 + 趋势图,下半屏大片留白;排查后发现真正的问题不是图表不够,而是没有数据可画——趋势图按
applied_at分月,而当时 3 条投递记录一条都没填投递日期,所以它不是"这几个月没投递",而是每一行都缺日期。因此本批分两步:① 让数据被填上(见下方 Fixed 里表单的改动);② 把指标与版面补起来,并且任何一张图都不许渲染会撒谎的零。新增四个区块:转化与卡点(进行中 / 超过 7 天没有更新 / 没有下一步 + 六阶段漏斗)、时间与节奏(月度趋势 + 周内投递分布 + 最近 7/30 天新增 + 待办提醒四档紧急度)、渠道与去向(内推转化与内推状态分布、投递最多的公司按company_key归并、记录来源)、简历与健康度(简历份数、带一致性提醒 / 带【待补】的份数、待确认主张数、投递-简历关联覆盖率);概览另补 Offer 率。诚实口径有两层:新增applied_date_gap(有 / 没有可用投递日期的条数)驱动一条DataGapNotice,如实说明"有几条未填、未计入";当一张图的输入全空时渲染明确空态而不是一排零轴——一张全零的趋势图会被读成"这几个月真的一份没投"。跨模块复用而非各写一份:services/ratios.py(比率唯一实现,放叶子模块以避开analytics ↔ referral_service成环)、models/tracker.py的STALLED_DAYS/is_stalled("卡住"口径,与/api/stats同源)、referral_service.referral_stats/referral_status_counts(内推转化只认_is_converted)、reminder_service.reminder_urgency_counts(四档阈值只此一处)、新增services/resume_health.py(复用resume_completeness.find_incomplete与VERIFICATION_PENDING)。刻意不提供的指标也写进了services/analytics.py的 docstring,免得被反复提议:平均"投递→面试"天数 / 各阶段流失率 / 面试通过率(没有状态历史表,只有当前状态 + 一个status_date,算不出来)、按行业分布(全库无此字段)、按岗位类型看转化与每份简历的表现(job_id/resume_id在录入界面上没有入口,是结构性为空,所以本轮只把关联覆盖率显示出来让缺口可见)。口径命名守住一条线:updated_at会被任何编辑刷新,所以该指标叫「超过 7 天没有更新」而不是「面试卡住」。前端零新增 SVG 组件:抽出HorizontalBarChart(漏斗与排行共用同一份几何与maxWidth封顶——封顶散成两份的话,上次"图表随窗口放大"的修复只会修到一半),FunnelChart变成它的薄壳(原有测试一行未改仍通过,这正是"泛化没改变漏斗渲染"的证据),BarChart放宽为通用label/count序列并参数化aria-label(否则新增的周内分布会与趋势图撞同一个可访问名);另加StatCard/SectionCard/DataGapNotice三个非图表组件。助手侧新增dashboard_brief投影:get_analytics_overview此前把整个看板 dict 直接塞进上下文,新增约 25 个字段后会把长数组也带进去、稀释标量,现在保留全部标量、裁掉趋势/周内/内推状态并只留公司榜前 5。 -
知识库(新模块):侧栏在「资料箱」之后新增「知识库」,沉淀愿意反复查阅的成文内容——面经总结、简历技巧、求职策略、面试问答、公司信息、行业知识等。条目带标题、分类、标签与 Markdown 正文,支持搜索、按分类/标签筛选、新增、编辑与删除(删除走回收站软删除,可恢复);正文预览复用助手的 Markdown 渲染。它与「资料箱」的分工是:资料箱放还没成体系的零散材料(证书、链接、笔记),知识库放已经整理成文、愿意反复看的笔记。求职助手能检索与增改知识库(
list_knowledge/get_knowledge/create_knowledge/update_knowledge)。新增GET/POST /api/knowledge、GET /api/knowledge/categories、GET/PUT/DELETE /api/knowledge/{id}。 -
内推增强:内推码 + 图片备注 + 状态分色。内推记录现在可以记内推码(
referral_code)与备注图片(note_images,如内推海报截图),列表按状态分色一眼区分「进行中 / 已投递 / 已关闭 / 无效」。两列由迁移0019补(只加列),业务读写走既有的/api/referrals。 -
日历提醒上首页 + 紧急度分色 + 「打开应用时弹出提醒」开关。提醒不再是「求职进度」页里的一个面板:首页也会展示近期提醒,并按紧急度分色——逾期 / 24 小时内 / 3 天内三档一眼看出哪件最急。设置里新增「打开应用时弹出提醒」开关(默认开,
GET/PUT /api/settings/reminder-popup),关掉后启动不再弹近期提醒。 -
题库单题生成详细参考答案:个性化题库里可以给单题生成详细参考答案(不再只有题目),方便提前自测后对照。
-
题库与面试复盘历史(可保存、回看、删除):个性化题库与面试复盘都可以保存成历史记录,之后回看或删除——不再是一次生成、关掉就丢。两张表
question_bank_record/interview_review_record由迁移0019新增(带deleted_at软删除,删除进回收站可恢复)。求职助手能读这两类历史(list_question_banks/list_reviews)。 -
全局搜索扩域:首页搜索不再只搜岗位与简历,还覆盖内推 / 提醒 / 面经 / 事实台账 / 资料箱 / 助手技能——一个框找到更多内容。
-
求职助手「了如指掌」:补齐 9 个读工具 + 3 个写工具,新增「能力地图」与「行为准则」。为让助手对整个界面和数据库都答得上、且实事求是、绝不编造,系统提示重写:置顶「行为准则」(不知道就说不知道、只在你明确要求时才写入、涉及用户数据必须用工具查证后再答),并新增「能力地图」逐条说明 15 个功能域在哪、助手能帮什么。工具侧补齐只读的提醒 / 内推 / 面经 / 题库历史 / 复盘历史 / 知识库 / 求职统计看板 / 分享包列表,以及写入的知识库增改(
create_knowledge/update_knowledge)与提醒新增(create_reminder)——写工具全部进入提示词「写入类工具」清单并与Tool.writes双向一致(有测试钉住)。 -
岗位匹配新增「参考分」:五类结论之外再给一个 0–100 参考分与 5 个分项,仅作展示辅助、不进入投递准入。此前匹配分析只给「已匹配 / 表达缺口 / 证据不足 / 真实缺口 / 待确认」五类结论与准入建议,并且刻意不显示任何百分比评分——因为一个"看起来精确、其实是编的"分数比没有分数更误导人。参考分延续这条底线:它由
services/match_scoring.py用纯本地规则现算(技能覆盖 / 年限 / 项目相关度 / 硬性门槛 / JD 关键词覆盖五个分项加权求和),是派生值、不落库、不回写分析结果,而且不参与、也不改变五类结论与准入闸门(「能投 / 需确认 / 不投」仍只由admission_of单源判定);信号缺失时给中性分(50)而不是把"没信息"误报成"不匹配"。分数旁固定带免责文案("不代表真实 ATS 解析结果或投递成功概率,投递准入仍以五类结论为准"),未配置模型走本地降级时不产分数,不假装做过 AI 打分。 -
简历写作增强(四个 LLM 变换 + 版本差异对比)。编辑简历时,对一段经历描述可以做四种就地变换:STAR 量化改写(把"负责后端开发"改写成"情境 → 任务 → 行动 → 结果"并尽量量化;可挂一条台账主张作为「事实边界」,改写不会越过个人边界)、话术生成器(同一段事实一次生成简历版 / STAR 版 / 面试口述版三种表达)、多风格润色(大厂严谨 / 简洁技术 / 应届校园)、中英互译(忠实原意,数字 / 日期 / 术语不篡改)。四个变换都是「一段文本进、一段文本出」,结果直接回填到对应字段、零适配;未配置模型时接口返回清晰中文错误、不做本地降级(不伪造结果)。另有版本差异对比:任选两份简历看三态差异(新增 / 删除 / 不变),用本地 difflib 计算、不调用模型。
-
质量与合规检查(查重 / 敏感词 / 夸大风险 / 面试深挖风险点 / 合规校验 / ATS 本地检测)。简历编辑页新增质量检查面板,把"这份简历投出去会不会踩坑"拆成六类:查重与敏感词在本机做(确定性规则、不含模型),夸大风险与面试深挖风险点联动事实台账——能说多强由「承担程度 / 个人边界」约束,越界的表述会被点出;合规校验覆盖保密 / NDA / 违规表述等红线;ATS 本地检测只做格式 / 关键词覆盖 / 信息位置三类静态检查,并带明确免责(它不是招聘系统的真实解析结果)。
-
面试与求职工具链(个性化题库 / 面试全流程 / 面试复盘 / 日历提醒 / 内推管理 / 求职数据看板 / 面经知识库)。新增个性化题库(基础 / 项目深挖 / 反问 HR 三类,按关联岗位与资料生成)与面试全流程(录入真实面试问题 → AI 分析 → 反向优化简历);面试后可复盘沉淀;日历提醒跟进投递与面试日程;内推管理记录通过人脉拿到的推荐机会并统计转化率(口径由关联的求职进度
ApplicationTrack派生,进入面试及以上才算转化,写入侧不接受手填converted);侧栏新增「求职统计」数据看板;面经知识库沉淀面试经验。 -
导出与分享补齐(Word / 纯文本导出、自定义水印、一键隐私脱敏、离线分享包、本地模板市场)。导出格式从 HTML / JSON / Markdown / PDF 扩到 Word(docx)与纯文本(txt)(Word 复用同一套版式口径、不引入第二套排版引擎),并支持自定义水印(HTML 用 CSS 覆盖层、PDF 用 pypdf 叠加、Word 写页眉;纯文本与 JSON 没有版面概念,请求加水印时报明确错误)与一键隐私脱敏(姓名 / 电话 / 邮箱 / 公司 / 学校等,始终基于脱敏内容导出)。新增离线分享包:把一份简历打包成脱敏 HTML/PDF + 只读快照 + 评论回传文件 + 本地 token,可选「只读」或「可评论」,发出去给别人看、不带真实敏感信息;以及本地模板市场(互联网大厂 / 国企事业单位 / 外企 / 应届校园四套预设)。
-
回收站扩展到后四张新表(内推 / 提醒 / 面经 / 分享包)。此前回收站只覆盖岗位、简历记录、投递记录、事实台账、资料箱材料、助手会话六类;本批新增的内推、提醒、面经、分享包四张表也带
deleted_at列,因此一并纳入TRASH_SPECS,删掉后同样可恢复、可彻底删除,不再出现"删了就找不回"的半软删。注册表驱动的设计让这类扩展只需加一行。 -
简历生成改为后台任务:真实阶段条 + 显式取消 + 完成弹窗。生成弹窗不再是"点完开始就锁死、只能盯着一串文字转圈"。前端新增结构化阶段条(整理资料 → 筛选资料 → 调用模型生成 → 校验修复 → 完成),阶段由后端
progress文案映射、映射不上的只显示原文不硬凑;进度只显示"已接收 N 字",刻意不做百分比——模型逐字流式输出、后端无法预知总长,任何百分比都只能靠猜。后端新增resume_generate_task表(迁移0017_resume_generate_task,只加表、幂等、带完整 downgrade,旧备份仍可导入)与ResumeGenerateRunner单例后台线程——借apply/task_runner.py的单线程纪律,但不用它的 CDP 内核(生成只需把异步生成器转成同步消费,与浏览器无关)。接口新增POST /api/resumes/generate/tasks(返回task_id)、GET /api/resumes/generate/tasks/{id}(前端 1.5s 轮询)、POST /api/resumes/generate/tasks/{id}/cancel;旧POST /generate(SSE)原样保留,不破坏既有流式语义与一批测试。三条数据一致性保证:只在完成时落库简历(取消/失败都不写半成品)、取消标记cancelled且不落库(线程在流循环里检查停止信号,except asyncio.CancelledError标 cancelled,复制assistant_stream.py的取消范式)、防重复(已有进行中任务时返回409,连点两次不会造出两份任务/两份简历)。前端关掉弹窗 = 后台继续(不再是maskClosable=false),关掉后仍按 1.5s 轮询,完成后弹「简历已生成」通知并直接打开结果预览;配置页的「请勿关闭弹窗」文案同步改掉。前后端均有测试:阶段映射与字数计数、后端无 progress 不崩、完成落库、取消不落库、重复拒绝、后台状态流转、迁移表集合不变/旧备份兼容/可 downgrade。 -
简历预览在内容超出时不再"裁掉看不见",而是展开成按 A4 高度切分的近似分页。此前内容超出所选页数时,预览被
overflow:hidden直接裁掉,屏幕上只剩一句"内容超出了 N 页"——用户既看不出超了多少、也看不出超出的内容长什么样。现在:① 预览自己用浏览器量到的版面算出"按当前设置约需几页(上限几页)、正文还多出多少"(与后端derive_pages同一套ceil算术口径,但不走网络、也不依赖只在 PDF 导出才有的X-Resume-Pages头),紧贴预览上方醒目展示;② 内容确实超出时,放不下的部分继续向右并排展开成第 2、3…张 A4(左右铺开、水平滚动,用 CSScolumns让同一份连续流"报纸式"横排到 N 列,不造页盒、不引入第二套分页口径,各列共用同一个--fit-scale,所以第 2 页的字号 / 边距与第 1 页天然一致),每页边界画一条虚线并标注"第 N 页 / 共 M 页(近似)";③ 全程明确标注这是"按 A4 高度切分的近似分页",真实分页以「浏览器打印 / 下载 PDF」为准——不标注的近似等于新的"看起来真、其实不准"(有测试盯着标注不能被单独去掉)。另在「浏览器打印 / 另存为 PDF」入口补了一句悬停提示:打印分页以浏览器为准、可能与屏幕近似分页略有不同(浏览器不给 JS 暴露打印页数,应用内算不出真实打印页数,只能如实说明"打印是权威、屏幕是近似")。两条边界:不改用户存的page_limit(多页只作展示、不影响导出);内容装得下时行为与之前完全一致(仍缩到一页、仍overflow:hidden),只有确实超出才切多页。 -
求职助手正文里的
[来源N]现在能点击跳转到对应原文了。开启联网搜索时,模型正文会写[来源1]这类编号引用,此前它们是纯文本、点不动(行内 Markdown 只认裸 URL 与[文字](链接),不认[来源N])。之前刻意没做,是因为编号不可靠:自动预搜与每次工具搜索各自从 1 重新编号,而界面展示的是"按 URL 去重合并"后的列表,编号对不上、会跳错来源。现在改成全局稳定编号:一次回答里的每个来源在首次收集时分配一个全局单调递增编号(跨自动预搜与所有工具搜索共用一个SourceNumberer计数器),web_context与工具搜索结果都用这同一个编号输出,并把"编号 → URL"映射持久化进助手消息的context.source_map;前端[来源N]的链接 href 只用这份映射(绝不用去重后的数组下标),所以模型当时看到的第 N 条就是用户点到的第 N 条。宁可不可点、也不跳错:映射缺失 / 编号越界 / URL 不是 http(s) 时一律退化成纯文本,不渲染链接、不报错。系统提示同步把"可按编号引用"改成"编号在本次回答内唯一、可直接引用"。 -
求职助手:开启「思考强度」后可以查看模型的思考过程。回复下方新增默认折叠的「思考过程」面板,点开即可看到模型作答前的推理(流式生成过程中就能实时展开查看,历史消息里也能回看);没有思考内容时整块不渲染,所以不开思考、或更早存下的历史消息的界面与之前完全一致。四层一起打通:provider 协议新增
reasoning帧(OpenAI 兼容系读reasoning_content(DeepSeek)与reasoning,原生 Anthropic 读thinking_delta——字段名缺失是常态,缺失时什么都不产出、绝不臆造)、SSE 新增reasoning事件、思考内容按tool_calls同款写进助手消息的context供历史回看。两条刻意的边界:思考内容有存储上限(MAX_REASONING_CONTEXT_CHARS=20000,超出如实截断并在context.reasoning_truncated标记,既不静默丢弃、也不让库里字段无限膨胀);思考内容不回灌给模型(它只是给用户看的解释,塞回下一轮会让模型把自己的草稿当成事实,也会触发 Anthropic 的协议要求)。已知限制:原生 Anthropic(api_style: anthropic)+ 思考强度 + 多轮工具调用时,官方要求把上一轮的思考块原样回传给下一轮,这一步尚未实现,该组合的第二轮可能被服务端以 HTTP 400 拒绝;已在docs/user-guide.md「常见问题」与代码注释里说明,遇此情况把「思考强度」调回「关闭」或改用 OpenAI 兼容协议即可(只回答、不调用工具时不受影响)。 -
求职助手可制作「简历格式模板」与带知识文件的「助手技能」。新增
create_format_template/update_format_template两个工具,只处理格式模板(强调色 / 行高 / 页边距 / 区块间距 / 字号系数),参数清单与范围直接取自格式模板编辑器用的同一份FORMAT_FIELDS,落库复用工作台既有的create_user_template/update_user_template(重名拒绝、总量上限 40、名称字符集都沿用后端原话回给模型,不另写一份校验);update只改一项时合并进现有config,不会把其它已调好的参数悄悄清空。create_skill/update_skill补上files知识文件参数(单文件 ≤ 20000 字符、合计 ≤ 60000、最多 10 个,工具描述里对模型明说上限)。刻意不让助手生成样式模板(完整 HTML):模型长篇写 HTML 容易出错,且保存前会经sanitize_template_html清洗掉脚本与外链,用户可能拿到"看起来生成了...