- 待确认记原因码(extract.FAILURES,存在 ai_values.failures),解析结果按原因计数 (verify_failures);前端待确认条目显示具体原因 - 相邻分块重叠约 800 字;模型输出被截断或不是合法 JSON 时对半重跑(最多 3 层), 拆到底仍不完整记 output_incomplete。修复:以前截断时整块条目被当成「没有结果」悄悄丢掉 - 项目基本信息提示词 v2:12 个字段逐项表态 found / ambiguous / not_found,跨分块合并为 found / absent / needs_review(info_fields) - 「____元」「××」「( )」「待填」等占位符不算取值 - 提示词 substantive v4,qualification / scoring / schedule / tech_param v3:每条禁止事项 对应一个原因码,对照表在 prompts/README.md - 顾问(Fable)深读参考项目的结论补进 ref-bid-engine-analysis.md §5;架构 §13 SK12 - 待确认:国动 4 → 2,S02 13 → 3,S06 57 → 55 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2921 lines
106 KiB
YAML
2921 lines
106 KiB
YAML
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 }
|
||
verify_failures:
|
||
type: object
|
||
description: 没通过校验的原因码和条数(如 quote_too_short、value_unverified),评测和排查用
|
||
additionalProperties: { type: integer }
|
||
chunks_split: { type: integer, description: 模型输出被截断、把分块对半重跑的次数 }
|
||
output_incomplete: { type: integer, description: 拆到底输出仍不完整的分块数(这些分块的结果可能不全) }
|
||
info_fields:
|
||
type: object
|
||
description: 项目基本信息各字段的结论:found / absent(文件本身没写)/ needs_review(抽取有问题,要人核对)
|
||
additionalProperties: { type: string, enum: [found, absent, needs_review] }
|
||
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 }
|