Files
bid-doctor/docs/architecture/openapi.yaml
YANG JIANKUAN be62440531 docs: 工程骨架落地记录(架构 §13)与 AGENTS.md 工程一节
- 架构 §13:SK1–SK8,SK1(事件流用 Valkey stream)、SK2(测试不用 testcontainers)待确认
- api-design §10:接口实现进度
- openapi.yaml:ChecklistItem 的必填字段在同一层声明,生成的类型不再是可选
- AGENTS.md:目录结构、常用命令、编码约定、当前进度

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:48:10 +08:00

2911 lines
105 KiB
YAML
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.

openapi: 3.1.0
info:
title: 标博士用户端接口
version: 0.1.0
summary: MVP 用户端(网页版 / 桌面版)的核心接口契约草案
description: |
约定见 `docs/architecture/api-design.md`;数据模型见 `data-model.md`。
这是设计阶段的契约草案。后端骨架搭好后,以 FastAPI 生成的 OpenAPI 快照为准(api-design §9 API1),本文件不再维护。
不含运营后台接口(独立应用,另行定义)。
servers:
- url: /api/v1
description: 网页版同源;桌面版由用户选择服务器地址(架构 §5.2)
security:
- cookieSession: []
- bearerToken: []
tags:
- name: auth
description: 登录、注册、会话(identity)
- name: me
description: 当前用户与企业
- name: billing
description: 套餐、额度、开通申请
- name: projects
description: 项目、基本信息字段、分包
- name: uploads
description: 分片上传
- name: documents
description: 文档、页、句子、出处、解析任务
- name: checklist
description: 项目检查清单
- name: checks
description: 检查批次、问题、人工核对
- name: reports
description: 检查报告
- name: qualifications
description: 最小资质库
- name: notifications
description: 站内通知、事件流、反馈
paths:
# ------------------------------------------------------------------ auth
/auth/sms-codes:
post:
tags: [auth]
operationId: sendSmsCode
summary: 发送短信验证码
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [phone, purpose]
properties:
phone: { $ref: '#/components/schemas/Phone' }
purpose: { type: string, enum: [login, register, close_tenant] }
responses:
'202':
description: 已发送(号码是否注册都返回 202,不泄露注册状态)
content:
application/json:
schema:
type: object
required: [resend_after_s]
properties:
resend_after_s: { type: integer, description: 多少秒后可以重发 }
'422': { $ref: '#/components/responses/ValidationFailed' }
'429': { $ref: '#/components/responses/RateLimited' }
/auth/register:
post:
tags: [auth]
operationId: register
summary: 手机号注册(同时创建企业主体和免费版订阅)
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/RegisterRequest' }
responses:
'201':
description: 注册成功并登录。网页版设置会话 cookie;桌面版在响应体里拿到令牌(只返回这一次)
headers:
Set-Cookie: { $ref: '#/components/headers/SetSessionCookie' }
content:
application/json:
schema: { $ref: '#/components/schemas/LoginResult' }
'409':
description: '`phone_already_registered`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'422': { $ref: '#/components/responses/ValidationFailed' }
/auth/login:
post:
tags: [auth]
operationId: login
summary: 手机号 + 验证码登录
description: |
同一账号已有其他设备在线时返回 409 `concurrent_session`,附在线设备列表;
用户确认后带 `force: true` 重发,其他会话被挤下线(API3)。
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LoginRequest' }
responses:
'200':
description: 登录成功
headers:
Set-Cookie: { $ref: '#/components/headers/SetSessionCookie' }
content:
application/json:
schema: { $ref: '#/components/schemas/LoginResult' }
'404':
description: '`phone_not_registered`(验证码校验通过后才会返回)'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'409':
description: '`concurrent_session`'
content:
application/problem+json:
schema:
allOf:
- $ref: '#/components/schemas/Problem'
- type: object
properties:
sessions:
type: array
items: { $ref: '#/components/schemas/SessionInfo' }
'422': { $ref: '#/components/responses/ValidationFailed' }
'429': { $ref: '#/components/responses/RateLimited' }
/auth/logout:
post:
tags: [auth]
operationId: logout
summary: 退出登录(吊销当前会话)
responses:
'204': { description: 已退出 }
'401': { $ref: '#/components/responses/Unauthenticated' }
# ------------------------------------------------------------------ me
/me:
get:
tags: [me]
operationId: getMe
summary: 当前用户、企业、套餐、权限点
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Me' }
'401': { $ref: '#/components/responses/Unauthenticated' }
patch:
tags: [me]
operationId: updateMe
summary: 修改显示名
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
display_name: { type: string, minLength: 1, maxLength: 40 }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Me' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/me/sessions:
get:
tags: [me]
operationId: listMySessions
summary: 我的在线会话
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/SessionInfo' }
/me/sessions/{session_id}:
delete:
tags: [me]
operationId: revokeMySession
summary: 让某个设备下线
parameters:
- $ref: '#/components/parameters/SessionId'
responses:
'204': { description: 已吊销 }
'404': { $ref: '#/components/responses/NotFound' }
/tenant/close:
post:
tags: [me]
operationId: closeTenant
summary: 注销账号(删除企业下全部业务数据,不可恢复)
description: data-model §6.2。需要短信验证码和企业名称两次确认。成功后当前会话失效。
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [sms_code, confirm_tenant_name]
properties:
sms_code: { type: string, pattern: '^[0-9]{6}$' }
confirm_tenant_name: { type: string }
responses:
'202': { description: 已受理,后台删除文件 }
'422': { $ref: '#/components/responses/ValidationFailed' }
# ------------------------------------------------------------------ billing
/plans:
get:
tags: [billing]
operationId: listPlans
summary: 套餐列表(含额度、文件大小和页数上限,购买页和上传页展示用)
security: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Plan' }
/quota:
get:
tags: [billing]
operationId: getQuota
summary: 额度总览:本期已用、剩余、按功能分布
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/QuotaOverview' }
/quota/entries:
get:
tags: [billing]
operationId: listQuotaEntries
summary: 额度流水
parameters:
- name: unit
in: query
schema: { $ref: '#/components/schemas/QuotaUnit' }
- name: project_id
in: query
schema: { $ref: '#/components/schemas/Id' }
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: 按时间倒序
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Page'
- type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/QuotaEntry' }
/activation-requests:
get:
tags: [billing]
operationId: listActivationRequests
summary: 我的专业版开通申请
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ActivationRequest' }
post:
tags: [billing]
operationId: createActivationRequest
summary: 提交专业版开通申请(之后对公转账,运营确认到账后开通,G8)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ActivationRequestCreate' }
responses:
'201':
description: 已提交
content:
application/json:
schema: { $ref: '#/components/schemas/ActivationRequest' }
'409':
description: '`activation_pending`:已有一份待处理的申请'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'422': { $ref: '#/components/responses/ValidationFailed' }
# ------------------------------------------------------------------ projects
/projects:
get:
tags: [projects]
operationId: listProjects
summary: 工作台项目列表(FR-M6-01)
parameters:
- name: status
in: query
style: form
explode: false
schema:
type: array
items: { $ref: '#/components/schemas/ProjectStatus' }
- name: deadline_from
in: query
schema: { type: string, format: date-time }
- name: deadline_to
in: query
schema: { type: string, format: date-time }
- name: q
in: query
description: 按项目名称、编号、采购人搜索
schema: { type: string, maxLength: 100 }
- name: sort
in: query
schema: { type: string, enum: [deadline, -deadline, updated, -updated], default: deadline }
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Page'
- type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ProjectListItem' }
post:
tags: [projects]
operationId: createProject
summary: 新建项目
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
maxLength: 200
description: 选填;不填时为「未命名项目」,解析后按招标文件自动填写
responses:
'201':
description: 已创建;建项目的人自动成为负责人(AR12)
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectDetail' }
'409':
description: '`plan_limit_exceeded`:免费版限 1 个项目'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/projects/{project_id}:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [projects]
operationId: getProject
summary: 项目概览
responses:
'200':
description: OK
headers:
ETag: { $ref: '#/components/headers/ETag' }
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectDetail' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
tags: [projects]
operationId: deleteProject
summary: 删除项目(彻底删除业务内容,不可恢复;额度流水保留,G10)
responses:
'204': { description: 已删除;对象存储里的文件由后台清理 }
'404': { $ref: '#/components/responses/NotFound' }
/projects/{project_id}/fields/{field_key}:
parameters:
- $ref: '#/components/parameters/ProjectId'
- name: field_key
in: path
required: true
schema: { $ref: '#/components/schemas/ProjectFieldKey' }
put:
tags: [projects]
operationId: setProjectField
summary: 人工修改项目基本信息字段(FR-M2-03)
description: '`value` 为 null 表示撤销人工值,恢复为 AI 提取值。'
parameters:
- $ref: '#/components/parameters/IfMatch'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [value]
properties:
value: { $ref: '#/components/schemas/FieldValue' }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectField' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'412': { $ref: '#/components/responses/PreconditionFailed' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/projects/{project_id}/fields/{field_key}/pending:
parameters:
- $ref: '#/components/parameters/ProjectId'
- name: field_key
in: path
required: true
schema: { $ref: '#/components/schemas/ProjectFieldKey' }
post:
tags: [projects]
operationId: resolvePendingField
summary: 确认或驳回补遗带来的新值(FR-M2-10)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accept]
properties:
accept: { type: boolean }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectField' }
'409':
description: '`no_pending_value` 或 `project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/projects/{project_id}/transitions:
parameters:
- $ref: '#/components/parameters/ProjectId'
post:
tags: [projects]
operationId: transitionProject
summary: 手动变更项目状态(递交 / 撤销递交 / 归档 / 撤销归档,FR-M6-02)
description: 致命问题未处理也可以递交(红线 4);响应里带当时的就绪状态,由前端提示。
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [action]
properties:
action: { type: string, enum: [submit, unsubmit, archive, unarchive] }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectDetail' }
'409':
description: '`invalid_transition`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/projects/{project_id}/lots:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [projects]
operationId: listLots
summary: 分包列表
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Lot' }
post:
tags: [projects]
operationId: createLot
summary: 手工增加分包(FR-M2-04)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LotInput' }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Lot' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/projects/{project_id}/bidding-lots:
parameters:
- $ref: '#/components/parameters/ProjectId'
put:
tags: [projects]
operationId: setBiddingLots
summary: 选择要投的包
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [lot_ids]
properties:
lot_ids:
type: array
minItems: 1
items: { $ref: '#/components/schemas/Id' }
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Lot' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
/lots/{lot_id}:
parameters:
- name: lot_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
patch:
tags: [projects]
operationId: updateLot
summary: 修改分包
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LotInput' }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Lot' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'422': { $ref: '#/components/responses/ValidationFailed' }
delete:
tags: [projects]
operationId: deleteLot
summary: 删除分包(原属这个包的清单项变为各包通用)
responses:
'204': { description: 已删除 }
'409':
description: '`last_lot`:至少保留一个包;或 `project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
# ------------------------------------------------------------------ uploads
/uploads:
post:
tags: [uploads]
operationId: createUpload
summary: 开始一次分片上传(架构 §5.2 第 7 条)
description: 先按套餐校验文件大小;超出上限直接返回 409,不建上传(FR-M2-01)。
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [filename, size_bytes, purpose]
properties:
filename: { type: string, maxLength: 255 }
size_bytes: { type: integer, minimum: 1 }
sha256: { type: string, pattern: '^[0-9a-f]{64}$', description: 选填;提供时完成上传时校验 }
purpose: { type: string, enum: [project_document, qualification_file] }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Upload' }
'409':
description: '`plan_limit_exceeded`:超出单文件大小上限,`detail` 写明上限值'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/uploads/{upload_id}:
parameters:
- $ref: '#/components/parameters/UploadId'
get:
tags: [uploads]
operationId: getUpload
summary: 查询已收到的分片(断点续传用)
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Upload' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
tags: [uploads]
operationId: abortUpload
summary: 放弃上传
responses:
'204': { description: 已放弃 }
/uploads/{upload_id}/parts/{part_no}:
parameters:
- $ref: '#/components/parameters/UploadId'
- name: part_no
in: path
required: true
schema: { type: integer, minimum: 1 }
put:
tags: [uploads]
operationId: putUploadPart
summary: 上传一个分片(重复上传同一分片会覆盖)
requestBody:
required: true
content:
application/octet-stream:
schema: { type: string, format: binary }
responses:
'204': { description: 已收到 }
'404': { $ref: '#/components/responses/NotFound' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/uploads/{upload_id}/complete:
parameters:
- $ref: '#/components/parameters/UploadId'
post:
tags: [uploads]
operationId: completeUpload
summary: 完成上传:合并分片、按文件头识别格式、读页数
description: |
格式、加密、损坏、页数超限都在这里拦截,不建任务、不扣点(FR-M2-01)。
识别出电子投标平台的加密格式时返回 `bid_file_platform_encrypted`(FR-M3-01)。
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/UploadCompleted' }
'409':
description: '`upload_incomplete`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'422':
description: |
`file_format_unsupported`、`file_encrypted`、`file_corrupted`、`bid_file_platform_encrypted`、
`upload_checksum_mismatch`、`plan_limit_exceeded`(页数)
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
# ------------------------------------------------------------------ documents
/projects/{project_id}/documents:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [documents]
operationId: listDocuments
summary: 项目的文档(招标文件、补遗、澄清、投标分册的各个版本)
parameters:
- name: role
in: query
schema: { $ref: '#/components/schemas/DocumentRole' }
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Document' }
post:
tags: [documents]
operationId: createDocument
summary: 把上传好的文件挂到项目上(不扣点;招标侧文件要另外发起解析)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/DocumentCreate' }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Document' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/documents/{document_id}:
parameters:
- $ref: '#/components/parameters/DocumentId'
get:
tags: [documents]
operationId: getDocument
summary: 文档详情
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Document' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
tags: [documents]
operationId: deleteDocument
summary: 删除文档
description: 已被清单项或检查批次引用的文档不能删除,返回 `document_in_use`。
responses:
'204': { description: 已删除 }
'409':
description: '`document_in_use` 或 `project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/documents/{document_id}/file:
parameters:
- $ref: '#/components/parameters/DocumentId'
get:
tags: [documents]
operationId: getDocumentFileUrl
summary: 取文件的短时下载地址(pdf.js 直接加载,不需要带鉴权头)
parameters:
- name: variant
in: query
required: true
schema: { type: string, enum: [normalized, original] }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/DownloadUrl' }
'409':
description: '`document_not_ready`:规范化 PDF 还没生成'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/documents/{document_id}/pages:
parameters:
- $ref: '#/components/parameters/DocumentId'
get:
tags: [documents]
operationId: listDocumentPages
summary: 逐页摘要(阅读器的页尺寸、OCR 标记)
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [normalized_sha256, items]
properties:
normalized_sha256: { type: string }
items:
type: array
items: { $ref: '#/components/schemas/DocumentPage' }
/documents/{document_id}/pages/{page_no}/sentences:
parameters:
- $ref: '#/components/parameters/DocumentId'
- name: page_no
in: path
required: true
schema: { type: integer, minimum: 1 }
get:
tags: [documents]
operationId: listPageSentences
summary: 一页的句子(阅读器里选句补出处用;数据来自对象存储的文字层,DM1)
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Sentence' }
'404': { $ref: '#/components/responses/NotFound' }
/projects/{project_id}/citations:
parameters:
- $ref: '#/components/parameters/ProjectId'
post:
tags: [documents]
operationId: createCitation
summary: 人工建一条出处(在阅读器里选中句子)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CitationCreate' }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Citation' }
'422':
description: '`sentence_not_found`:句子编号不在这份文档里,或不在同一页'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/documents/{document_id}/parse-jobs:
parameters:
- $ref: '#/components/parameters/DocumentId'
post:
tags: [documents]
operationId: startParseJob
summary: 发起解析(预扣 1 次解析;失败、超时、取消都退回)
description: |
只用于招标文件、补遗、澄清;投标分册的解析算在检查里(01 §4)。
失败后重试也调用这个接口,会建一条新任务并重新预扣,上一条已经退回(FR-M2-02)。
requestBody:
content:
application/json:
schema:
type: object
properties:
expected_charge:
type: integer
enum: [1]
description: 前端提示用户的扣点数;和服务端算的不一致时返回 `billing_changed`(API4)
responses:
'201':
description: 已排队
content:
application/json:
schema: { $ref: '#/components/schemas/ParseJob' }
'409':
description: '`job_already_running`(附 `job_id`)、`quota_exhausted`、`billing_changed`、`document_role_not_parsable`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/parse-jobs/{job_id}:
parameters:
- $ref: '#/components/parameters/JobId'
get:
tags: [documents]
operationId: getParseJob
summary: 解析进度(事件流断线时的轮询兜底)
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ParseJob' }
'404': { $ref: '#/components/responses/NotFound' }
/parse-jobs/{job_id}/cancel:
parameters:
- $ref: '#/components/parameters/JobId'
post:
tags: [documents]
operationId: cancelParseJob
summary: 取消解析(退回额度)
responses:
'202':
description: 已受理;worker 在下一个检查点停下
content:
application/json:
schema: { $ref: '#/components/schemas/ParseJob' }
'409':
description: '`job_finished`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
# ------------------------------------------------------------------ checklist
/projects/{project_id}/checklist:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [checklist]
operationId: getChecklist
summary: 清单概况:状态、版本、各类数量、标注规则、数量核对提示
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Checklist' }
'404': { $ref: '#/components/responses/NotFound' }
/projects/{project_id}/checklist/items:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [checklist]
operationId: listChecklistItems
summary: 清单项(一个项目几百条,一次返回全部,不分页)
parameters:
- name: zone
in: query
schema: { $ref: '#/components/schemas/Zone' }
- name: category
in: query
schema: { $ref: '#/components/schemas/Category' }
- name: lot_scope
in: query
description: '`bidding` = 通用 + 所投的包(默认,FR-M2-04);`all` = 全部'
schema: { type: string, enum: [bidding, all], default: bidding }
- name: include_deleted
in: query
schema: { type: boolean, default: false }
responses:
'200':
description: 按原文顺序
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ChecklistItem' }
post:
tags: [checklist]
operationId: createChecklistItem
summary: 人工新增清单项(标注「人工添加」,可以没有出处)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItemCreate' }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/checklist-items/{item_id}:
parameters:
- $ref: '#/components/parameters/ItemId'
get:
tags: [checklist]
operationId: getChecklistItem
summary: 清单项详情
responses:
'200':
description: OK
headers:
ETag: { $ref: '#/components/headers/ETag' }
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
'404': { $ref: '#/components/responses/NotFound' }
patch:
tags: [checklist]
operationId: updateChecklistItem
summary: 修改清单项(改过的字段记入 edited_fields;已确认的清单回到草稿)
parameters:
- $ref: '#/components/parameters/IfMatch'
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItemUpdate' }
responses:
'200':
description: OK
headers:
ETag: { $ref: '#/components/headers/ETag' }
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'412': { $ref: '#/components/responses/PreconditionFailed' }
'422': { $ref: '#/components/responses/ValidationFailed' }
delete:
tags: [checklist]
operationId: deleteChecklistItem
summary: 删除(软删除,可以恢复;待确认区的条目就是「丢弃」)
responses:
'204': { description: 已删除 }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
/checklist-items/{item_id}/restore:
parameters:
- $ref: '#/components/parameters/ItemId'
post:
tags: [checklist]
operationId: restoreChecklistItem
summary: 恢复已删除的条目
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
/checklist-items/{item_id}/accept:
parameters:
- $ref: '#/components/parameters/ItemId'
post:
tags: [checklist]
operationId: acceptPendingItem
summary: 从待确认区采纳进正式清单(FR-M2-07)
description: 必须补上出处,或者声明「人工确认、无出处」;可以同时修改字段(修改后采纳)。
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
citation_ids:
type: array
items: { $ref: '#/components/schemas/Id' }
no_citation_ack: { type: boolean, default: false }
changes: { $ref: '#/components/schemas/ChecklistItemUpdate' }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
'409':
description: '`item_not_pending` 或 `project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'422':
description: '`citation_required`:既没有出处也没有声明无出处'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/checklist-items/{item_id}/citations:
parameters:
- $ref: '#/components/parameters/ItemId'
put:
tags: [checklist]
operationId: setItemCitations
summary: 替换清单项的出处列表(按顺序)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [citation_ids]
properties:
citation_ids:
type: array
items: { $ref: '#/components/schemas/Id' }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
/checklist-items/{item_id}/supersession:
parameters:
- $ref: '#/components/parameters/ItemId'
post:
tags: [checklist]
operationId: resolveSupersession
summary: 确认或驳回「已被补遗 N 更新」(FR-M2-10)
description: 接受时旧条目软删除,新条目保留;驳回时解除关联,两条都保留,由用户自己处理。
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accept]
properties:
accept: { type: boolean }
responses:
'200':
description: 返回旧条目
content:
application/json:
schema: { $ref: '#/components/schemas/ChecklistItem' }
'409':
description: '`not_superseded`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/checklist-items/{item_id}/history:
parameters:
- $ref: '#/components/parameters/ItemId'
get:
tags: [checklist]
operationId: getItemHistory
summary: 修改记录(谁、什么时候、改前、改后;来自审计事件)
responses:
'200':
description: 按时间倒序
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/HistoryEntry' }
/projects/{project_id}/checklist/confirm:
parameters:
- $ref: '#/components/parameters/ProjectId'
post:
tags: [checklist]
operationId: confirmChecklist
summary: 确认清单(生成不可变版本)
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Checklist' }
'409':
description: '`checklist_empty` 或 `project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/projects/{project_id}/checklist/versions:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [checklist]
operationId: listChecklistVersions
summary: 清单版本
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ChecklistVersion' }
# ------------------------------------------------------------------ checks
/projects/{project_id}/check-preview:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [checks]
operationId: getCheckPreview
summary: 发起检查前的预览:这次扣不扣点、用哪些文件、有什么提示
description: 「开始检查」按钮旁的扣点说明来自这里(红线 9)。
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/CheckPreview' }
/projects/{project_id}/check-runs:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [checks]
operationId: listCheckRuns
summary: 检查记录
responses:
'200':
description: 按时间倒序
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/CheckRun' }
post:
tags: [checks]
operationId: startCheckRun
summary: 发起检查(FR-M3-02)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CheckRunCreate' }
responses:
'201':
description: 已排队
content:
application/json:
schema: { $ref: '#/components/schemas/CheckRun' }
'409':
description: |
`job_already_running`(附 `job_id`)、`quota_exhausted`、`billing_changed`(附最新的 `preview`)、
`no_parsed_tender`、`no_bid_documents`、`project_read_only`
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/check-runs/{run_id}:
parameters:
- $ref: '#/components/parameters/RunId'
get:
tags: [checks]
operationId: getCheckRun
summary: 检查批次(进度、覆盖范围、各级别数量)
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/CheckRun' }
'404': { $ref: '#/components/responses/NotFound' }
/check-runs/{run_id}/cancel:
parameters:
- $ref: '#/components/parameters/RunId'
post:
tags: [checks]
operationId: cancelCheckRun
summary: 取消检查(退回额度)
responses:
'202':
description: 已受理
content:
application/json:
schema: { $ref: '#/components/schemas/CheckRun' }
'409':
description: '`job_finished`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/check-runs/{run_id}/findings:
parameters:
- $ref: '#/components/parameters/RunId'
get:
tags: [checks]
operationId: listFindings
summary: 本批次的全部问题(含「本次已解决」,一次返回全部)
parameters:
- name: severity
in: query
schema: { $ref: '#/components/schemas/Severity' }
- name: status
in: query
schema: { $ref: '#/components/schemas/FindingStatus' }
- name: presence
in: query
schema: { $ref: '#/components/schemas/Presence' }
responses:
'200':
description: 按级别、再按原文顺序
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Finding' }
/check-runs/{run_id}/findings/bulk-resolve:
parameters:
- $ref: '#/components/parameters/RunId'
post:
tags: [checks]
operationId: bulkResolveFindings
summary: 批量处理(只限「提示」级,FR-M3-07)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [finding_ids, status]
properties:
finding_ids:
type: array
minItems: 1
maxItems: 500
items: { $ref: '#/components/schemas/Id' }
status: { type: string, enum: [confirmed_ok, ignored] }
note: { type: string, maxLength: 500 }
responses:
'200':
description: 全部成功才提交,否则一条都不改
content:
application/json:
schema:
type: object
required: [items, project]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Finding' }
project: { $ref: '#/components/schemas/ProjectReadiness' }
'409':
description: '`run_superseded`、`project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'422':
description: '`bulk_only_hint`(附不符合的问题编号)、`note_required`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/findings/{finding_id}/resolve:
parameters:
- $ref: '#/components/parameters/FindingId'
post:
tags: [checks]
operationId: resolveFinding
summary: 处理一个问题
description: |
忽略必须写原因;致命问题选「确认无误」也必须写说明,说明同时进入误报反馈(U4)。
只有最近一次检查的问题可以处理,历史批次只读。
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [status]
properties:
status: { type: string, enum: [confirmed_ok, modified, ignored] }
note: { type: string, maxLength: 500 }
responses:
'200':
description: 返回问题和项目最新的就绪状态
content:
application/json:
schema:
type: object
required: [finding, project]
properties:
finding: { $ref: '#/components/schemas/Finding' }
project: { $ref: '#/components/schemas/ProjectReadiness' }
'409':
description: '`run_superseded`、`finding_resolved_this_run`、`project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
'422':
description: '`note_required`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/findings/{finding_id}/reopen:
parameters:
- $ref: '#/components/parameters/FindingId'
post:
tags: [checks]
operationId: reopenFinding
summary: 撤销处理,回到待处理(U 键)
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [finding, project]
properties:
finding: { $ref: '#/components/schemas/Finding' }
project: { $ref: '#/components/schemas/ProjectReadiness' }
'409':
description: '`run_superseded`、`project_read_only`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/projects/{project_id}/manual-checks:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [checks]
operationId: listManualChecks
summary: 需人工核对的条目和勾选结果(按项目存,P2)
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ManualCheck' }
put:
tags: [checks]
operationId: setManualCheck
summary: 勾选或取消勾选一条
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [check_key, checked]
properties:
check_key: { type: string, description: 'item:<清单项编号> 或 rule:<规则编号>' }
checked: { type: boolean }
note: { type: string, maxLength: 500 }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ManualCheck' }
'409': { $ref: '#/components/responses/ProjectReadOnly' }
'422': { $ref: '#/components/responses/ValidationFailed' }
# ------------------------------------------------------------------ reports
/check-runs/{run_id}/reports:
parameters:
- $ref: '#/components/parameters/RunId'
post:
tags: [reports]
operationId: generateReport
summary: 生成检查报告 PDF(后台生成,完成后推送事件)
description: 致命问题未处理也可以导出(红线 4)。每次生成都是一份新报告。
responses:
'202':
description: 已排队
content:
application/json:
schema: { $ref: '#/components/schemas/ReportJob' }
'409':
description: '`run_not_succeeded`'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
/report-jobs/{job_id}:
parameters:
- $ref: '#/components/parameters/JobId'
get:
tags: [reports]
operationId: getReportJob
summary: 报告生成进度(轮询兜底)
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ReportJob' }
'404': { $ref: '#/components/responses/NotFound' }
/projects/{project_id}/reports:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [reports]
operationId: listReports
summary: 项目的报告(随项目归档,可以重复下载)
responses:
'200':
description: 按时间倒序
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Report' }
/reports/{report_id}/file:
parameters:
- name: report_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
get:
tags: [reports]
operationId: getReportFileUrl
summary: 报告的短时下载地址
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/DownloadUrl' }
'404': { $ref: '#/components/responses/NotFound' }
# ------------------------------------------------------------------ qualifications
/qualifications:
get:
tags: [qualifications]
operationId: listQualifications
summary: 资质库
parameters:
- name: kind
in: query
schema: { $ref: '#/components/schemas/QualificationKind' }
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Qualification' }
post:
tags: [qualifications]
operationId: createQualification
summary: 新增资质
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/QualificationInput' }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Qualification' }
'422': { $ref: '#/components/responses/ValidationFailed' }
/qualifications/{qualification_id}:
parameters:
- $ref: '#/components/parameters/QualificationId'
put:
tags: [qualifications]
operationId: updateQualification
summary: 修改资质
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/QualificationInput' }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Qualification' }
'404': { $ref: '#/components/responses/NotFound' }
'422': { $ref: '#/components/responses/ValidationFailed' }
delete:
tags: [qualifications]
operationId: deleteQualification
summary: 删除资质(连同附件)
responses:
'204': { description: 已删除 }
/qualifications/{qualification_id}/files:
parameters:
- $ref: '#/components/parameters/QualificationId'
post:
tags: [qualifications]
operationId: attachQualificationFile
summary: 挂上一个已上传的附件
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [upload_id]
properties:
upload_id: { $ref: '#/components/schemas/Id' }
responses:
'201':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Qualification' }
/qualifications/{qualification_id}/files/{file_id}:
parameters:
- $ref: '#/components/parameters/QualificationId'
- name: file_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
get:
tags: [qualifications]
operationId: getQualificationFileUrl
summary: 附件的短时下载地址
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/DownloadUrl' }
delete:
tags: [qualifications]
operationId: deleteQualificationFile
summary: 删除附件
responses:
'204': { description: 已删除 }
# ------------------------------------------------------------------ notifications / events / feedback
/notifications:
get:
tags: [notifications]
operationId: listNotifications
summary: 站内通知
parameters:
- name: unread
in: query
schema: { type: boolean }
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: 按时间倒序
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Page'
- type: object
required: [items, unread_count]
properties:
unread_count: { type: integer }
items:
type: array
items: { $ref: '#/components/schemas/Notification' }
/notifications/read:
post:
tags: [notifications]
operationId: markNotificationsRead
summary: 标为已读(不传编号就是全部已读)
requestBody:
content:
application/json:
schema:
type: object
properties:
ids:
type: array
items: { $ref: '#/components/schemas/Id' }
responses:
'204': { description: OK }
/events:
get:
tags: [notifications]
operationId: streamEvents
summary: 事件流(SSE)
description: |
当前用户在当前企业下的事件。前端用 fetch 读流(可以带 Bearer 头),不用浏览器的 EventSource(API2)。
每条事件带 `id`;断线重连时带 `Last-Event-ID`,服务端尽量补发,补不了时发 `resync`,前端重新拉取数据。
事件只是「有变化」的提示,权威状态以对应资源的 GET 为准。
parameters:
- name: Last-Event-ID
in: header
schema: { type: string }
responses:
'200':
description: '`text/event-stream`;`event` 字段是 Event.type,`data` 是 Event 的 JSON'
content:
text/event-stream:
schema: { $ref: '#/components/schemas/Event' }
/feedback:
post:
tags: [notifications]
operationId: createFeedback
summary: 反馈判断有误(误报 / 漏报,J5)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/FeedbackCreate' }
responses:
'201': { description: 已提交 }
'422': { $ref: '#/components/responses/ValidationFailed' }
components:
securitySchemes:
cookieSession:
type: apiKey
in: cookie
name: bd_session
description: 网页版。HttpOnly、Secure、SameSite=Lax;写操作还要带 `X-CSRF-Token`(等于 cookie `bd_csrf` 的值)
bearerToken:
type: http
scheme: bearer
description: 桌面版。令牌存系统钥匙串;不需要 CSRF 头
headers:
SetSessionCookie:
description: 网页版登录时设置 `bd_session`(HttpOnly)和 `bd_csrf`(前端可读)
schema: { type: string }
ETag:
description: 资源版本;修改时放进 If-Match
schema: { type: string }
parameters:
ProjectId:
name: project_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
DocumentId:
name: document_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
UploadId:
name: upload_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
JobId:
name: job_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
RunId:
name: run_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
ItemId:
name: item_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
FindingId:
name: finding_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
QualificationId:
name: qualification_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
SessionId:
name: session_id
in: path
required: true
schema: { $ref: '#/components/schemas/Id' }
Cursor:
name: cursor
in: query
description: 上一页返回的 next_cursor
schema: { type: string }
Limit:
name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
IfMatch:
name: If-Match
in: header
description: 选填;带上时版本不一致返回 412(多人编辑时防止覆盖)
schema: { type: string }
responses:
Unauthenticated:
description: '`unauthenticated`、`session_expired`、`session_revoked`(被其他设备挤下线时附 `reason: concurrent_login`)'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
NotFound:
description: '`not_found`;访问别的企业的资源也返回这个(架构 §3.3)'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
ValidationFailed:
description: '`validation_failed`;`errors` 列出每个字段的问题'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
RateLimited:
description: '`rate_limited`;响应头 Retry-After'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
ProjectReadOnly:
description: '`project_read_only`:项目已递交或已归档,要先撤销'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
PreconditionFailed:
description: '`precondition_failed`:资源已被修改,请刷新后再改'
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
schemas:
# ---------- 基础
Id:
type: string
format: uuid
Timestamp:
type: string
format: date-time
description: RFC 3339,带时区
Money:
type: string
pattern: '^-?[0-9]+(\.[0-9]{1,2})?$'
description: 金额用字符串传,避免浮点误差;单位元
Phone:
type: string
pattern: '^1[3-9][0-9]{9}$'
description: 中国大陆手机号,11 位
Page:
type: object
required: [next_cursor]
properties:
next_cursor:
type: [string, 'null']
description: 为 null 表示没有下一页
Problem:
type: object
description: RFC 9457 problem details,`code` 是给前端判断用的稳定编码(api-design §5)
required: [type, title, status, code, request_id]
properties:
type: { type: string, description: '固定为 about:blank 或错误码文档地址' }
title: { type: string, description: 给用户看的一句话(中文) }
status: { type: integer }
code: { type: string, examples: [quota_exhausted] }
detail: { type: string }
request_id: { type: string }
errors:
type: array
items:
type: object
required: [field, code, message]
properties:
field: { type: string, description: '字段路径,如 spec.tech_param.value' }
code: { type: string }
message: { type: string }
additionalProperties: true
DownloadUrl:
type: object
required: [url, expires_at]
properties:
url: { type: string, format: uri }
expires_at: { $ref: '#/components/schemas/Timestamp' }
sha256: { type: string }
UserRef:
type: object
description: 操作人;人离职或注销后仍显示当时的姓名
required: [id, name]
properties:
id: { $ref: '#/components/schemas/Id' }
name: { type: string }
# ---------- 枚举(值与数据库一致)
ProjectStatus: { type: string, enum: [tracking, drafting, checked, submitted, archived] }
LegalRegime: { type: string, enum: [gov_procurement, tendering, unknown] }
DocumentRole: { type: string, enum: [tender, addendum, clarification, bid_volume] }
DetectedFormat: { type: string, enum: [pdf, docx, doc, wps, ofd, encrypted_bid, unknown] }
JobStatus: { type: string, enum: [queued, running, succeeded, failed, cancelled] }
Category: { type: string, enum: [qualification, invalidity, scoring, schedule, tech_param, format_doc, price] }
Importance:
type: string
enum: [substantive, important, normal]
description: 界面显示为 ★ / ▲ / 一般
Zone: { type: string, enum: [pending, formal] }
PendingReason: { type: string, enum: [no_citation, low_confidence, verify_failed, marker_uncovered, absent_in_rerun] }
Origin: { type: string, enum: [ai, rule, human] }
FindingType:
type: string
enum: [unresponded, negative_deviation, suspected_unmet, formality, qualification, price_form, manual_review]
Severity: { type: string, enum: [fatal, important, hint] }
FindingStatus: { type: string, enum: [pending, confirmed_ok, modified, ignored] }
Presence:
type: string
enum: [new, carried, resolved]
description: 新增 / 沿用 / 本次已解决
BillingMode:
type: string
enum: [charged, waived_recheck, free_plan]
QuotaUnit: { type: string, enum: [parse, check, generate, dedupe] }
QuotaEntryKind: { type: string, enum: [grant, hold, settle, release, expire, adjust, waive] }
PlanCode: { type: string, enum: [free, pro, enterprise] }
QualificationKind: { type: string, enum: [certificate, system_cert, software_copyright, other] }
ProjectFieldKey:
type: string
enum: [name, code, purchaser, agency, procurement_method, legal_regime, budget_total, ceiling_total,
eval_method, accept_consortium, accept_imported, sme_only, deadline_at]
NextAction:
type: string
description: 工作台「下一步」,由服务端按项目状态算出
enum: [upload_tender, wait_parse, select_lots, confirm_checklist, upload_bid, run_check,
handle_fatal, export_report, archive, none]
Permission:
type: string
description: 权限点(AR12)。MVP 的所有者拥有全部;前端只用来决定按钮显不显示,服务端仍会检查
enum: [project.create, project.edit, project.delete, checklist.edit, checklist.confirm,
check.run, finding.resolve, report.export, quota.view, billing.manage, qualification.edit, tenant.close]
# ---------- auth / me
RegisterRequest:
type: object
required: [phone, sms_code, tenant_name, display_name, terms_version, client_kind]
properties:
phone: { $ref: '#/components/schemas/Phone' }
sms_code: { type: string, pattern: '^[0-9]{6}$' }
tenant_name: { type: string, minLength: 2, maxLength: 100, description: 企业名称(MVP 不做认证,G9) }
display_name: { type: string, minLength: 1, maxLength: 40 }
terms_version: { type: string, description: 同意的服务协议版本(含围串标禁止条款) }
client_kind: { type: string, enum: [web, desktop] }
LoginRequest:
type: object
required: [phone, sms_code, client_kind]
properties:
phone: { $ref: '#/components/schemas/Phone' }
sms_code: { type: string, pattern: '^[0-9]{6}$' }
client_kind: { type: string, enum: [web, desktop] }
force:
type: boolean
default: false
description: 已有其他设备在线时,确认挤下线后重发
LoginResult:
type: object
required: [me]
properties:
me: { $ref: '#/components/schemas/Me' }
token:
type: string
description: 只有 client_kind = desktop 时返回,且只返回这一次
expires_at: { $ref: '#/components/schemas/Timestamp' }
SessionInfo:
type: object
required: [id, client_kind, created_at, last_seen_at, current]
properties:
id: { $ref: '#/components/schemas/Id' }
client_kind: { type: string, enum: [web, desktop] }
device: { type: string, description: 从 User-Agent 归纳,如「Chrome · macOS」 }
ip_region: { type: string, description: 'IP 归属地,如「江苏南京」;不返回完整 IP' }
created_at: { $ref: '#/components/schemas/Timestamp' }
last_seen_at: { $ref: '#/components/schemas/Timestamp' }
current: { type: boolean }
Me:
type: object
required: [user, tenant, role, plan, permissions]
properties:
user:
type: object
required: [id, display_name, phone_hint]
properties:
id: { $ref: '#/components/schemas/Id' }
display_name: { type: string }
phone_hint: { type: string, examples: ['138****5678'] }
tenant:
type: object
required: [id, name]
properties:
id: { $ref: '#/components/schemas/Id' }
name: { type: string }
role: { type: string, enum: [owner, admin, member] }
plan: { $ref: '#/components/schemas/PlanSummary' }
permissions:
type: array
items: { $ref: '#/components/schemas/Permission' }
# ---------- billing
PlanSummary:
type: object
required: [code, name, read_only]
properties:
code: { $ref: '#/components/schemas/PlanCode' }
name: { type: string }
starts_at: { $ref: '#/components/schemas/Timestamp' }
ends_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
read_only:
type: boolean
description: 套餐到期后数据只读(FR-M6-05)
Plan:
type: object
required: [code, version, name, quotas, limits]
properties:
code: { $ref: '#/components/schemas/PlanCode' }
version: { type: integer }
name: { type: string }
quotas:
type: object
description: 每个周期的额度;专业版的检查从解析额度扣
additionalProperties: { type: integer }
limits:
type: object
properties:
max_file_bytes: { type: integer }
max_pages: { type: integer }
max_projects: { type: [integer, 'null'] }
check_rule:
type: string
description: 给用户看的检查计费说明,如「检查从解析额度扣 1 次;同一项目 24 小时内复查不扣」
QuotaOverview:
type: object
required: [plan, accounts]
properties:
plan: { $ref: '#/components/schemas/PlanSummary' }
accounts:
type: array
items:
type: object
required: [unit, balance, period_granted, period_used]
properties:
unit: { $ref: '#/components/schemas/QuotaUnit' }
balance: { type: integer, description: 剩余,含正在预扣的 }
held: { type: integer, description: 正在预扣、还没结算的 }
period_granted: { type: integer }
period_used: { type: integer }
used_ratio: { type: number, description: '0~1;≥ 0.8 时界面变赭色(Q9)' }
by_feature:
type: array
description: 按功能分布,如解析、检查
items:
type: object
required: [feature, used]
properties:
feature: { type: string, enum: [parse, check] }
used: { type: integer }
QuotaEntry:
type: object
required: [id, unit, kind, amount, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
unit: { $ref: '#/components/schemas/QuotaUnit' }
kind: { $ref: '#/components/schemas/QuotaEntryKind' }
amount: { type: integer, description: 对余额的影响;结算、免扣为 0 }
reason: { type: string, description: '调整原因;免扣时为规则说明,如「24 小时内复查」' }
project:
type: [object, 'null']
properties:
id: { $ref: '#/components/schemas/Id' }
name: { type: [string, 'null'], description: '项目已删除时为 null,界面显示「(已删除的项目)」' }
deleted: { type: boolean }
ref:
type: [object, 'null']
properties:
type: { type: string, enum: [parse_job, check_run] }
id: { $ref: '#/components/schemas/Id' }
label: { type: [string, 'null'], description: '如「招标文件解析」「第 3 次检查」' }
created_at: { $ref: '#/components/schemas/Timestamp' }
ActivationRequestCreate:
type: object
required: [plan_code, invoice_info]
properties:
plan_code: { type: string, enum: [pro] }
invoice_info: { $ref: '#/components/schemas/InvoiceInfo' }
InvoiceInfo:
type: object
required: [title, tax_no]
properties:
title: { type: string, description: 发票抬头 }
tax_no: { type: string, description: 纳税人识别号 }
address_phone: { type: string }
bank_account: { type: string }
invoice_type: { type: string, enum: [general, special] }
contact_name: { type: string }
contact_phone: { type: string }
ActivationRequest:
type: object
required: [id, plan_code, status, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
plan_code: { $ref: '#/components/schemas/PlanCode' }
status: { type: string, enum: [submitted, paid, activated, rejected, cancelled] }
invoice_info: { $ref: '#/components/schemas/InvoiceInfo' }
payment_instructions: { type: string, description: 对公转账账户和备注要求 }
ops_note: { type: string }
created_at: { $ref: '#/components/schemas/Timestamp' }
# ---------- projects
ProjectReadiness:
type: object
description: 就绪状态只在 status = checked 时有意义(G7);未就绪也不阻止导出和递交(红线 4)
required: [status, open_fatal_count, ready]
properties:
status: { $ref: '#/components/schemas/ProjectStatus' }
open_fatal_count: { type: integer }
ready: { type: [boolean, 'null'], description: '不是 checked 状态时为 null' }
ProjectListItem:
type: object
required: [id, name, status, readiness, next_action, bidding_lots, updated_at]
properties:
id: { $ref: '#/components/schemas/Id' }
name: { type: string }
code: { type: [string, 'null'] }
purchaser: { type: [string, 'null'] }
deadline_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
status: { $ref: '#/components/schemas/ProjectStatus' }
readiness: { $ref: '#/components/schemas/ProjectReadiness' }
bidding_lots:
type: array
items: { type: string, description: 包号 }
pending_item_count: { type: integer, description: 待确认区条目数 }
active_job:
type: [object, 'null']
description: 正在跑的解析或检查
properties:
kind: { type: string, enum: [parse_job, check_run] }
id: { $ref: '#/components/schemas/Id' }
progress: { type: integer }
label: { type: string }
next_action: { $ref: '#/components/schemas/NextAction' }
updated_at: { $ref: '#/components/schemas/Timestamp' }
ProjectDetail:
allOf:
- $ref: '#/components/schemas/ProjectListItem'
- type: object
required: [legal_regime, fields, key_dates, created_by, created_at]
properties:
legal_regime: { $ref: '#/components/schemas/LegalRegime' }
agency: { type: [string, 'null'] }
procurement_method: { type: [string, 'null'] }
fields:
type: array
description: 基本信息字段(FR-M2-03),每个都带出处
items: { $ref: '#/components/schemas/ProjectField' }
key_dates:
type: array
description: 关键日期时间线(FR-M2-09),来自「日期与递交」类清单项
items:
type: object
required: [item_id, label, at]
properties:
item_id: { $ref: '#/components/schemas/Id' }
label: { type: string, examples: [投标截止] }
at: { $ref: '#/components/schemas/Timestamp' }
superseded_pending: { type: boolean, description: 补遗带来了新值、等用户确认 }
latest_check_run_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
members:
type: array
items:
type: object
properties:
user: { $ref: '#/components/schemas/UserRef' }
role: { type: string, enum: [lead, editor, viewer] }
created_by: { $ref: '#/components/schemas/UserRef' }
created_at: { $ref: '#/components/schemas/Timestamp' }
FieldValue:
description: 按字段:文本、布尔、金额(Money 字符串)或时间
type: [string, boolean, 'null']
ProjectField:
type: object
required: [key, value, source]
properties:
key: { $ref: '#/components/schemas/ProjectFieldKey' }
value: { $ref: '#/components/schemas/FieldValue' }
source: { type: string, enum: [ai, human, none] }
ai_value: { $ref: '#/components/schemas/FieldValue' }
citation:
oneOf:
- $ref: '#/components/schemas/Citation'
- type: 'null'
updated_by:
oneOf:
- $ref: '#/components/schemas/UserRef'
- type: 'null'
pending:
type: [object, 'null']
description: 补遗带来的新值(FR-M2-10)
properties:
value: { $ref: '#/components/schemas/FieldValue' }
document_label: { type: string, examples: [补遗 1] }
citation: { $ref: '#/components/schemas/Citation' }
LotInput:
type: object
properties:
lot_no: { type: string, maxLength: 40 }
name: { type: string, maxLength: 200 }
budget_amount:
oneOf:
- $ref: '#/components/schemas/Money'
- type: 'null'
ceiling_amount:
oneOf:
- $ref: '#/components/schemas/Money'
- type: 'null'
Lot:
type: object
required: [id, lot_no, is_default, is_bidding, origin, edited_fields]
properties:
id: { $ref: '#/components/schemas/Id' }
lot_no: { type: string }
name: { type: [string, 'null'] }
budget_amount:
oneOf:
- $ref: '#/components/schemas/Money'
- type: 'null'
ceiling_amount:
oneOf:
- $ref: '#/components/schemas/Money'
- type: 'null'
is_default: { type: boolean, description: 没有分包时自动建的默认包,界面跳过选包 }
is_bidding: { type: boolean }
origin: { $ref: '#/components/schemas/Origin' }
edited_fields:
type: array
items: { type: string }
# ---------- uploads / documents
Upload:
type: object
required: [id, part_size, part_count, received_parts, expires_at]
properties:
id: { $ref: '#/components/schemas/Id' }
part_size: { type: integer, description: 字节,最后一片可以更小 }
part_count: { type: integer }
received_parts:
type: array
items: { type: integer }
expires_at: { $ref: '#/components/schemas/Timestamp' }
UploadCompleted:
type: object
required: [id, filename, size_bytes, detected_format, sha256]
properties:
id: { $ref: '#/components/schemas/Id' }
filename: { type: string }
size_bytes: { type: integer }
sha256: { type: string }
detected_format: { $ref: '#/components/schemas/DetectedFormat' }
extension_mismatch:
type: boolean
description: 扩展名和真实格式不一致(07 F2),界面只做提示
page_count:
type: [integer, 'null']
description: PDF 能直接读出;Word 要转换后才知道,这里为 null
DocumentCreate:
type: object
required: [upload_id, role]
properties:
upload_id: { $ref: '#/components/schemas/Id' }
role: { $ref: '#/components/schemas/DocumentRole' }
volume_name: { type: string, maxLength: 40, description: '投标分册名:商务 / 技术 / 报价 / 资格证明' }
replaces_document_id:
$ref: '#/components/schemas/Id'
description: 投标分册重新上传时填上一版的编号,生成新版本(FR-M3-01)
Document:
type: object
required: [id, role, label, original_filename, detected_format, size_bytes, status, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
role: { $ref: '#/components/schemas/DocumentRole' }
label: { type: string, description: '如「招标文件」「补遗 1」「技术分册 v2」' }
seq_no: { type: integer }
volume_name: { type: [string, 'null'] }
lineage_id: { $ref: '#/components/schemas/Id' }
is_latest_version: { type: boolean }
original_filename: { type: string }
detected_format: { $ref: '#/components/schemas/DetectedFormat' }
size_bytes: { type: integer }
page_count: { type: [integer, 'null'] }
normalized_sha256: { type: [string, 'null'] }
status: { type: string, enum: [uploaded, normalizing, ready, failed] }
latest_parse_job:
oneOf:
- $ref: '#/components/schemas/ParseJob'
- type: 'null'
uploaded_by: { $ref: '#/components/schemas/UserRef' }
created_at: { $ref: '#/components/schemas/Timestamp' }
DocumentPage:
type: object
required: [page_no, width_pt, height_pt, rotation, text_source]
properties:
page_no: { type: integer }
width_pt: { type: number }
height_pt: { type: number }
rotation: { type: integer, enum: [0, 90, 180, 270] }
text_source: { type: string, enum: [text, ocr, mixed, empty] }
Quad:
type: array
description: 一个矩形 [x0, y0, x1, y1],单位 pt,原点在页面左上角
items: { type: number }
minItems: 4
maxItems: 4
Sentence:
type: object
required: [key, text, quads, location]
properties:
key: { type: string, examples: ['<document>:p38:s4'] }
text: { type: string }
quads:
type: array
items: { $ref: '#/components/schemas/Quad' }
location: { type: string, enum: [body, table, header_footer] }
format_runs:
type: array
description: 文字格式片段 [起, 止, 格式],格式取 b / i / u 的组合
items:
type: array
prefixItems:
- { type: integer }
- { type: integer }
- { type: string }
section_path: { type: string, description: '所在章节,如「第三章 采购需求 > 3.2 技术参数」' }
CitationCreate:
type: object
required: [document_id, sentence_keys]
properties:
document_id: { $ref: '#/components/schemas/Id' }
sentence_keys:
type: array
minItems: 1
description: 同一页上连续的句子
items: { type: string }
char_start: { type: integer, minimum: 0 }
char_end: { type: integer, minimum: 1 }
Citation:
type: object
description: 出处,不可变(data-model §5.1)
required: [id, document_id, document_label, page_no, sentence_keys, quote, quads, normalized_sha256, created_by_kind]
properties:
id: { $ref: '#/components/schemas/Id' }
document_id: { $ref: '#/components/schemas/Id' }
document_label: { type: string, examples: [招标文件] }
normalized_sha256: { type: string, description: 和文档当前哈希不一致时前端不画高亮 }
page_no: { type: integer }
sentence_keys:
type: array
items: { type: string }
char_start: { type: [integer, 'null'] }
char_end: { type: [integer, 'null'] }
quote: { type: string, description: 原句 }
quads:
type: array
items: { $ref: '#/components/schemas/Quad' }
created_by_kind: { $ref: '#/components/schemas/Origin' }
JobError:
type: object
description: 给用户看的失败原因;不含文档内容
required: [code, message, refunded]
properties:
code: { type: string, examples: [model_timeout, converter_crashed] }
message: { type: string }
refunded: { type: boolean, description: 额度是否已退回 }
ParseJob:
type: object
required: [id, document_id, attempt_no, status, progress, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
document_id: { $ref: '#/components/schemas/Id' }
attempt_no: { type: integer }
status: { $ref: '#/components/schemas/JobStatus' }
stage:
type: [string, 'null']
enum: [detect, convert, text_layer, ocr, structure, extract, verify, merge, null]
progress: { type: integer, minimum: 0, maximum: 100 }
cancel_requested: { type: boolean }
error:
oneOf:
- $ref: '#/components/schemas/JobError'
- type: 'null'
result:
type: [object, 'null']
description: 成功后的摘要
properties:
items_added: { type: integer }
items_updated: { type: integer }
items_pending: { type: integer }
items_absent: { type: integer, description: 重新解析时没再出现的旧条目 }
lots_found: { type: integer }
created_at: { $ref: '#/components/schemas/Timestamp' }
finished_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
# ---------- checklist
MarkingRule:
type: object
required: [id, marker, meaning, citation]
properties:
id: { $ref: '#/components/schemas/Id' }
marker: { type: string, enum: [star, asterisk, triangle, italic_underline, bold, underline, keyword] }
marker_text: { type: [string, 'null'] }
meaning: { type: string, enum: [substantive, important] }
declared_count: { type: [integer, 'null'] }
found_count: { type: integer, description: 实际抽到的条数 }
citation: { $ref: '#/components/schemas/Citation' }
Checklist:
type: object
required: [status, version_no, counts, marking_rules]
properties:
status: { type: string, enum: [draft, confirmed] }
version_no: { type: integer, description: 最近一次确认的版本号;没确认过为 0 }
confirmed_by:
oneOf:
- $ref: '#/components/schemas/UserRef'
- type: 'null'
confirmed_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
counts:
type: object
required: [formal, pending, deleted]
properties:
formal: { type: integer }
pending: { type: integer }
deleted: { type: integer }
superseded_pending: { type: integer }
by_category:
type: object
additionalProperties: { type: integer }
marking_rules:
type: array
items: { $ref: '#/components/schemas/MarkingRule' }
warnings:
type: array
description: '如「文件写明 ★ 项共 14 条,实际抽到 13 条」'
items:
type: object
required: [code, message]
properties:
code: { type: string, enum: [declared_count_mismatch, marker_rule_not_found, legal_regime_unknown] }
message: { type: string }
ItemSpec:
type: object
description: 类别特有字段(05 §3.3),按类别分组;一条清单项有几个类别就可以有几组
properties:
qualification:
type: object
properties:
kind: { type: string, enum: [general, specific, credit] }
validity_requirement: { type: string, examples: [近 3 个月社保缴纳证明] }
invalidity:
type: object
properties:
source: { type: string, enum: [statute, tender_text, substantive_marker] }
scoring:
type: object
properties:
factor: { type: string }
points: { type: string, description: '分值,可能是「0~5」这样的区间,所以用字符串' }
criteria: { type: string }
part: { type: string, enum: [price, technical, commercial] }
schedule:
type: object
properties:
date_kind: { type: string, enum: [question_deadline, bid_deadline, opening, deposit_deadline, other] }
at: { $ref: '#/components/schemas/Timestamp' }
deposit_amount: { $ref: '#/components/schemas/Money' }
submission_mode: { type: string, enum: [electronic, paper, ca_encrypted, mixed] }
copies: { type: string }
tech_param:
type: object
properties:
param: { type: string }
op: { type: string, enum: ['>=', '<=', '=', contains, supports] }
value: { type: string }
unit: { type: string }
device: { type: string }
format_doc:
type: object
properties:
doc_name: { type: string, examples: [投标函] }
modifiable: { type: boolean }
price:
type: object
description: 分项限价、单价上限(DM7);只存招标文件写明的限价,系统不给任何报价建议(红线 6)
properties:
kind: { type: string, enum: [item_ceiling, unit_price_ceiling] }
target: { type: string }
op: { type: string, enum: ['<='] }
value: { $ref: '#/components/schemas/Money' }
ChecklistItemFields:
type: object
properties:
categories:
type: array
minItems: 1
uniqueItems: true
items: { $ref: '#/components/schemas/Category' }
title: { type: string, minLength: 1, maxLength: 200 }
requirement_text: { type: [string, 'null'] }
importance: { $ref: '#/components/schemas/Importance' }
lot_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
evidence_required: { type: [string, 'null'] }
spec: { $ref: '#/components/schemas/ItemSpec' }
ChecklistItemCreate:
allOf:
- $ref: '#/components/schemas/ChecklistItemFields'
- type: object
required: [categories, title]
properties:
categories:
type: array
minItems: 1
uniqueItems: true
items: { $ref: '#/components/schemas/Category' }
title: { type: string, minLength: 1, maxLength: 200 }
citation_ids:
type: array
items: { $ref: '#/components/schemas/Id' }
ChecklistItemUpdate:
$ref: '#/components/schemas/ChecklistItemFields'
ChecklistItem:
allOf:
- $ref: '#/components/schemas/ChecklistItemFields'
- type: object
required: [id, categories, title, importance, origin, zone, citations, edited_fields, updated_at]
properties:
categories:
type: array
minItems: 1
uniqueItems: true
items: { $ref: '#/components/schemas/Category' }
title: { type: string, minLength: 1, maxLength: 200 }
importance: { $ref: '#/components/schemas/Importance' }
id: { $ref: '#/components/schemas/Id' }
summary:
type: [string, 'null']
description: AI 摘要,界面标注 AI 生成
importance_basis: { type: [string, 'null'], enum: [marker, statute, tender_text, human, null] }
marking_rule_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
lot_no: { type: [string, 'null'], description: 空表示各包通用 }
origin: { $ref: '#/components/schemas/Origin' }
origin_label: { type: string, description: '如「AI」「人工添加」「补遗 1」' }
zone: { $ref: '#/components/schemas/Zone' }
pending_reason:
oneOf:
- $ref: '#/components/schemas/PendingReason'
- type: 'null'
no_citation_ack: { type: boolean, description: 人工确认、无出处 }
citations:
type: array
items: { $ref: '#/components/schemas/Citation' }
ai_values:
type: [object, 'null']
description: AI 原值;和当前值不同的字段界面可以显示「AI 原为……」
edited_fields:
type: array
items: { type: string }
confidence: { type: [number, 'null'] }
superseded_by:
type: [object, 'null']
description: 已被补遗更新,等用户确认
properties:
item_id: { $ref: '#/components/schemas/Id' }
document_label: { type: string }
confirmed: { type: boolean }
accepted_by:
oneOf:
- $ref: '#/components/schemas/UserRef'
- type: 'null'
deleted_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
updated_at: { $ref: '#/components/schemas/Timestamp' }
ChecklistVersion:
type: object
required: [id, version_no, kind, item_count, created_by, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
version_no: { type: integer }
kind: { type: string, enum: [confirmed, check] }
item_count: { type: integer }
created_by: { $ref: '#/components/schemas/UserRef' }
created_at: { $ref: '#/components/schemas/Timestamp' }
HistoryEntry:
type: object
required: [action, actor, at]
properties:
action: { type: string, examples: [checklist_item.update, checklist_item.accept, checklist_item.delete] }
actor: { $ref: '#/components/schemas/UserRef' }
at: { $ref: '#/components/schemas/Timestamp' }
changes:
type: array
items:
type: object
required: [field]
properties:
field: { type: string }
before: {}
after: {}
# ---------- checks
CheckPreview:
type: object
required: [can_start, billing, charge, checklist_confirmed, bid_documents, lot_ids]
properties:
can_start: { type: boolean }
blockers:
type: array
description: can_start 为 false 的原因
items:
type: string
enum: [no_parsed_tender, no_bid_documents, job_already_running, quota_exhausted, project_read_only]
billing: { $ref: '#/components/schemas/BillingMode' }
charge: { type: integer, enum: [0, 1], description: 这次检查扣几次 }
charge_unit: { $ref: '#/components/schemas/QuotaUnit' }
waive_until:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
description: 24 小时免扣窗口的结束时间(从上一次扣点的检查算起,P1)
checklist_confirmed: { type: boolean, description: 为 false 时提示「清单未确认,检查结果以当前清单为准」 }
pending_item_count: { type: integer, description: 待确认区条目不参与检查 }
bid_documents:
type: array
description: 默认用每个分册的最新版本
items: { $ref: '#/components/schemas/Document' }
lot_ids:
type: array
items: { $ref: '#/components/schemas/Id' }
CheckRunCreate:
type: object
required: [document_ids, expected_charge]
properties:
document_ids:
type: array
minItems: 1
description: 本次检查的投标文件版本
items: { $ref: '#/components/schemas/Id' }
expected_charge:
type: integer
enum: [0, 1]
description: 前端按 check-preview 提示用户的扣点数;和服务端当下算的不一致时返回 `billing_changed`,不扣点(API4)
CheckRun:
type: object
required: [id, run_no, status, progress, billing, checklist_version_no, checklist_confirmed, rule_set_version, created_by, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
run_no: { type: integer }
status: { $ref: '#/components/schemas/JobStatus' }
stage:
type: [string, 'null']
enum: [prepare_bid, match, rules, verify, carry_over, summarize, null]
progress: { type: integer, minimum: 0, maximum: 100 }
cancel_requested: { type: boolean }
error:
oneOf:
- $ref: '#/components/schemas/JobError'
- type: 'null'
billing: { $ref: '#/components/schemas/BillingMode' }
is_latest: { type: boolean, description: 只有最近一次检查的问题可以处理 }
checklist_version_no: { type: integer }
checklist_confirmed: { type: boolean }
rule_set_version: { type: string }
legal_regime: { $ref: '#/components/schemas/LegalRegime' }
documents:
type: array
items: { $ref: '#/components/schemas/Document' }
coverage:
type: [object, 'null']
description: 查了什么、没查什么(报告首页,FR-M3-09)
properties:
checked:
type: array
items: { type: string }
not_checked:
type: array
items: { type: string }
counts:
type: [object, 'null']
properties:
fatal: { type: integer }
important: { type: integer }
hint: { type: integer }
open_fatal: { type: integer }
resolved_this_run: { type: integer }
new: { type: integer }
created_by: { $ref: '#/components/schemas/UserRef' }
created_at: { $ref: '#/components/schemas/Timestamp' }
finished_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
Finding:
type: object
required: [id, type, severity, title, explanation, detector, presence, status, tender_citations, bid_citations]
properties:
id: { $ref: '#/components/schemas/Id' }
type: { $ref: '#/components/schemas/FindingType' }
severity: { $ref: '#/components/schemas/Severity' }
title: { type: string }
explanation: { type: string }
detector: { type: string, enum: [ai, rule] }
checklist_item:
type: [object, 'null']
description: AI 判断的问题必有;规则问题没有对应条目时为 null,显示 rule(DM6)
properties:
id: { $ref: '#/components/schemas/Id' }
title: { type: string }
importance: { $ref: '#/components/schemas/Importance' }
categories:
type: array
items: { $ref: '#/components/schemas/Category' }
rule:
type: [object, 'null']
properties:
code: { type: string, examples: [PRICE.CAPITAL_MISMATCH] }
description: { type: string }
detail:
type: object
description: 按类型的补充信息:参数的要求值和响应值、资质预警种类(expired / insufficient_level / not_covered)、从投标文件读到的金额
tender_citations:
type: array
items: { $ref: '#/components/schemas/Citation' }
bid_citations:
type: array
items: { $ref: '#/components/schemas/Citation' }
search_scope:
type: [object, 'null']
description: 「未找到响应」时找过哪些分册、哪些章节(FR-M3-03)
properties:
documents:
type: array
items: { type: string }
sections:
type: array
items: { type: string }
presence: { $ref: '#/components/schemas/Presence' }
carried_status:
oneOf:
- $ref: '#/components/schemas/FindingStatus'
- type: 'null'
description: 沿用时,上一次的处理状态(上次「已修改」的这次回到待处理)
status: { $ref: '#/components/schemas/FindingStatus' }
resolution_note: { type: [string, 'null'] }
resolved_by:
oneOf:
- $ref: '#/components/schemas/UserRef'
- type: 'null'
resolved_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
ManualCheck:
type: object
required: [check_key, label, checked]
properties:
check_key: { type: string }
label: { type: string, examples: [正副本份数与装订] }
requirement_text: { type: [string, 'null'] }
citation:
oneOf:
- $ref: '#/components/schemas/Citation'
- type: 'null'
checked: { type: boolean }
note: { type: [string, 'null'] }
checked_by:
oneOf:
- $ref: '#/components/schemas/UserRef'
- type: 'null'
checked_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
# ---------- reports
ReportJob:
type: object
required: [id, check_run_id, status]
properties:
id: { $ref: '#/components/schemas/Id' }
check_run_id: { $ref: '#/components/schemas/Id' }
status: { $ref: '#/components/schemas/JobStatus' }
report_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
error:
oneOf:
- $ref: '#/components/schemas/JobError'
- type: 'null'
Report:
type: object
required: [id, check_run_id, run_no, page_count, template_version, generated_by, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
check_run_id: { $ref: '#/components/schemas/Id' }
run_no: { type: integer }
page_count: { type: integer }
sha256: { type: string }
template_version: { type: string }
open_fatal_at_export: { type: integer, description: 导出时还没处理的致命问题数 }
generated_by: { $ref: '#/components/schemas/UserRef' }
created_at: { $ref: '#/components/schemas/Timestamp' }
# ---------- qualifications
QualificationInput:
type: object
required: [name, kind]
properties:
name: { type: string, minLength: 1, maxLength: 200 }
kind: { $ref: '#/components/schemas/QualificationKind' }
level: { type: [string, 'null'] }
cert_no: { type: [string, 'null'] }
issuer: { type: [string, 'null'] }
valid_from: { type: [string, 'null'], format: date }
valid_to: { type: [string, 'null'], format: date, description: 空表示长期有效 }
Qualification:
allOf:
- $ref: '#/components/schemas/QualificationInput'
- type: object
required: [id, files, created_at, updated_at]
properties:
id: { $ref: '#/components/schemas/Id' }
files:
type: array
items:
type: object
required: [id, filename, size_bytes]
properties:
id: { $ref: '#/components/schemas/Id' }
filename: { type: string }
size_bytes: { type: integer }
created_at: { $ref: '#/components/schemas/Timestamp' }
updated_at: { $ref: '#/components/schemas/Timestamp' }
# ---------- notifications / events / feedback
Notification:
type: object
required: [id, kind, title, created_at]
properties:
id: { $ref: '#/components/schemas/Id' }
kind:
type: string
enum: [parse_succeeded, parse_failed, check_succeeded, check_failed, quota_80, quota_100,
subscription_activated, subscription_expiring]
title: { type: string }
project_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
link: { type: [string, 'null'], description: '前端路由,如 /projects/<id>/results' }
read_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
created_at: { $ref: '#/components/schemas/Timestamp' }
Event:
type: object
required: [type, at]
properties:
type:
type: string
enum: [job.progress, job.finished, report.ready, notification.created, quota.changed, project.changed, resync]
at: { $ref: '#/components/schemas/Timestamp' }
project_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
job:
type: [object, 'null']
properties:
kind: { type: string, enum: [parse_job, check_run, report_job] }
id: { $ref: '#/components/schemas/Id' }
status: { $ref: '#/components/schemas/JobStatus' }
stage: { type: [string, 'null'] }
progress: { type: integer }
notification_id:
oneOf:
- $ref: '#/components/schemas/Id'
- type: 'null'
FeedbackCreate:
type: object
required: [project_id, object_type, kind]
properties:
project_id: { $ref: '#/components/schemas/Id' }
object_type: { type: string, enum: [finding, checklist_item, checklist] }
object_id: { $ref: '#/components/schemas/Id' }
kind: { type: string, enum: [false_positive, false_negative] }
note: { type: string, maxLength: 1000 }