yumoqing 4092304726 feat(feasibility): P3可行性研究——opp_feasibility引擎(fp_estimate规则引擎FP/draft_feasibility八节报告/score_candidate评分/promote_to_project立项门禁)+report_type列(models/DDL/CRUD重生成)+详情页与PPT按类型选四节或八节模板(sections_for_type单一规则源)+列表类型徽标+prompt链路C+params/demand_mining字典种子+i18n词条
- fp_calc.calc()返回键实锤核对: total_fp/counts/errors(原total_ufp/by_type兜底猜错,已修)
- promote门禁修正: resolve_approval会把status置approved,confirmed只是中间态→门禁接受confirmed/approval_initiated/approved(原只认confirmed会导致审批通过后反而不能立项)
- _cluster_ctx补项目隔离(对齐_check_batch_visible口径)+全personal类别如实拒绝可行性样本不足
- 八节模板兜底节=需求规格;四节存量解析口径零变化
- opp_approvals CRUD手改(项目过滤)被xls2ui重生成吃掉→已checkout还原,只保留opp_reports新列部分
2026-09-14 09:03:58 +08:00

197 lines
13 KiB
Markdown
Raw 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.

# pipeline-opportunity — 商机产线能力包
把招标市场情报转化为软件研发商机:**热点软件推荐 → 每日AI/Agent招标 → 研发报告编写 → 人工确认 → 研发审批流程**。
## 平台边界(重要)
- **数据爬取平台**(ebpe.cc:/d/ymq/work/tender_opp,独立部署):采集中国招标网站数据、
持久存储、去重,仅通过内网 HTTP 只读接口对外(`192.168.16.2:9085`,X-API-Token 鉴权)。
- **本模块(产线平台侧)**:商机业务闭环。只消费爬虫数据,不爬取、不存招标原始数据;
报告/审批状态落产线库 `opp_reports` / `opp_approvals` 表,绝不回写爬虫库。
依赖方向:产线平台 ──HTTP──> 数据爬取平台(单向)。
## 产线闭环与门禁
```
opp_hot_software / opp_daily_ai_tenders(爬虫平台数据)
↓ 选定方向
opp_create_report(草稿)→ opp_update_report(补写,数据必须来自工具返回)
↓
opp_submit_report ──> 人工确认任务(门禁,pipeline_human_tasks: opp_report_confirm)
↓ 人工确认通过(confirm 回流)
initiate_approval ──> 研发审批任务(门禁,opp_dev_approval)
↓ 人工审批结论回流
approved / rejected(rejected 可退回 draft 重来)
```
状态机:`draft → confirmed → approval_initiated → approved/rejected`(非法迁移一律拒绝)。
agent 禁止自批自审:确认与审批结论只能由人工任务回流驱动。
## 工具清单(主 agent / 驾驶舱)
| 工具 | 用途 |
|------|------|
| opp_hot_software | 热点软件推荐(招标量+预算+地区排名) |
| opp_daily_ai_tenders | 每日 AI/Agent 招标展示数据 |
| opp_search_tenders | 关键词检索招标信息 |
| opp_tender_detail | 单条详情(含原文/预算/联系人/来源链接) |
| opp_crawler_stats | 爬虫平台采集健康度 |
| opp_create_report / opp_update_report | 报告草稿创建与补写 |
| opp_list_reports / opp_get_report | 报告列表与全文 |
| opp_submit_report | 提交人工确认(门禁) |
| opp_list_approvals | 审批单列表 |
| opp_diagnose | 产线诊断(报告分布/待办/爬虫连通性) |
| opp_start_mining | 启动需求挖掘批次(后台:拉取→向量化→聚类→命名;scope=top 全量TopX / targeted 指定类型) |
| opp_mining_status | 批次状态轮询(pulling/embedding/clustering/naming/done/failed+原因) |
| opp_list_clusters | 类别排名表(热度/需求数/占比/命名证据)= TopX 热门需求类别 |
| opp_cluster_detail | 类内需求明细样例(每条带来源 URL 可查证) |
角色:`agent.opp_analyst`(商机分析师)、`agent.opp_writer`(研发报告撰写工程师)。
slash 命令:`/hot` `/ai` `/reports` `/oppdiag`。
菜单:📊 研发报告 / ✅ 研发审批 / ⛏ 需求挖掘(批次→类别→明细三级下钻,2026-09-12)/ 📋 数据参考。
## 数据参考弹窗(三级下钻,2026-09-08 交互定稿)
菜单「📋 数据参考」→ `opp_references_popup.dspy`,三级结构全部**整行点击**(cursor:pointer,
无「查看分项」按钮):
1. **汇总层**(主题排名:招标/国内众包/海外三板块):点主题条目 → 弹出该主题分项列表
(`opp_references_items` / `opp_demand_items` / `opp_overseas_items.dspy`)。
2. **分项层**(明细条目):每条说明文字标注「来源:xxx」(`source_text()` 解析,
来源按钮已废弃删除);点条目 → 弹出具体内容(`opp_item_detail.dspy`:
爬虫平台 `/api/tenders/{id}` 全量字段 + 公告/需求全文 + 原文页/采集来源页按钮 + 附件列表)。
3. **附件层**:详情弹窗列出条目附件(爬虫平台 `/api/tenders/{id}/attachments`,
字段容错归一 `normalize_attachment`),「下载」按钮走服务端代理
`opp_attachment_dl.dspy`——先回查条目附件清单做归属校验(防 SSRF,前端传任意
att_url 不在清单内即拒绝),再按降级链取字节:记录 download_url →
`/api/attachments/{id}/download` → 附件原始 url;代理失败如实报错不静默。
附件端点未就绪(爬虫平台附件子系统部署中)时:分项列表不显示 📎 角标、详情弹窗
显示「附件服务暂不可用」,其余功能不受影响。
## 研发报告列表/详情/成果文件下载(2026-09-09 用户需求改造)
菜单「📊 研发报告」由原 CRUD 裸表页(`opp_reports/index.ui`,无过滤+可增删改)
改为只读列表页 `api/opp_reports_list.dspy`,三点改造:
1. **按项目 owner 过滤**(`list_visible_reports` 单一事实源):
- 挂项目的报告:当前用户是项目 owner(`sd_projects.created_by==uid`)才可见;
agent 创建的项目(`created_by LIKE 'agent.%'`,模式经 `${kw}$` 参数化——
SQL 字面量裸 `%` 会被驱动当格式化符炸 ValueError,实测踩过)降级为同机构成员可见
(对齐 `check_project_owner` 既有降级语义,不重复发明)
- 未挂项目的报告(平台级调研,存量数据大多是这类):登录用户可见
- 项目已删的孤儿报告:owner 无法验证 → 不可见(宁缺勿漏,LEFT JOIN 自然排除)
- 详情/下载端点复用 `check_report_visible`(与列表同一规则,语义不漂移)
2. **详情四节展示**(`opp_report_detail.dspy`,整行点击下钻,范式同 `opp_references_items`):
报告正文按四节模板解析(`parse_report_sections`)——`## 研发场景` / `## 输入` /
`## 输出` / `## 成果`,逐节 MdWidget 渲染;兼容带序号/冒号/加粗的标题变体,
三级标题不误判,重复同名节拼接不丢内容。**旧报告(无四节标题)全文归「输出」节**,
空节如实显示「本报告未填写此节」不编造。agent 写报告的四节模板硬要求已写进
OPP_PROMPT + opp_create/update_report 工具描述 + opp-workflow 技能。
3. **成果文件多个列出、逐个下载**(`list_report_files` + `opp_report_file_dl.dspy`):
文件范围 = 项目工作空间 `deliverables/` 全部文件 + 报告 PPT(`ppt_path`,
按 realpath 去重)。下载安全模型对齐 `opp_attachment_dl`:前端只传
`report_id + name`,服务端**回查清单命中才给文件**,文件名绝不参与路径拼接
(清单只来自目录列举+ppt_path,`../` 穿越名在清单里不可能出现)。
CRUD 页(`opp_reports/` `opp_approvals/`)保留供后台运维直接编辑,菜单入口不再暴露。
## 爬虫平台接入配置
系统级配置禁硬编码,走 appbase `params` 表(兜底默认值内网地址):
| params 键 | 值 |
|-----------|-----|
| `tender_api_base` | `http://192.168.16.2:9085`(默认) |
| `tender_api_token` | 爬虫平台 `state/api.env` 中的 TENDER_API_TOKEN |
## 部署(宿主 = pipeline-app)
1. `git clone` 到 `<APP_ROOT>/pkgs/pipeline-opportunity`
2. `bash build.sh`(建表 + 种子数据 + wwwroot 软链 + pip install)
3. app 入口已接线:`load_pipeline_opportunity()`(try/except 兜底)
4. RBAC:`cd <APP_ROOT> && py3/bin/python pkgs/pipeline-opportunity/scripts/load_path.py`
5. i18n:合并 `i18n/{zh,en}/msg.txt` 到宿主 wwwroot(merge_i18n.py),调 /i18n_getmsgs 刷新
6. 重启应用;浏览器验证驾驶舱 `/pipeline-opportunity/agent`
## 表
- `opp_reports`:研发报告(software/title/content/status/confirm_task_id/confirmed_by)
- `opp_approvals`:研发审批(report_id/status/note/resolved_by)
- `opp_mining_batches`:需求挖掘批次(org_id+project_id 隔离,project_id 空=平台级挖掘;
scope=top/targeted;状态机 pulling→embedding→clustering→naming→done/failed;
stats_json 进度统计;error_msg 可行动报错)
- `opp_demand_snap`:需求快照(batch_id+src_id 去重;cluster_id 聚类回填;embed_status)
- `opp_clusters`:需求类别(heat_rank 热度排名/share 占比/naming_evidence 命名证据/centroid_snap_id)
挖掘结果的项目可见性口径与 opp_reports 对齐(2026-09-12 用户确认挖掘也分项目):
挂项目的批次仅本项目会话可见;平台级批次(无项目会话发起)本机构登录可见;
无当前项目的会话只见平台级批次。`_check_batch_visible` 是单一规则源
(mining_status/list_clusters/cluster_detail 三层复用,E2E 11 项断言实测全过)。
## 需求挖掘架构(P1,2026-09-11)
众包需求(爬虫平台 record_type=demand)→ 语义聚类 → TopX/指定类型分析:
- **embedding 走模型调用**:rag `/rag/api/embed.dspy`(凭据单点 rag_engine_configs)
← pipeline_service.rag_client.rag_embed_texts(project_id)(Bearer,10条/批)。
- **向量与近邻压给 VDB(Milvus)**(upapp.rag-vdb):kNN=/v1/query+vector+pagerows;
标量过滤=expr;协议细节见技能 rag-module-operations。
- **两层 collection**:共享缓存 `opp_demand_emb_cache`(基础数据全机构共用,
一条需求只嵌一次)+ 批次工作集 `opp_mine_<batch16>`(分析结果按批次隔离,
每机构保留最近 opp_mine_keep_batches 个,旧批自动 drop)。
- **聚类算法**:kNN(top30) + τ阈值(默认0.75) + 纯 Python 并查集(零原生依赖,
确定性可复现);>150 条大簇质心二次细分(τ+0.10,防众包模板标题过度聚合——
2075条实测 τ=0.75 拐点:42簇 top1=8.2%);小组(<5)质心并入最近大簇或归其他。
- **LLM 只做簇命名**(llm_call purpose=utility 60s 短超时),失败规则兜底(高频词)。
- **机构隔离**(用户定夺):基础数据共享、分析结果隔离——批次/快照/类别挂 org_id,
查询强制过滤(E2E 伪 org 拒绝实测通过)。
- 算法参数全走 appbase params 表:opp_mine_tau/topk/min_cluster/big_split/keep_batches、
opp_embed_batch、opp_emb_cache_col(代码默认兜底)。
- 验证脚本:scripts/p0_probe_vdb_embed*.py(VDB/embed 协议探测)、
p1_cluster_analyze.py(多τ对比+缓存,调参用)、p1_create_tables.py(幂等建表)。
## 可行性研究(P3,2026-09-14)
挖掘类别 → 可行性研究 → 评分 → 立项(report_type=feasibility 八节报告):
- **opp_feasibility.py**(引擎):
- `fp_estimate`:功能点估算。LLM 只提供结构化功能清单
(name/type=EI|EO|EQ|ILF|EIF/det/ret/ftr/evidence/confidence),
复杂度判定与 FP 加权**全部走规则引擎 fp_calc.py**
(pipeline-core `skills_library/all/function-point-counting/`,部署位
`skills/global/`,进程内 importlib 加载;加载失败如实报错,绝不 LLM 心算 FP)。
返回键对齐 fp_calc.calc() 实际输出:total_fp/counts(→by_type)/errors/pending。
- `draft_feasibility(cluster_id)`:类别须已完成共性提取(normalize done)且有
core/ext 标准需求(全 personal 时如实拒绝"可行性样本不足")。FP 输入=每条
核心/扩展标准需求映射 1 ILF+1 EI+1 EQ(DET 按证据原子数粗估);成本=FP×
opp_fp_pm_per_fp(默认0.05人月/FP)×opp_cost_wan_per_pm(默认3.0万元/人月),
params 可配。LLM 只写三节叙述(架构与可行性/商业价值/风险),其余五节用
挖掘真实数据拼装。落库 opp_reports(report_type=feasibility)。
- `score_candidate(cluster_id)`:规则评分=需求热度40%(log 规模防大簇垄断)+
共性集中度35%(core+ext 覆盖文档占比)+规模适配25%(标准需求 8-25 个最佳)。
- `promote_to_project(report_id)`:立项门禁——仅 report_type=feasibility 且
过人工确认(status∈confirmed/approval_initiated/approved)且审批单 approved
才可建 sd_projects(pipeline=opportunity_general,project_type=demand_mining,
字典种子在 init/data.json)。agent 不能自批自审。
- 项目隔离:`_cluster_ctx` 对齐 `_check_batch_visible` 口径(批次挂项目的仅
同项目会话可见,平台级批次本机构可见)。
- **八节模板**(report_type=feasibility):`## 市场需求 / ## 共性需求 / ## 需求规格 /
## 架构与可行性 / ## FP与成本 / ## 商业价值 / ## 风险 / ## 结论建议`。
`sections_for_type(report_type)` 是单一规则源:详情页(opp_report_detail.dspy)
与 PPT(opp_ppt.py FEASIBILITY_ORDER)按类型选四节/八节渲染;
research 报告四节模板不变,存量报告解析口径零变化。
- **opp_reports.report_type 列**(VARCHAR(16) DEFAULT 'research'):
测试机/生产走 pipeline-app `deploy/migrations/m0030_opp_reports_report_type.json`
(dmig apply,幂等 ADD COLUMN);models/opp_reports.json 已同步(新建表天然带列)。
- **报告列表标签**:opp_reports_list.dspy 行内加类型徽标(调研=蓝/可行性研究=紫)。
- PPT 文件名区分:feasibility_report_<software>_<rid8>.pptx。
- 4 个新工具注册进 OPP_HANDLERS(opp_fp_estimate/opp_draft_feasibility/
opp_score_candidate/opp_promote_to_project),OPP_PROMPT 加「可行性研究与立项」
流程段;opp-workflow 技能同步(链路C)。
## 技能
产线技能在 pipeline-core 技能库:`skills_library/pipelines/opportunity_general/`
(common/opp-workflow 总纲 + roles/agent.opp_analyst、agent.opp_writer 角色技能)。