feat(personal-assistant): add confirmed project memory

This commit is contained in:
2026-07-19 08:19:24 +08:00
parent 151cc26caf
commit 24da833fa6
31 changed files with 4037 additions and 60 deletions
+53
View File
@@ -14,6 +14,7 @@
| 案例沉淀 `/knowledge/cases` | `GET /api/knowledge/case/capabilities`、`POST /api/knowledge/case/upload`、`/organize`、`/curate`、`GET /records`、`GET /records/{caseId}`、`POST /records/{caseId}/review`、`GET /records/{caseId}/media` | `/capabilities` 返回服务端判定的案例提交/查看能力,移动端不再向普通员工展示无权提交的素材表单;`/upload` 改为 multipart 真实语音上传并走 ASR,服务端只接受 MP3/WAV/M4A/WebM/OGG/AAC/FLAC,成功后原始音频写入 `sys_oss`,案例记录只保存 `mediaOssId` 供受保护媒体接口读取,不向客户端回传原始 `mediaUrl`;`/organize` 用真实转写调 chat 模型整理案例,未配置模型时按真实 transcript 本地结构化,并从背景外的真实摘要项提取学习点;APP 用户的项目范围从 `aihr_org_snapshot` 登录身份解析,上传、整理、入库、列表和详情均按项目范围校验,未完成正式组织映射时安全拒绝,不接受前端伪造项目范围;员工列表/详情只返回 `已入库` 案例,管理端系统用户保留全局运营视图;移动端和管理端案例详情通过受保护媒体接口回放原始音频,主管/项目负责人可提交脱敏点评;预渲染视频样片仍待正式媒体资产接入 |
| 案例媒体安全 | `GET /api/knowledge/case/records/{caseId}/media` | 案例详情只返回 `mediaOssId`,不返回原始 `sys_oss.url`;媒体下载会重复执行登录、后台角色或 APP 项目范围校验,再由服务端流式读取 OSS。管理端与 `mobile-uni` 通过鉴权 blob/temp 文件播放,关闭详情页时释放本地对象 URL |
| 问师傅与 SOP 知识库 `/pages/user/sop/index`、`/knowledge/sop` | `POST /api/knowledge/query`、`POST /api/knowledge/query-media`、`GET /api/knowledge/resources/{attachmentId}/content`、`POST /api/knowledge/answer-feedback`、`GET /api/knowledge/position-sop`、`GET /api/aihr/mobile/onboard/tasks`、`POST /api/aihr/mobile/onboard/tasks/{id}/complete`、`GET /api/aihr/mobile/qualification`、`POST /api/knowledge/doc/upload` | 文字/现场附件查询走统一知识空间授权和 MySQL Fulltext + Qdrant 混合召回;内部登录端可选传 `conversationId/contextVersion`,服务端保留 30 分钟不活跃过期、最近 6 轮脱敏截断上下文,用现有 chat 模型或保守规则输出 `QA/FILE/VIDEO/DATA_TOOL` 和 `rewrittenQuery`,旧版本冲突返回 `409`。原文件/视频仅从当前命中且仍有授权的附件返回,内容接口每次重新计算租户、应用和主体权限;无资源时明确返回空列表,不生成假链接。旧客户端和外部 API 保持单轮无状态。搜索继续返回 `reviewId/promptVersion`,员工反馈保存评审批次;详细边界见 [专项设计](20260718/问师傅多轮会话与原始资料交付设计.md)。员工学习页按当前 APP 身份读取正式岗前/入职任务,资格证据无正式数据时明确返回 `NOT_CONFIGURED` |
| 工作助手确认式项目记忆 `/pages/user/sop/index`、`/pages/user/assistant/memories` | 既有 `POST /api/knowledge/query` 可选返回 `memoryCandidate`;`GET /api/aihr/personal-assistant/memory-candidates?status=DRAFT`、`POST /api/aihr/personal-assistant/memory-candidates/{id}/confirm`、`POST .../{id}/dismiss`、`GET /api/aihr/service-memories`、`GET /api/aihr/service-memories/{id}` | 2026-07-19 最小切片只识别 `RESIDENT_NEED/FOLLOW_UP` 并保存到 `PROJECT_SERVICE`。用户文字或 ASR 转写进入同一候选检测;候选草稿不是长期记忆,只有当前员工显式确认后才按服务端解析出的 `tenant_id + project_code` 写入项目服务记录和版本表。多项目无法唯一判断时返回 `NEEDS_INPUT`,不能显示确认按钮;确认携带 `expectedVersion + idempotencyKey`,同键同内容重放返回原结果,同键不同内容或旧版本返回冲突。提醒默认关闭;首批不创建个人知识库、Qdrant collection,也不把个人想法写入项目域 |
| 资料处理 `/knowledge/processing` | `GET /api/knowledge/processing/overview`、`POST /api/knowledge/doc/upload-async`、`GET /api/knowledge/doc/upload-items`、`POST /api/knowledge/doc/upload-items/{id}/retry` | 已接入解析任务状态聚合;页面只保留“批量导入”,接口暂存+入队即秒回,后台 worker(并发 2)逐条解析/归类/向量化;ZIP 在 worker 内安全解压后把支持的子文件继续入同一批次队列,页面按批次轮询进度、失败可单文件重试;不提供浏览器目录选择或服务端目录导入入口 |
| 组织人员同步 | `POST /api/aihr/org/sync` | 从开放组织同步系统的 `/api/open/v1/sync/snapshot` 拉取 `company/department/employee` 快照,分页参数使用 `limit`;员工手机号只落 `person_phone` 用于移动端身份映射,不在组织列表响应暴露;岗位识别 `position/job_title/post/job_name/role/title` 等字段。`dryRun` 必须显式传入 `true`(预检)或 `false`(写入),省略或传 `null` 直接拒绝,避免空请求意外写库;默认 `replaceExisting=true`,写入前必须先用 `{"dryRun":true}`;dry-run 不访问本地快照表、不执行 DDL/写库,返回 `phoneLinked/maskedPhone/suspectText/warnings` 且 `syncedCount=0`。非 dry-run 覆盖写入遇到员工被跳过、手机号不完整、脱敏手机号或疑似乱码时默认拒绝,只有确认 dry-run 结果后显式传 `allowPartialReplace=true` 才允许覆盖;重复 `ext_party_id` 始终拒绝写入,因为数据库唯一键会折叠重复身份;`replaceExisting=false` 不触发不完整快照覆盖闸门,但仍拒绝重复身份。2026-07-15 线上已用 `/api/open/v1` 前缀完成配置、dry-run 和覆盖同步:开放平台返回公司 17、部门 963、员工 3417;生产 `aihr_org_snapshot` 为 3417 行,`phoneLinked=3392`、`maskedPhone=25`、`suspectText=0`,在职 2943、离职 474。当前剩余 25 人手机号不可用属于上游数据质量问题;功能发布不再被组织同步能力阻塞 |
| 移动端手机号登录 | `GET /resource/sms/code`、`POST /auth/mobile/sms-login` | 已复用 sms4j 阿里云配置 `config1` 和 RuoYi `sms` 授权策略;短信发送成功后才写 Redis 验证码;手机号不存在时自动注册 `app_user`;`aihr.sms.dev-fixed-code` 非空时不真发短信、验证码固定(dev 默认 `123456`)。prod 默认关闭,试点期只有同时设置 `AIHR_SMS_DEV_FIXED_CODE` 与 `AIHR_SMS_PROD_FIXED_CODE_ENABLED=true` 才启用固定码。 |
@@ -220,3 +221,55 @@ Content-Type: application/json
成功响应包含 `requestId`、`answer`、`citations`、`usedSpaceCodes`、`noEvidence` 和兼容 `legacy` 数据。引用携带 `spaceCode/sourceType/docId/title/snippet/fragmentId`;客户端不得把无引用回答包装成有知识依据的正式答案。
空间、授权和应用管理接口统一位于 `/api/knowledge/admin`:`spaces`、`spaces/{id}/grants`、`spaces/{id}/documents`、`apps`、`apps/{id}/spaces`、`apps/{id}/rotate-token`。`DELETE /spaces/{id}/documents/{attachId}` 只解绑当前空间成员;其他空间仍引用同一 OSS 时原文件不会删除。仅知识平台管理角色可调用,API_TOKEN 创建或轮换后只返回一次明文。完整初始化、令牌保管、legacy 迁移与回滚步骤见 [KNOWLEDGE_PLATFORM_RUNBOOK.md](KNOWLEDGE_PLATFORM_RUNBOOK.md)。
## 工作助手项目服务记忆最小切片
该切片复用上面的短会话请求,不新增另一套问答 session。登录员工说“记一下:3 栋 2 单元 1201 室住户想了解养老服务,十天后回访”时,`POST /api/knowledge/query` 在正常回答字段之外可返回:
```json
{
"memoryCandidate": {
"id": 301,
"version": 1,
"status": "DRAFT",
"memoryType": "RESIDENT_NEED",
"targetDomain": "PROJECT_SERVICE",
"draft": {
"buildingName": "3栋",
"unitName": "2单元",
"roomNo": "1201",
"category": "养老服务咨询",
"summary": "住户希望了解社区养老服务",
"occurredAt": "2026-07-19T14:00:00+08:00",
"followUpAt": null
},
"missingFields": [],
"expiresAt": "2026-07-20T14:00:00+08:00"
}
}
```
确认请求只提交用户在卡片上确认或修改的草稿;`projectCode` 即使传入也不能作为授权依据:
```http
POST /api/aihr/personal-assistant/memory-candidates/301/confirm
Authorization: Bearer <session-token>
Content-Type: application/json
{
"expectedVersion": 1,
"idempotencyKey": "memory-confirm-301-<stable-uuid>",
"enableReminder": false,
"draft": {
"buildingName": "3栋",
"unitName": "2单元",
"roomNo": "1201",
"category": "养老服务咨询",
"summary": "住户希望了解社区养老服务",
"occurredAt": "2026-07-19T14:00:00+08:00",
"followUpAt": null
}
}
```
服务端必须在事务中锁定候选,重新解析当前员工有效项目,写入 `aihr_service_memory` 与 `aihr_service_memory_version` 后再把候选改为 `CONFIRMED`。`DRAFT` 默认 24 小时过期;`NEEDS_INPUT` 只用于继续追问。列表、详情和后续问答召回必须同时过滤当前租户和授权 `project_code`,不得因为记录由本人创建而绕过项目权限。