# 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)→ 章节评审(含篇幅门禁)→ 合成 > → 整书评分 → 交付确认。 **多流程模板(2026-09-15 批2)**:本产线声明两条流程(`BID_FLOW_TEMPLATES`,引擎 FlowTemplate 机制首个接入)——`bid_standard`(上述标准链)与 `bid_tech_proposal` (**纯技术方案流程**:输入=技术需求书而非招标文件,产出=技术方案书): > 技术需求书 → 六类技术评估项解析(功能清单/功能点/硬件配置/技术架构/功能架构/部署架构, > 落 `bid_tech_items`)→ QC 对标需求书(qc_type=tech_items)→ 模版三级检索(知识库→RAG→网络) > + 骨架拟定(六类覆盖度硬校验,漏类拒绝)+ **用户确认待办**(tech_template_confirm,全局阻塞, > agent 无确认权)→ 分章编写 → 章节评审(对标需求书覆盖度)→ 合成 → 交付确认。 首文件导入后对账器 LLM 自动判流(`detect_flow_template`,置信度+判据摘录进确认待办); 判流失败发人工待办不默认硬跑。已确认计划 `base_flow_key=bid_tech_proposal` 的项目走 `bid_flow._reconcile_tech_proposal` 独立对账分支,不碰评分项/资质/成本收益。 规范见技能 `bid-tech-proposal`;确认端点 `api/tech_template_confirm.dspy`。 核心机制: - **角色任务流转不用 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)。 - **章节配图形态硬门禁(2026-09-15 用户要求)**:技术方案/标书中的图必须 invoke_model 调 t2i/i2i 生成真实图片(`![图N 标题](URL)` 嵌入,合成 docx 自动嵌真图),禁 mermaid/plantuml/ ASCII 字符画。`write_chapter` 机制层确定性检测拒写伪图,`review_chapter` 评审兜底同款检测必退 (存量正文/绕行写入);检测器复用 `pipeline_service.diagram_gate`(ImportError 逃逸阀); 豁免钥匙=文中如实标注「配图缺失:平台无可用文生图模型」(仅 t2i/i2i 全无时)。 ## 表清单(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_analysis_history` | QC 分析历史快照(每轮 QC 逐审核记录归档;2026-09-10 归籍补 models 定义,此前是孤儿表) | project_id, qc_type, round, task_id, fit_score, improvement, payload | | `bid_cost_benefit` | 成本收益分析(分析维度4,单项+summary 结论行;m0006 建表未进 models,2026-09-10 归籍) | project_id, kind, category, item_name, amount, confidence | | `bid_tech_items` | 技术评估项(纯技术方案流程 tech_analysis 产出;kind=item/summary,category 六类;m0034 建表) | project_id, kind, category, item_name, requirement, mandatory, source_ref | | `bid_kb_docs` | 公司投标知识库 | org_id, doc_type(sample_bid 等), doc_name, doc_no, tags, file_path, content_text | | `bid_material_gaps` | 投标材料缺口清单(占位符规范:正文 `【材料待补:…】` 自动登记,合成后出清单,人工/产线补齐;m0035 建表) | project_id, chapter_no, material_name, placeholder_text, fingerprint, status(pending/filled/waived) | | `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` 统一渲染规范)。 - **api/tech_template_confirm.dspy** — 技术方案骨架用户确认/驳回端点(2026-09-15)。 入参 human_task_id/decision(confirm|reject)/comment(驳回必填)。用户专属:同机构+ 超管/创建者校验复用引擎 `_check_confirm_operator`;agent 无对应工具无法代确认。 确认后骨架落 bid_chapters(section=technical) 开写;驳回意见由对账器注入重派模版任务。 - **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()` — 把约 60 个投标角色工具 schema (read_tender_file / extract_* / add_* / list_cost_benefit / update_cost_benefit_item / delete_cost_benefit_item / patch_chapters / write_chapter(支持 mode=append 分批追加超长章节)/ review_chapter(支持 material_missing=1 实体材料缺失如实披露放行)/ 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)。