pipeline-bidding/README.md

154 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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-bidding — 投标产线模块
## 模块定位
产线平台(pipeline-app / pipeline-core)的第二条产线能力包:**投标产线 `bidding_general`**。
验证并落地「产线能力包住自己仓库、宿主不装则零接触」的分层原则——能力模块全部住在本仓库,
不进 `pipeline-service`;宿主只要不 `import pipeline_bidding`,一切都不会发生。
模块 = 数据(`bid_*` 表 + CRUD)+ 能力(`bid_*_capability`)+ 流转(`bid_flow` 状态对账器)
+ 主 agent 能力包(`bid_ability`)+ 角色技能(部署期随 pipeline-core skills_library 分发)。
**产线起点 = 上传招标文件**(`import_tender_file` 无 project_id 时自动立项)。阶段链:
> 收到招标文件(自动立项)→ 解析(评分项/资质/投标文件要求/商务要素/章节骨架)
> → QC 契合度审核(四类逐类,10 分制,>9.5 放行)→ 资料准备 → 分角色并发编写
> (business→bid_biz_writer / technical→bid_writer)→ 章节评审(含篇幅门禁)→ 合成
> → 整书评分 → 交付确认。
核心机制:
- **角色任务流转不用 RoleSpec.next_role**(全留空):投标是「一章一任务 + 两级回退循环」
扇出结构,由 `bid_flow` 状态对账器(每 15s 一轮)补派,对账器是角色任务的**唯一创建者**。
- **阻塞门禁在创建源头**(对账器判 `pipeline_human_tasks.project_id + pending`),
不写进引擎 poller SQL。`tenant_id=project_id` 只是角色任务约定,禁止写进引擎层。
- **两级回退循环**:章节级(得分率 < `bid_chapter_pass_ratio` 或篇幅不达标 → rejected+意见 →
自动重写,超 `bid_max_revise` 抛人工);整书级(< `bid_pass_ratio` 或否决项未响应 →
按 `target_chapter_id` 打回 → 重写→重审→重合成→重评分,超 `bid_max_round` 抛人工)。
- **QC 门禁**:四类解析产出逐类审核落 `bid_qc_reviews`;不通过 → 清空该类产出 → 对账器重派
解析(`params.qc_redo=1` + 改进意见)→ 重审;轮次超 `bid_qc_max_round` → `qc_escalation`
阻塞人工(逃逸阀:人工修产出后在记录页把 passed 改 1 即生效)。
- **门限参数全部读 appbase `params` 表 + DEFAULT_PARAMS 兜底**,不硬编码:
`bid_chapter_pass_ratio`(0.8) / `bid_pass_ratio`(0.85) / `bid_max_revise`(3) /
`bid_max_round`(3) / `bid_qc_pass_score`(9.5) / `bid_qc_max_round`(3) /
`bid_chapter_min_words`(800) / `bid_chapter_min_words_biz`(500) / `bid_write_concurrency`(4)。
## 表清单(models/*.json,建表产物 mysql.ddl.sql)
| 表名 | 说明 | 关键字段 |
|---|---|---|
| `bid_tenders` | 招标商务要素 | project_id, source, title, purchaser, tender_no, industry |
| `bid_tender_files` | 招标文件 | project_id, tender_id, file_name, file_path, file_type, content_text |
| `bid_scoring_items` | 评分项与得分规则 | project_id, section, item_no, item_name, max_score, scoring_rule, is_veto |
| `bid_qualifications` | 招标所需资质 | project_id, qual_name, requirement, is_mandatory, owner, kb_doc_id, match_status |
| `bid_doc_requirements` | 投标文件要求 | project_id, req_type, chapter_no, chapter_title, requirement, page_limit, source_ref |
| `bid_chapters` | 投标文件章节 | project_id, chapter_no, parent_no, title, section, outline, status, content, word_count, review_score |
| `bid_reviews` | 标书评审记录 | project_id, scope, chapter_id, document_id, round, reviewer, review_type |
| `bid_scores` | 标书评分明细 | project_id, review_id, scoring_item_id, item_name, score, max_score, reason |
| `bid_documents` | 合成标书 | project_id, version, doc_name, file_path, page_count, chapter_count, total_score |
| `bid_qc_reviews` | 解析产出 QC 审核记录 | project_id, qc_type, round, reviewer, review_type, fit_score, pass_score |
| `bid_kb_docs` | 公司投标知识库 | org_id, doc_type(sample_bid 等), doc_name, doc_no, tags, file_path, content_text |
| `bid_members` | 投标项目参与人员 | project_id, user_id, user_name, member_role, duty, status |
种子数据(`init/data.json`,build.sh 幂等写入):`pipelines` 产线主记录 `bidding_general` +
`appcodes` 12 组码表(bid_qc_type / bid_tender_source / bid_member_role / bid_file_type /
bid_scoring_section / bid_qual_match_status / bid_qual_owner / bid_req_type /
bid_chapter_status / bid_review_scope / bid_kb_doc_type / bid_doc_status)。
## 对外 API / dspy 端点(wwwroot/)
部署后挂载在宿主 `wwwroot/pipeline-bidding/`(软链):
- **api/bid_probe.dspy** — 服务内探针(诊断)。`?action=status` 返回模块装载状态 +
poller 心跳 + 活跃投标项目数;`?action=reconcile` 在服务进程内跑一轮全量对账返回动作明细。
- **api/bid_task_tree.dspy** — 任务树懒加载(根 → role:阶段 → 任务 → step:/del: 叶子)。
项目解析三级兜底:session_settings(default_bidding_general) → agent_settings →
最近 in_progress 项目。
- **api/bid_task_io.dspy** — 任务树右侧详情面板:输入 = 任务描述 + QC 改进意见,
执行步骤,输出 = 交付件(复用 `pipeline_service/task_io_render.py` 统一渲染规范)。
- **bid_task_tree_popup.ui** — ResourceBrowser 复合 widget 弹窗(左树右详情联动)。
- **agent/index.ui** — 驾驶舱入口页(顶栏含任务树按钮)。
- **12 个 CRUD 目录**(bid_tenders / bid_tender_files / bid_scoring_items /
bid_qualifications / bid_doc_requirements / bid_chapters / bid_reviews / bid_scores /
bid_documents / bid_qc_reviews / bid_kb_docs / bid_members)— 每个含
index.ui + get/add/update/delete_*.dspy 标准五件套。
⚠️ RBAC 铁律:`/api/` 前缀不覆盖具体 dspy,**新 API 必须逐条显式注册**(scripts/load_path.py),
否则 403;注册后 redis db0 FLUSHDB 即生效。
## load 注册函数
宿主入口(pipeline_app.py)两步,缺一不可:
```python
from pipeline_bidding.init import load_pipeline_bidding
load_pipeline_bidding() # import 后必须再显式调用,漏了无任何日志(静默大坑)
```
`load_pipeline_bidding()`(幂等)做三件事:
1. `role_tool_schemas.register_role_tools()` — 把约 55 个投标角色工具 schema
(read_tender_file / extract_* / add_* / patch_chapters / write_chapter /
review_chapter / start_qc / finish_qc / compose_bid / start_bid_score /
finalize_bid_score / dispatch_analysis_dim 等)注册进引擎
`pipeline_service.capability_tools.TOOL_SCHEMAS`(引擎 `exec_capability_tool` 的
`module` 支持全路径,裸名仍走 `pipeline_service.*`,SDLC 零变化)。
2. `import bid_ability` — 主 agent 能力包 + slash 命令注册(import 即注册)。
3. `bid_flow.start_poller()` — 状态对账器启动(`PIPELINE_MODE=web` 时不注册,
与引擎 poller 同款模式开关)。
角色工具装配链:角色技能 frontmatter `capability+tools` → `_capability_to_tools_map`
→ `TOOL_SCHEMAS` → **两者交集才是可见工具**(技能声明了但 schema 没注册 = 工具静默缺失)。
独立脚本验证必须先显式调 `load_pipeline_bidding()`,仅 import 包不触发注册。
码表导入自持:`scripts/import_init_bidding.py`(幂等 + 废弃清理 + 产线描述),
不依赖宿主 `import_init.py` 的 INIT_MODULES。
## 宿主集成(部署)
在宿主应用(pipeline-app)根目录或本目录执行一键脚本:
```bash
./build.sh
```
build.sh 职责(运行期禁止 schema 变更,建表只在部署期):
1. **建表**:读 `mysql.ddl.sql`(json2ddl 产物),把 `drop table if exists` 剔除、
`CREATE TABLE` 改为 `CREATE TABLE IF NOT EXISTS` 幂等化,用宿主 config 的
`pipeline` 库连接执行。
2. **种子数据**:`init/data.json` 幂等写入 pipelines 产线记录 + appcodes/appcodes_kv 码表。
3. **wwwroot 软链**:`ln -sf 本仓库/wwwroot → $APP_ROOT/wwwroot/pipeline-bidding`。
4. **pip install**:`$APP_ROOT/py3/bin/pip install 本目录`(非 editable 安装)。
宿主负责的后续:app 入口 import + 调用 `load_pipeline_bidding()`;
RBAC 用 `scripts/load_path.py`(其中 set_role_perm.py 必须用宿主根**绝对路径**);
i18n 合并用 merge_i18n.py。
## 部署注意(实测踩坑)
1. **非 editable 安装**:测试机只 `git pull` 仓库不会更新 site-packages,改码后必须
`./py3/bin/pip install pkgs/pipeline-bidding` 再重启(可对比 site-packages 与仓库文件
md5 确认加载版本)。
2. **运行时技能树独立仓库**:在 `/d/pipeline/pipeline-app/skills/`(git:
yumoqing/pipeline-sdlc.git),不是 pipeline_core 的 skills_library;改了 bid-workflow
等技能必须同步 SKILL.md 到 `pipelines/bidding_general/common/<技能名>/` 再重启。
3. **宿主入口漏调用 `load_pipeline_bidding()` 是静默大坑**:无任何日志。诊断走
`api/bid_probe.dspy?action=status` 心跳探针。
4. **RBAC 缓存**:新路径注册后 redis db0 FLUSHDB 即生效(旧说须重启已过时);
精确 path 匹配,父路径不覆盖子路径。
5. **角色任务 failed 必须是对账器阻塞态**:否则每 15s 重复造任务(机构未配 LLM 时必现);
测试环境无 LLM 模型时任务 failed 属预期。
6. **自动立项的项目不继承模型**:不带 default_model,解析链兜底全局默认名可能不存在 → 401;
建后必查/补 `sd_projects.default_model`。
7. **bid_chapters 键归一**:`resolve_project_id` 先按 id 校验、按 name 反查归一,
名字歧义拒绝(防「项目名当 project_id」写库产生孤儿骨架)。
8. 对账器结构铁律:维度过滤(等上游 QC 的不算掉链)必须在 `_open_orch_tasks` 判定前
无条件执行,编排任务创建/掉链计数留在「无编排任务在办」的 else 分支内,
否则每 15s 重复建编排任务堆积。
9. 共享工具 `create_task` 有产线角色校验守卫(get_role_spec 查不到即拒绝),
`list_roles` 查看本产线可用角色。
详细设计见 `docs/DESIGN.md`;技能库见 pipeline-core
`skills_library/pipelines/bidding_general/`(common 7 技能 + roles 9 角色含 bid_biz_writer)。