Resume-Matcher 求职看板(Application Tracker)完全指南:七列 Kanban 流水线的自动建卡、拖拽排序与 API 深度解析
Resume-Matcher 求职看板Application Tracker完全指南七列 Kanban 流水线的自动建卡、拖拽排序与 API 深度解析【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher导读Application Tracker 是 Resume-Matcher 内置的求职申请看板前端路由/tracker为每一份经过 Tailor 流程定制的简历提供一个按七个状态分列的 Kanban 位置实现从简历定制到投递进度管理的无缝衔接。本文以 docs/agent/features/application-tracker.md 为骨架结合后端 ORM 模型、Pydantic Schema、FastAPI 路由、数据层 CRUD 实现与前后端测试完整讲解看板的数据模型、自动建卡机制、手动添加流程、拖拽排序算法、批量操作与全部 REST API帮助你掌握该模块的架构设计与二次开发要点。功能概览Application Tracker 的核心价值在于让简历定制与投递跟踪形成闭环当你在 Tailor 流程中确认一份定制简历后系统会自动在看板的applied列创建一张卡片记录哪份简历投了哪个岗位你随后可以手动添加卡片、拖拽卡片跨列流转如从applied拖到interview、accepted并在详情弹窗中随时回看当时的 JD 与被投递的简历版本。看板在代码中由以下核心文件构成层级文件职责数据模型apps/backend/app/models.pyApplicationORM 模型SQLAlchemy 2.0 风格Mapped注解请求/响应契约apps/backend/app/schemas/applications.pyPydantic 模型 ApplicationStatus枚举路由apps/backend/app/routers/applications.py全部看板端点prefix/applications数据访问apps/backend/app/database.py门面式 CRUD、批量操作与列内 position 重排自动建卡钩子apps/backend/app/routers/resumes.pyTailor confirm 成功后自动落卡前端 API 客户端apps/frontend/lib/api/tracker.ts类型化请求封装前端拖拽逻辑apps/frontend/components/tracker/reorder.ts纯函数planMove解析拖拽落点看板页面apps/frontend/app/(default)/tracker/page.tsx/tracker/page.tsx)路由页面七列状态稳定键与 i18n 标签解耦看板固定为七列每一列对应一个稳定键stable key前后端共用saved · applied · no_response · response · interview · accepted · rejected这套键定义在后端 schemas/applications.py 的ApplicationStatus枚举中前端在 lib/api/tracker.ts 以同名字符串字面量联合类型镜像了一份并定义了渲染顺序APPLICATION_STATUS_ORDERclass ApplicationStatus(str, Enum): saved saved applied applied no_response no_response response response interview interview accepted accepted rejected rejected APPLICATION_STATUS_ORDER: list[str] [s.value for s in ApplicationStatus]设计要点稳定键与界面显示文案i18n完全解耦。界面上的列标题、按钮文字等通过 frontend/i18n 的语言包翻译而数据库存储、API 传输、前后端状态同步一律使用稳定键。这样即使某语言的列名被翻译为已投递面试中等不同表述数据层与接口契约都不会受影响也便于新增语言。默认落列规则Tailor 流程自动创建的卡片落入applied表示已投递手动添加的卡片默认也是applied但可以通过status参数显式创建为saved收藏/备选不产生applied_at时间戳。从数据层实现看create_application只有在status ! saved时才会自动填充applied_at为当前时间见 database.py即saved列是唯一一个未投递时间的列。数据模型Application 实体Application表定义在 apps/backend/app/models.py字段如下字段类型说明application_idString (PK)卡片主键uuid4()生成job_idString (indexed)关联的岗位记录JDresume_idString (indexed)投递/定制后的简历 ID详情弹窗展示、Edit 打开的对象master_resume_idString, nullable可选的基础母版简历 ID——定制简历所派生的源简历用于共享简历徽标与堆叠分组statusString (indexed)七列稳定键之一默认appliedcompanyString, nullable公司名roleString, nullable岗位名称applied_atString, nullable投递时间ISO 字符串notesText, nullable备注positionInteger卡片在所属列内的顺序索引由服务端在 PATCH 时统一重排created_at/updated_atStringUTC ISO 时间戳值得注意的两处设计1. 并发安全去重。表上定义了UniqueConstraint(job_id, resume_id)uq_application_job_resume即同一岗位 同一投递简历只允许一张卡片。数据层的create_application采用先 select 再 insert策略先查已有卡片命中则原样返回不重复建卡若并发请求同时写入触发IntegrityError则回滚后重新查询并返回胜出者写入的那张卡见 database.py。这一机制让 confirm 接口的重复提交/重试都能安全幂等。2. 列内排序由服务端维护。position由服务端重排而非客户端指定绝对值——客户端在 PATCH 时只传期望插入的目标索引数据层会将该列所有卡片重排为连续的0..n-1序列。list_applications也统一按(status, position)排序返回database.py。五大核心流程1. 自动建卡Tailor 确认后零额外 LLM 调用这是该模块最巧妙的设计。POST /resumes/improve/confirm以及旧版POST /resumes/improve在持久化定制简历之后会调用_auto_create_tracker_application钩子resumes.pyasync def _auto_create_tracker_application( *, job_id: str, tailored_resume_id: str, master_resume_id: str, job: dict[str, Any] | None, title: str | None, ) - None: Best-effort: drop an applied card on the tracker after a tailoring. try: company (job or {}).get(company) role title or (job or {}).get(role) await db.create_application( job_idjob_id, resume_idtailored_resume_id, master_resume_idmaster_resume_id, statusapplied, companycompany, rolerole, ) except Exception as e: # noqa: BLE001 - tracker is non-critical logger.warning(Failed to auto-create tracker application: %s, e)实现要点公司名与岗位名来自缓存的 keyword-extraction 结果job 记录上的company/role字段因此这条路径上不会产生任何额外的 LLM 调用——LLM 开销在 Tailor 预览阶段已经付过了role优先取 job 上的role缺失时回退到title定制简历的生成标题如 Senior Backend Engineer - Acme Corp整个钩子被try/except包裹best-effort 语义tracker 的任何失败数据库不可用等都只会记录 warning绝不破坏已经成功的简历定制主流程。集成测试 test_tracker_autocreate.py 完整验证了这条链路它驱动一次真实的/improve/preview → /improve/confirm往返所有 LLM 边界均 mock然后断言applied列出现一张卡片且company Acme Corp来自缓存的 job、role Senior Backend Engineer - Acme Corp回退到标题、master_resume_id指向原始简历——同时 confirm 路径上公司/岗位零额外 LLM 调用。2. 手动添加粘贴 JD 自动建卡用户也可以在未经过 Tailor 流程的情况下从一段粘贴的 JD 直接创建卡片。POST /applications的处理逻辑applications.py先用db.create_job(contentjob_description, resume_id...)创建 job 记录若请求中未提供company/role调用_extract_company_role做一次尽力而为的提取——内部复用extract_job_keywords即 Tailor 流程同一套缓存 keyword 提取通道对返回结果做类型守卫后取company/role任何失败都回退为空字符串可后续编辑绝不让一次不稳定的 LLM 调用阻塞建卡applications.py创建 application 卡片若创建失败会清理掉刚创建的 job避免孤儿 job 造成重试漂移提取出的 company/role 以 best-effort 方式写回 job 记录缓存供后续复用同样 never 500。值得学习的是这个回滚兜底模式create_application失败时先尝试db.delete_job清理孤儿数据清理本身也包裹 try/except 仅记录 warning。3. 拖拽排序前端乐观更新 服务端重排看板使用 dnd-kit 实现拖拽关键设计是把拖拽结束后的落点解析抽成纯函数planMovereorder.tsoverId既可以是卡片 ID也可以是列级 droppable ID形如column:status因此空列也是合法落点同列内重排用arrayMove调整顺序并把该列所有position重新编号为连续的0..n-1跨列移动从源列 splice 出卡片插入目标列指定索引空列则追加到末尾同时更新status并重排两个受影响的列返回null表示 no-op拖回原位上层无需发请求返回的MovePlan同时给出next乐观渲染的完整新列状态与status/position要发给服务端的 PATCH 载荷保证乐观状态与服务端持久化结果一致。前端在收到 PATCH 失败时会回滚到旧状态PATCH /applications/{id}的数据层实现database.py会先将该行position临时置为10_000_000停靠出去flush 后重排旧列若跨列、再按目标索引将本行 splice 进新列并整体重排最终保持每列 position 连续。4. 详情弹窗JD 投递简历一次往返GET /applications/{id}一次往返返回卡片 内嵌 JD 投递的简历applications.pyjob_content取 job 记录的内容resume取resume_id对应的简历记录容忍简历被删除若引用的简历已被删除返回resume: null而非 500前端弹窗渲染resume unavailable占位详情上下文的加载同样是 best-effort包裹在 try/except 中仅记录 warning卡片的加载失败才返回 404。对应的响应模型 ApplicationDetailResponse 在卡片字段基础上追加了可空的job_content与resume。弹窗中的Edit按钮会以resume_id打开/builder?idresume_id进入简历构建器继续编辑。5. 批量操作多选后一次请求移动/删除看板顶部提供批量操作栏多选卡片后可一次完成PATCH /applications/bulk把多张卡移动到同一列返回{message, affected}POST /applications/bulk-delete一次删除多张卡。数据层的批量实现database.py先收集受影响的旧列集合将所有目标行临时赋予20_000_000 index的占位 positionflush 后统一对涉及的旧列与目标列重排保证批量移动/删除后各列 position 依旧连续。REST API 参考所有看板端点挂在/api/v1前缀下路由前缀/applications完整清单如下同 文档 的 API 表MethodPathPurposeGET/applications所有卡片按列分组返回7 个键始终齐全POST/applications手动添加先建 job 再建卡best-effort 提取 company/roleGET/applications/{id}卡片 内嵌 JD 简历简历删除时为 nullPATCH/applications/{id}更新 status/position/notes/company/role/applied_atPATCH/applications/bulk多卡移动到同一列DELETE/applications/{id}删除单卡POST/applications/bulk-delete批量删除分组响应与未知状态容错GET /applications的_group_by_statusapplications.py会先以 7 个稳定键初始化空列保证响应中 7 个键始终存在逐条按status归组若某行出现未知状态例如历史脏数据跳过该行并记录 warning 而不是让整个看板 500——因为看板只渲染 7 个已知列未知状态无法被枚举背书的ApplicationResponse表示。关键请求/响应示例来自 schemas/applications.pyPOST /applications请求体{ resume_id: 3fa85f64-..., job_description: Senior Backend Engineer at Acme Corp: Python, FastAPI., company: Acme Corp, role: Senior Backend Engineer, status: applied, notes: 投递备注 }说明resume_id必填job_description必填且min_length1company/role可选覆盖缺省时走 best-effort 提取status默认applied可显式设为savednotes可选。PATCH /applications/{id}请求体全部字段可选model_dump(exclude_unsetTrue)只提交显式传入的字段status会被归一化为枚举的字符串值后再进数据层{ status: interview, position: 0, notes: 一轮技术面已约, company: Acme Corp, role: Senior Backend Engineer, applied_at: 2026-09-01T09:30:00Z }PATCH /applications/bulk与POST /applications/bulk-delete均要求application_ids非空数组min_length1分别配合目标status成功响应统一为{message: ..., affected: n}。前端实现细节前端看板由 components/tracker 下的组件族构成kanban-board.tsx看板主体、kanban-column.tsx列、application-card.tsx卡片、card-detail-modal.tsx详情弹窗、bulk-action-bar.tsx批量操作栏、manual-add-application-dialog.tsx手动添加弹窗。API 客户端 lib/api/tracker.ts 提供了类型完整的封装listApplications/createApplication/getApplicationDetail/updateApplication/bulkUpdateStatus/deleteApplication/bulkDeleteApplications与后端路由一一对应。一个值得一提的健壮性细节是extractDetail函数FastAPI 的HTTPException将detail返回为字符串而参数校验错误返回的是{msg, loc, ...}数组——该函数对两种形态统一做字符串化避免前端把错误渲染成[object Object]。测试覆盖该模块的测试分前后端两组可作为验证行为契约的权威依据后端apps/backend/testsintegration/test_applications_api.pyCRUD、列分组、详情容错简历删除时resume: null、批量移动/删除integration/test_tracker_autocreate.pyconfirm 自动创建applied卡、company/role 取值来源、幂等去重unit/test_database.py 的TestApplications数据层去重、position 重排等单元级验证。前端apps/frontend/teststracker-reorder.test.tsplanMove的同列内重排、跨列移动、空列落点、no-op 判定等纯逻辑api-tracker.test.ts客户端请求载荷与 URL 正确性。关键文件索引文件用途apps/backend/app/models.pyApplicationORM 模型apps/backend/app/schemas/applications.pyPydantic 请求/响应模型 状态枚举apps/backend/app/routers/applications.py看板端点apps/backend/app/database.py门面式 CRUD/批量/重排方法apps/backend/app/routers/resumes.py_auto_create_tracker_application自动建卡钩子两条 confirm 路径共用apps/backend/app/services/improver.py app/prompts/templates.py公司/岗位加入 keyword 提取apps/frontend/app/(default)/tracker/page.tsx/tracker/page.tsx)看板路由apps/frontend/components/tracker/看板、列、卡片、详情弹窗、批量栏、手动添加弹窗apps/frontend/components/tracker/reorder.ts纯拖拽落点解析planMoveapps/frontend/lib/api/tracker.ts类型化 API 客户端小结Application Tracker 的设计贯穿了三条值得复用的工程原则主流程非关键路径best-effort隔离——tracker 的任何失败都不影响简历定制LLM 成本复用——自动建卡复用缓存的 keyword 提取结果做到零额外模型调用状态一致性与容错——前端纯函数解析拖拽保证乐观更新与服务端一致未知状态/已删简历等边界情况均以降级而非报错处理。若你要扩展该模块例如新增列、增加自定义字段或接入外部 ATS 同步建议从 schemas/applications.py 的枚举与 models.py 的字段开始并同步更新 lib/api/tracker.ts 的类型定义最后用文档列出的两组测试验证端到端行为。【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考