From 7c9b837dd320b2a0a124a91f2851b15b04cb6297 Mon Sep 17 00:00:00 2001 From: let5sne Date: Sat, 25 Jul 2026 14:17:04 +0800 Subject: [PATCH] docs(broadcast): plan multi-role file insights --- ...-broadcast-multi-role-document-insights.md | 522 ++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-25-broadcast-multi-role-document-insights.md diff --git a/docs/superpowers/plans/2026-07-25-broadcast-multi-role-document-insights.md b/docs/superpowers/plans/2026-07-25-broadcast-multi-role-document-insights.md new file mode 100644 index 00000000..ad6980e2 --- /dev/null +++ b/docs/superpowers/plans/2026-07-25-broadcast-multi-role-document-insights.md @@ -0,0 +1,522 @@ +# 大喇叭公司文件多岗位解读 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 在现有“大喇叭消息 + 单个公司文件”链路中生成有原文依据的多岗位解读,并让员工默认看到自己岗位视角、可切换查看全部相关视角。 + +**Architecture:** 复用现有附件解析、摘要、鉴权下载和消息追问链路。新增一个职责单一的 `AihrBroadcastInsightService`,使用现有模型调用边界按自然段分块生成结构化岗位解读,服务端只保留引用能在原文块中精确命中的内容;附件文件状态与解读状态分离,解读失败不阻塞文件发布和阅读。 + +**Tech Stack:** Java 17、Spring Boot、JdbcTemplate、Jackson、MySQL 8、Vue 3、uni-app、TypeScript、Node test、JUnit 5/Mockito。 + +--- + +## 文件职责 + +- Create `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastInsightService.java` + - 固定岗位目录、岗位映射、自然段分块、模型调用、JSON 清洗、原文引用校验、跨块合并和响应反序列化。 +- Modify `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentService.java` + - 文件提取完成后先保存 `READY`,再独立领取和处理岗位解读任务;恢复卡死任务。 +- Modify `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastService.java` + - 员工详情查询解读字段,基于认证手机号对应的在职组织岗位计算默认视角,不返回 `extracted_text`。 +- Modify `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastDto.java` + - 增加岗位解读、来源依据和解读状态字段。 +- Create `backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql` + - 为附件表增加 `insight_status`、`insights_json`、`insight_version` 及解读队列索引。 +- Create `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastInsightServiceTest.java` + - 覆盖岗位映射、自然段分块、伪造引用剔除、跨块合并和部分成功状态。 +- Modify existing broadcast backend tests + - 覆盖文件与解读状态解耦、DTO 不泄漏全文、租户/撤回/在职身份约束不回退。 +- Modify `mobile-uni/src/services/broadcast.ts` + - 声明岗位解读响应类型。 +- Modify `mobile-uni/src/pages/user/broadcast/detail.vue` + - 在文件摘要之后展示默认岗位、岗位切换、五类要点和折叠的原文依据。 +- Modify `mobile-uni/src/pages/user/broadcast/index.vue` + - 将通知命中标签“与我有关”改为“定向提醒”。 +- Create `mobile-uni/tests/broadcast-multi-role-insight.test.mjs` + - 覆盖类型契约、默认视角、切换、依据折叠、降级状态及术语改名。 +- Modify `scripts/reset-dev-db.sh`, `scripts/tests/aihr-schema-migrations.test.sh`, `scripts/release-preflight.sh`, `scripts/demo-check.sh` + - 纳入正式迁移与远端只读 schema 合同。 +- Modify `docs/API_INTEGRATION.md`, `docs/BRD_IMPLEMENTATION_AUDIT.md` + - 记录接口字段、生成状态、失败降级和验收边界。 + +### Task 1: 数据库合同与 DTO + +**Files:** +- Create: `backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql` +- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastDto.java` +- Modify: `scripts/tests/aihr-schema-migrations.test.sh` +- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java` + +- [ ] **Step 1: 写失败的 schema 与 DTO 合同测试** + +在迁移测试中要求以下三列存在: + +```bash +SELECT COUNT(*) +FROM information_schema.columns +WHERE table_schema='$DB' + AND table_name='aihr_broadcast_attachment' + AND column_name IN ('insight_status','insights_json','insight_version'); +``` + +在 `AihrBroadcastAttachmentContractTest` 中断言响应包含 `insightStatus`、`defaultPerspectiveCode`、`perspectives`,同时不包含 `extractedText` 和 `insightsJson`。 + +- [ ] **Step 2: 运行测试并确认失败** + +Run: + +```bash +bash scripts/tests/aihr-schema-migrations.test.sh +mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ + -Dtest=AihrBroadcastAttachmentContractTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +``` + +Expected: schema 测试缺少 3 列,Java 测试缺少新的 record component。 + +- [ ] **Step 3: 写幂等迁移和最小 DTO** + +迁移增加: + +```sql +ALTER TABLE aihr_broadcast_attachment + ADD COLUMN insight_status varchar(20) NOT NULL DEFAULT 'NOT_PROCESSED' AFTER summary, + ADD COLUMN insights_json longtext NULL AFTER insight_status, + ADD COLUMN insight_version varchar(30) NULL AFTER insights_json; +``` + +迁移必须通过 `information_schema` + prepared statement 分列幂等执行,并增加: + +```sql +KEY idx_aihr_broadcast_attachment_insight_queue + (insight_status, status, update_time) +``` + +DTO 增加: + +```java +public record BroadcastEvidence(int paragraphIndex, String quote) {} + +public record BroadcastPerspective( + String code, + String label, + String summary, + List concerns, + List impacts, + List actions, + List risks, + List evidence +) {} +``` + +附件响应追加: + +```java +String insightStatus, +String defaultPerspectiveCode, +List perspectiveLabels, +List perspectives +``` + +- [ ] **Step 4: 运行测试并确认通过** + +Run the two Task 1 commands. + +Expected: PASS,迁移可重复执行,DTO 没有原文和 JSON 存储字段。 + +- [ ] **Step 5: 提交** + +```bash +git add backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql \ + backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastDto.java \ + backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java \ + scripts/tests/aihr-schema-migrations.test.sh +git commit -m "feat(broadcast): add multi-role insight contract" +``` + +### Task 2: 岗位解读生成器 + +**Files:** +- Create: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastInsightService.java` +- Create: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastInsightServiceTest.java` + +- [ ] **Step 1: 写失败的核心逻辑测试** + +使用 Mockito 模拟 `AihrModelSeedService.chat` 返回 OpenAI-compatible JSON,覆盖: + +```java +assertEquals("finance", AihrBroadcastInsightService.defaultPerspectiveCode(List.of("财务经理"))); +assertEquals("living_advisor", AihrBroadcastInsightService.defaultPerspectiveCode(List.of("物业管家"))); +assertNull(AihrBroadcastInsightService.defaultPerspectiveCode(List.of("未知岗位"))); +assertTrue(AihrBroadcastInsightService.chunks(longParagraphText).size() > 1); +assertTrue(result.perspectives().getFirst().evidence().stream() + .allMatch(item -> source.contains(item.quote()))); +assertFalse(resultJson.contains("模型编造但原文不存在")); +``` + +另测第二块模型失败时保留第一块结果并返回 `PARTIAL`,所有块失败时返回 `FAILED`。 + +- [ ] **Step 2: 运行测试并确认失败** + +Run: + +```bash +mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ + -Dtest=AihrBroadcastInsightServiceTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +``` + +Expected: FAIL because the service does not exist. + +- [ ] **Step 3: 实现最小生成器** + +固定九个角色: + +```java +private static final Map ROLE_LABELS = Map.ofEntries( + entry("living_advisor", "生活顾问/客服"), + entry("cleaning", "保洁"), + entry("security", "保安"), + entry("engineering", "保修/工程"), + entry("finance", "财务"), + entry("hr", "人力"), + entry("operations", "业务运营"), + entry("audit_risk", "审计风控"), + entry("management", "管理层") +); +``` + +自然段块目标 4,000 字、相邻重叠 300 字,最多 80 块。每块调用: + +```java +modelService.chat(new ChatRequest( + prompt(fileName, chunk, paragraphOffset), + null, + "只根据给定原文返回 JSON;未提及的岗位不要输出;evidence.quote 必须逐字复制原文。" +)); +``` + +只接受 `mode == "openai-compatible"` 且 `error` 为空的响应。解析后执行: + +```java +if (!chunk.text().contains(evidence.quote())) { + continue; +} +``` + +每岗位最多 5 条 concerns/impacts/actions/risks、3 条 evidence;文本去空白、去重、限制长度;仅保留固定角色代码。成功块少于总块数或正文超过 80 块时为 `PARTIAL`,全部失败为 `FAILED`。 + +- [ ] **Step 4: 运行测试并确认通过** + +Run the Task 2 Maven command. + +Expected: PASS. + +- [ ] **Step 5: 提交** + +```bash +git add backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastInsightService.java \ + backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastInsightServiceTest.java +git commit -m "feat(broadcast): generate grounded role insights" +``` + +### Task 3: 附件异步链路与员工详情 + +**Files:** +- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentService.java` +- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastService.java` +- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastServiceTest.java` +- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java` + +- [ ] **Step 1: 写失败的链路测试** + +测试源代码和 JDBC 参数,要求: + +```java +assertTrue(attachmentSource.contains("set status = 'READY', extracted_text = ?, summary = ?")); +assertTrue(attachmentSource.contains("insight_status = 'PENDING'")); +assertTrue(attachmentSource.contains("insight_status = 'FAILED'")); +assertTrue(detailSql.contains("a.insights_json as attachment_insights_json")); +assertTrue(detailSql.contains("m.status = 'PUBLISHED'")); +assertFalse(responseFields.contains("extractedText")); +``` + +新增详情测试:岗位 `财务主管` 默认代码为 `finance`;同一响应仍返回其他全部岗位;撤回、跨租户、非在职员工继续在查询前失败关闭。 + +- [ ] **Step 2: 运行测试并确认失败** + +Run: + +```bash +mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ + -Dtest=AihrBroadcastServiceTest,AihrBroadcastAttachmentContractTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +``` + +Expected: FAIL on missing insight queue and response mapping. + +- [ ] **Step 3: 分离文件状态和解读状态** + +附件解析成功后立即写: + +```sql +status = 'READY', +extracted_text = ?, +summary = ?, +insight_status = 'PENDING', +insights_json = NULL, +insight_version = NULL +``` + +解读任务领取时把 `PENDING` 原子更新为内部 `PROCESSING`;API 将内部 `PROCESSING` 映射为 `PENDING`。成功保存 `READY/PARTIAL` 与 JSON、版本;异常只写 `insight_status='FAILED'`,不得把附件 `status` 改回 `FAILED`。启动恢复将超过 30 分钟的内部 `PROCESSING` 重置为 `PENDING`。 + +- [ ] **Step 4: 安全映射员工详情** + +详情 SQL 只在既有租户、`PUBLISHED`、在职员工门禁通过后读取存储 JSON。服务端用当前 APP 用户手机号和 `aihr_org_snapshot` 的精确在职匹配读取 `position_name`,再计算 `defaultPerspectiveCode`;客户端不得提交岗位。返回全部已生成岗位,但绝不返回 `extracted_text` 或 `insights_json`。 + +- [ ] **Step 5: 运行测试并确认通过** + +Run the Task 3 Maven command. + +Expected: PASS. + +- [ ] **Step 6: 提交** + +```bash +git add backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentService.java \ + backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastService.java \ + backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastServiceTest.java \ + backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java +git commit -m "feat(broadcast): expose employee role insights" +``` + +### Task 4: 员工端多岗位阅读 + +**Files:** +- Modify: `mobile-uni/src/services/broadcast.ts` +- Modify: `mobile-uni/src/pages/user/broadcast/detail.vue` +- Modify: `mobile-uni/src/pages/user/broadcast/index.vue` +- Create: `mobile-uni/tests/broadcast-multi-role-insight.test.mjs` +- Modify: `mobile-uni/tests/broadcast-attachment.test.mjs` + +- [ ] **Step 1: 写失败的移动端合同测试** + +断言源码包含: + +```js +assert.match(service, /interface BroadcastPerspective/); +assert.match(detail, /多岗位解读/); +assert.match(detail, /defaultPerspectiveCode/); +assert.match(detail, /原文依据/); +assert.match(detail, /insightStatus === 'FAILED'/); +assert.doesNotMatch(detail, /与我有关/); +assert.match(index, /定向提醒/); +``` + +- [ ] **Step 2: 运行测试并确认失败** + +Run: + +```bash +npm --prefix mobile-uni run test:unit +``` + +Expected: new multi-role test FAIL. + +- [ ] **Step 3: 增加类型和默认选择** + +类型与后端一致。详情加载后: + +```ts +const perspectives = computed(() => item.value?.attachment?.perspectives || []); +const selectedCode = ref(''); + +const selectInitialPerspective = (attachment?: BroadcastAttachment) => { + selectedCode.value = attachment?.perspectives.some( + perspective => perspective.code === attachment.defaultPerspectiveCode + ) + ? attachment.defaultPerspectiveCode || '' + : attachment?.perspectives[0]?.code || ''; +}; +``` + +显示“我的岗位视角”仅当默认代码实际命中;否则显示“多岗位解读”。 + +- [ ] **Step 4: 实现移动端布局和降级状态** + +摘要保持在前。其后依次展示: + +```text +多岗位解读 / 我的岗位视角 +[生活顾问] [财务] [人力] [业务运营] … +本岗位摘要 +关注点 / 影响 / 建议行动 / 风险 +原文依据(默认折叠) +``` + +`PENDING` 显示“AI 正在从不同岗位解读”;`PARTIAL` 显示“已生成部分岗位解读”;`FAILED` 或 `NOT_PROCESSED` 只显示轻量说明,不隐藏摘要、原文件和“问这条消息”。岗位 chips 允许横向滚动,按钮具备可识别选中态和可读文本。 + +- [ ] **Step 5: 运行移动端测试、类型检查和构建** + +Run: + +```bash +npm --prefix mobile-uni run test:unit +npm --prefix mobile-uni run typecheck +npm --prefix mobile-uni run build:h5 +``` + +Expected: all PASS. + +- [ ] **Step 6: 提交** + +```bash +git add mobile-uni/src/services/broadcast.ts \ + mobile-uni/src/pages/user/broadcast/detail.vue \ + mobile-uni/src/pages/user/broadcast/index.vue \ + mobile-uni/tests/broadcast-multi-role-insight.test.mjs \ + mobile-uni/tests/broadcast-attachment.test.mjs +git commit -m "feat(mobile): show broadcast role perspectives" +``` + +### Task 5: 开发重置、发布预检与文档 + +**Files:** +- Modify: `scripts/reset-dev-db.sh` +- Modify: `scripts/release-preflight.sh` +- Modify: `scripts/demo-check.sh` +- Modify: `docs/API_INTEGRATION.md` +- Modify: `docs/BRD_IMPLEMENTATION_AUDIT.md` + +- [ ] **Step 1: 写失败的发布合同** + +将远端附件合同从 11 个业务列扩展为 14 个,并明确要求: + +```text +insight_status +insights_json +insight_version +``` + +`demo-check.sh` 要求新迁移、解读服务、移动端“多岗位解读”和“定向提醒”存在。 + +- [ ] **Step 2: 运行静态预检并确认失败** + +Run: + +```bash +bash scripts/demo-check.sh +``` + +Expected: FAIL on missing preflight/docs markers before implementation. + +- [ ] **Step 3: 接入迁移与更新文档** + +`reset-dev-db.sh` 在附件基础迁移后执行新迁移。API 文档记录: + +```text +insightStatus: NOT_PROCESSED | PENDING | READY | PARTIAL | FAILED +defaultPerspectiveCode: 服务端按认证手机号对应在职岗位计算 +perspectives[]: code/label/summary/concerns/impacts/actions/risks/evidence +``` + +同时记录:所有有原消息阅读权限的员工可切换全部生成岗位;引用必须逐字命中原文;失败不影响摘要、下载和追问;旧“与我有关”改名“定向提醒”且语义仍为通知定向。 + +- [ ] **Step 4: 运行静态预检** + +Run: + +```bash +bash scripts/demo-check.sh +git diff --check +``` + +Expected: PASS. + +- [ ] **Step 5: 提交** + +```bash +git add scripts/reset-dev-db.sh scripts/release-preflight.sh scripts/demo-check.sh \ + docs/API_INTEGRATION.md docs/BRD_IMPLEMENTATION_AUDIT.md +git commit -m "docs(broadcast): define role insight release contract" +``` + +### Task 6: 集成、视觉和真实交互验收 + +**Files:** +- Verify all modified files +- Evidence: local browser screenshots at 390 × 844 and desktop responsive view + +- [ ] **Step 1: 运行后端与移动端全量相关检查** + +Run: + +```bash +mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ + -DskipITs -Dsurefire.failIfNoSpecifiedTests=false test +npm --prefix mobile-uni run test:unit +npm --prefix mobile-uni run typecheck +npm --prefix mobile-uni run build:h5 +bash scripts/demo-check.sh +git diff --check +``` + +Expected: PASS. + +- [ ] **Step 2: 启动本地服务并造两份可识别测试文件** + +Run: + +```bash +docker compose -f docker-compose.dev.yml up -d +portless run --name wygj-api ./scripts/dev-backend.sh +portless run --name wygj-mobile-uni npm --prefix mobile-uni run dev:h5 +``` + +通过现有管理端上传并发布: + +1. 同时涉及财务、人力、业务运营的短文件; +2. 超过 4,000 字且关键内容在尾部的长文件。 + +使用数据库中实际在职手机号和开发固定验证码登录,不在日志、截图或报告中保留手机号。 + +- [ ] **Step 3: 在 390 × 844 浏览器真实验证** + +逐项操作: + +1. 打开大喇叭详情,确认摘要位于多岗位解读之前; +2. 确认默认选中当前员工岗位对应视角; +3. 横向切换财务、人力、业务运营等全部返回岗位; +4. 展开原文依据并核对引文存在于文件; +5. 打开原文件; +6. 点击“问这条消息”; +7. 验证 `PENDING/PARTIAL/FAILED` 时主链路仍可用; +8. 检查文字换行、chip 溢出、底部按钮、滚动与点击区域。 + +保存并检查截图,不以页面可加载替代视觉验收。 + +- [ ] **Step 4: 安全回归** + +用自动化或直接 HTTP 证据确认: + +```text +撤回消息 -> 404 +跨租户消息 -> 404 +非在职 APP 身份 -> 403 +员工详情 JSON -> 不含 extracted_text / insights_json +``` + +- [ ] **Step 5: 代码审查与修复** + +分别进行 Java、TypeScript 和整体代码审查;修复所有高/中优先级问题后重跑 Task 6 Step 1。 + +- [ ] **Step 6: 提交最终修复** + +```bash +git add -u +git commit -m "fix(broadcast): close multi-role insight review findings" +``` + +- [ ] **Step 7: 交付分支** + +确认工作区干净,报告 `implemented / locally verified / visually verified`;生产部署与 `production-verified` 只有在执行正式迁移、静态发布、后端发布、完整 `release-preflight` 和线上真实账号验证后才能声明。