Files
prop-ai-hr/docs/superpowers/plans/2026-07-25-broadcast-multi-role-document-insights.md

523 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 大喇叭公司文件多岗位解读 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<String> concerns,
List<String> impacts,
List<String> actions,
List<String> risks,
List<BroadcastEvidence> evidence
) {}
```
附件响应追加:
```java
String insightStatus,
String defaultPerspectiveCode,
List<String> perspectiveLabels,
List<BroadcastPerspective> 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<String, String> 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` 和线上真实账号验证后才能声明。