docs(architecture): 接口契约 v0.1,确认 API1–API5

- openapi.yaml:用户端 MVP 主流程的 OpenAPI 3.1 契约(64 个路径、81 个操作)
- api-design.md:约定、鉴权、页面与接口对应、扣点预览、任务与事件、上传、错误码
- data-model.md:清单项 spec 改为按类别分组

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-03 17:44:28 +08:00
parent 0991d0237c
commit 5747a523bb
6 changed files with 3098 additions and 5 deletions

View File

@@ -156,4 +156,5 @@ samples/
- 2026-11-03:技术架构升到 v0.3,新增 §3.14。团队版、企业版、飞书 / 企业微信登录都是以后的版本,早期不做;MVP 的表结构先按多人、多登录方式建:人 / 登录方式 / 成员身份分三层,项目成员表和统一的权限检查点先建好,以后接入不用重构(AR11、AR12)。 - 2026-11-03:技术架构升到 v0.3,新增 §3.14。团队版、企业版、飞书 / 企业微信登录都是以后的版本,早期不做;MVP 的表结构先按多人、多登录方式建:人 / 登录方式 / 成员身份分三层,项目成员表和统一的权限检查点先建好,以后接入不用重构(AR11、AR12)。
- 2026-11-03:VI 手册第 20 页报告页脚去掉「保证」字样,改为与设计规范 §7.17 一致的「辅助检查结果,仅供投标前自查参考」,PDF 已重出。 - 2026-11-03:VI 手册第 20 页报告页脚去掉「保证」字样,改为与设计规范 §7.17 一致的「辅助检查结果,仅供投标前自查参考」,PDF 已重出。
- 2026-11-03:物理数据模型 v0.1(`docs/architecture/data-model.md` + `schema-draft.sql`,40 张表,PostgreSQL 16 上 16 项行为测试通过)。DM1–DM7 全部按建议确认:句子放对象存储不进库、外键都带租户、每个项目一把数据密钥、手机号不存明文、规则问题可以只挂规则编号、清单新增「报价」类别。 - 2026-11-03:物理数据模型 v0.1(`docs/architecture/data-model.md` + `schema-draft.sql`,40 张表,PostgreSQL 16 上 16 项行为测试通过)。DM1–DM7 全部按建议确认:句子放对象存储不进库、外键都带租户、每个项目一把数据密钥、手机号不存明文、规则问题可以只挂规则编号、清单新增「报价」类别。
- 下一步:接口契约(核心接口的 OpenAPI 定义),然后搭工程骨架,见 `docs/architecture/architecture.md` 第 12 节。 - 2026-11-03:接口契约 v0.1(`docs/architecture/api-design.md` + `openapi.yaml`,OpenAPI 3.1,64 个路径、81 个操作),覆盖 MVP 原型 13 页的主流程,运营后台另行定义。API1–API5 全部按建议确认:骨架搭好前以 `openapi.yaml` 为准,之后以 FastAPI 生成的快照为准;事件流用 fetch 读;异地登录确认后挤掉旧设备;扣点说明和实际不一致时拒绝、不扣点;报告后台生成。
- 下一步:搭工程骨架(仓库结构、CI、Compose 一键启动、「上传 → 解析 → 显示清单」最小链路),见 `docs/architecture/architecture.md` 第 12 节。CI 里补上 OpenAPI 的完整校验(Redocly 或 Spectral)。

View File

@@ -0,0 +1,181 @@
# 接口契约 v0.1
> 依据:架构 v0.3(§3.4 任务、§3.11 会话、§4.2 前端、§5.2 桌面约束)、`data-model.md`、03 功能需求、MVP 原型 v0.2 的 13 个页面。
> 契约文件:`openapi.yaml`(OpenAPI 3.1,64 个路径、81 个操作、81 个数据结构)。
> 范围:用户端(网页版 / 桌面版)的 MVP 主流程。运营后台是独立应用,接口另行定义。
> API1–API5 已于 2026-11-03 全部按建议确认(`docs/decisions.md`),见 §9。
## 0. 结论速览
- **一份契约,两端同时开发**:前端用 openapi-typescript 从 `openapi.yaml` 生成类型,用 mock 服务器先跑;后端按它写 Pydantic 模型。后端骨架搭好后,改为以 FastAPI 生成的文档为准(API1)。
- **两种鉴权,同一套接口**:网页版用 cookie,并加 CSRF 头;桌面版用 Bearer 令牌(架构 §5.2 第 3 条)。
- **错误统一用 RFC 9457 格式**,带一个稳定的 `code`,前端按 `code` 判断,不解析文案(§5)。
- **会扣点的接口都要带「预期扣点」**:前端把告诉用户的扣点数传上来,服务端算的和它不一致就拒绝,不扣点(API4)。这样用户看到的扣点说明和实际扣点永远一致(红线 9)。
- **进度用事件流推送,轮询兜底**:事件只是「有变化」的提示,以资源的 GET 结果为准。事件流用 fetch 读取,不用浏览器的 EventSource,因为 EventSource 带不了 Bearer 头(API2)。
- **大文件分片上传、断点续传**,和平台无关(架构 §5.2 第 7 条)。格式、加密、页数在上传完成时就拦截,不建任务、不扣点(FR-M2-01)。
## 1. 基本约定
| 事项 | 约定 |
|---|---|
| 地址 | `{服务器}/api/v1`。网页版同源;桌面版用用户选择的服务器地址 |
| 格式 | JSON,字段名用 snake_case(和数据库、Python 一致);生成的 TypeScript 类型也是 snake_case,不做转换 |
| 编号 | UUID 字符串 |
| 时间 | RFC 3339,带时区。显示格式(「11月20日 09:30」)由前端处理(Q10) |
| 金额 | 字符串,如 `"1280000.00"`,单位元,避免浮点误差 |
| 枚举 | 英文小写加下划线,值和数据库的 CHECK 约束一致;中文显示名由前端维护 |
| 列表 | 一个项目内的列表(清单项、问题、文档、分包)一次返回全部,最多几百条;跨项目、会一直增长的列表(项目、额度流水、通知)用游标分页:`?cursor=&limit=`,返回 `next_cursor` |
| 动作 | 不是增删改的操作,用 `POST /资源/{id}/动作`,例如 `/findings/{id}/resolve`、`/parse-jobs/{id}/cancel` |
| 版本 | 路径里的 `v1`。同一版本内只加字段、不删不改;前端忽略不认识的字段和枚举值 |
## 2. 鉴权与会话
| | 网页版 | 桌面版 |
|---|---|---|
| 登录后 | 服务端设置 cookie `bd_session`(HttpOnly、Secure、SameSite=Lax)和 `bd_csrf`(前端可读) | 响应体里返回 `token`,只返回这一次;存进系统钥匙串 |
| 每个请求 | 自动带 cookie;写操作(POST、PUT、PATCH、DELETE)还要带请求头 `X-CSRF-Token`,值等于 `bd_csrf` | `Authorization: Bearer <token>` |
| 退出 | `POST /auth/logout` 吊销会话 | 同左 |
- 登录、注册都用 `client_kind` 声明是哪一端。
- **异地多设备并发登录**(FR-M6-04):同一账号已有其他设备在线时,登录返回 409 `concurrent_session`,附在线设备列表(设备、IP 归属地、最近活跃时间);用户确认后带 `force: true` 重发,其他设备被挤下线(API3)。被挤下线的设备下一个请求收到 401 `session_revoked`,`reason: concurrent_login`,前端提示「你的账号在其他设备登录」。
- **发验证码不泄露注册状态**:号码有没有注册,`POST /auth/sms-codes` 都返回 202。验证码校验通过后,登录接口才会告诉用户「这个号码还没注册」。
- **权限点**:`GET /me` 返回当前用户的权限点列表(AR12),前端只用它决定按钮显示与否,服务端每个接口照样检查。MVP 只有所有者,拥有全部权限点。
## 3. 主流程和接口的对应
| 步骤(原型页面) | 接口 |
|---|---|
| 注册 / 登录(login) | `POST /auth/sms-codes` → `POST /auth/register` 或 `POST /auth/login` → `GET /me` |
| 工作台(workbench) | `GET /projects`,每行带「下一步」`next_action`、就绪状态、正在跑的任务;`GET /quota` 给顶栏额度 |
| 新建项目、上传招标文件(project-new) | `POST /projects` → `POST /uploads` → `PUT /uploads/{id}/parts/{n}` × N → `POST /uploads/{id}/complete` → `POST /projects/{id}/documents` → `POST /documents/{id}/parse-jobs`(预扣 1 次) |
| 解析进度(parsing) | `GET /events` 收 `job.progress`;断线时轮询 `GET /parse-jobs/{id}`;`POST /parse-jobs/{id}/cancel`;失败后重试再调一次 `POST /documents/{id}/parse-jobs` |
| 选包(lots) | `GET /projects/{id}/lots` → `PUT /projects/{id}/bidding-lots`;手工增删改分包 |
| 读标(checklist) | `GET /projects/{id}/checklist`、`GET /projects/{id}/checklist/items`;原文查看器用 `GET /documents/{id}/file?variant=normalized`、`GET /documents/{id}/pages`;选句补出处用 `GET .../pages/{n}/sentences` → `POST /projects/{id}/citations`;修改、采纳、删除、恢复、补遗更新、修改记录、确认清单 |
| 上传投标文件、发起检查(bid-check) | 上传同上,`role = bid_volume`;`GET /projects/{id}/check-preview` 给出扣点说明 → `POST /projects/{id}/check-runs` |
| 查标(results) | `GET /check-runs/{id}`、`GET /check-runs/{id}/findings` → `POST /findings/{id}/resolve`、`/reopen`(U 键)、`POST /check-runs/{id}/findings/bulk-resolve`;需人工核对用 `GET/PUT /projects/{id}/manual-checks`;`POST /feedback` |
| 报告(report) | `POST /check-runs/{id}/reports` → 事件 `report.ready` → `GET /reports/{id}/file` |
| 项目概览(project) | `GET /projects/{id}`、基本信息字段的修改和补遗确认、`GET /projects/{id}/documents`、`GET /projects/{id}/check-runs`、`POST /projects/{id}/transitions`(递交 / 归档及撤销)、`DELETE /projects/{id}` |
| 额度(quota) | `GET /quota`、`GET /quota/entries`、`GET /plans`、`POST /activation-requests` |
| 资质库(qualifications) | `/qualifications` 的增删改;附件先上传,再 `POST /qualifications/{id}/files` |
## 4. 几个关键设计
### 4.1 扣点:预览 + 预期扣点
- 发起检查前,前端调用 `GET /projects/{id}/check-preview`。服务端按 01 §4 和 P1 算出这次的计费方式(扣点 / 24 小时内免扣 / 免费版)、扣几次、免扣窗口到什么时候,以及能不能开始(没有招标文件、额度用完等)。按钮旁的扣点说明就用这份数据。
- 发起时带 `expected_charge`。如果服务端当下算的和它不一致(比如用户停在页面上时,24 小时窗口刚好过了),就返回 409 `billing_changed`,附最新的预览,**不扣点**。前端刷新说明,让用户再点一次。
- 解析同理:`POST /documents/{id}/parse-jobs` 也接受 `expected_charge`。
- 重复点击不会重复扣点:同一份文档、同一个项目同时只能有一个在跑的任务(数据库部分唯一索引),重复发起返回 409 `job_already_running`,附正在跑的任务编号。
### 4.2 任务与事件
- 解析任务、检查批次、报告生成都是后台任务:发起接口立刻返回任务对象(`status = queued`)。
- `GET /events` 是当前用户的事件流:`job.progress`、`job.finished`、`report.ready`、`notification.created`、`quota.changed`、`project.changed`。
- 每条事件带 `id`。重连时带上 `Last-Event-ID`,服务端尽量补发;补不了(Valkey 里的缓冲已过期)就发 `resync`,前端重新拉取当前页面的数据。
- 事件可能丢、可能重复。前端收到后只做一件事:让 TanStack Query 里对应的查询失效、重新请求。
- 断线兜底:页面上有进行中的任务时,每 5 秒轮询一次任务的 GET。
- 失败原因 `error` 只给用户看的文字和 `refunded`(是否已退回额度),不含文档内容和内部堆栈。
### 4.3 上传
1. `POST /uploads`:传文件名、大小和用途(项目文件或资质附件),可选传 sha256。超出套餐的单文件大小上限,直接返回 409,`detail` 写明上限值。
2. `PUT /uploads/{id}/parts/{n}`:逐片上传,片大小由服务端决定。重复上传同一片会覆盖,所以断点续传时可以先 `GET /uploads/{id}` 看收到了哪些片。
3. `POST /uploads/{id}/complete`:合并分片,按文件头识别真实格式,读取页数。不支持的格式、加密、损坏、电子投标平台的加密格式、页数超限,都在这里返回 422。扩展名和真实格式不一致时只做提示(`extension_mismatch`,07 F2)。
4. 挂到项目上:`POST /projects/{id}/documents`。投标分册重新上传时填 `replaces_document_id`,生成新版本。
上传会话 24 小时后过期,没挂到项目上的文件自动清理。分片经过 api 写入对象存储;本地磁盘和 S3 用同一套接口,私有化环境也一样。
### 4.4 原文和出处
- 原文用 pdf.js 渲染:`GET /documents/{id}/file?variant=normalized` 返回短时有效的下载地址,pdf.js 直接加载,不需要带鉴权头(架构 §5.2 第 5 条)。
- 出处对象自带原句、页码和坐标框,阅读器直接画高亮,不用再取文字层。`normalized_sha256` 和文档的哈希不一致时不画(data-model §5.1)。
- 人工补出处:在阅读器里选中句子(`GET /documents/{id}/pages/{n}/sentences`),再 `POST /projects/{id}/citations` 建一条出处,最后把它挂到清单项上(采纳或替换出处)。
### 4.5 修改清单和处理问题
- **并发修改**:清单项和项目字段的 GET 返回 `ETag`。修改时带上 `If-Match`,版本不一致返回 412。MVP 只有一个人,这个头是选填的;团队版上线后改为必填。
- **改清单项**:`PATCH` 只传要改的字段。服务端把改过的字段记进 `edited_fields`,AI 原值保留在 `ai_values` 里。已确认的清单被修改后回到草稿(FR-M2-08)。
- **处理问题**:
- `resolve` 返回问题本身和项目最新的就绪状态(`open_fatal_count`),项目头直接更新,不用再请求一次;
- 忽略必须写原因,致命问题「确认无误」也必须写说明(U4),否则返回 422 `note_required`;
- 只有最近一次检查的问题可以处理,历史批次返回 409 `run_superseded`;
- 批量处理只限「提示」级,混进了别的级别就整批拒绝,返回不符合的问题编号。
- **已递交、已归档的项目只读**:所有修改接口都返回 409 `project_read_only`。撤销递交或撤销归档之后才能改(FR-M6-02)。
### 4.6 不在接口里出现的东西
对照产品红线:
- 没有任何接口返回或接收「建议报价」。报价相关的字段只有招标文件写明的限价,以及从投标文件里读到的金额(红线 6)。
- 问题、报告的字段里没有「通过 / 合格」这类结论字段;就绪状态只用于提示,不阻止导出和递交(红线 3、4)。
- 没有用户可以填的模型地址或模型选择(T1)。
## 5. 错误
所有错误都用 `application/problem+json`:
```json
{
"type": "about:blank",
"title": "额度不足",
"status": 409,
"code": "quota_exhausted",
"detail": "本期解析额度已用完。专业版续费或加量请联系客服。",
"request_id": "req_01J…"
}
```
- `title` 和 `detail` 是给用户看的中文;前端按 `code` 决定怎么处理,不解析文案。
- 字段校验错误:422 `validation_failed`,`errors` 里逐个字段列出 `field`(如 `spec.tech_param.value`)、`code`、`message`。
- 访问别的企业的资源一律返回 404,不返回 403(架构 §3.3)。
- `request_id` 也放在响应头 `X-Request-ID` 里,用户反馈问题时可以报这个编号。
| HTTP | code | 含义 |
|---|---|---|
| 401 | `unauthenticated` `session_expired` `session_revoked` | 没登录、会话过期、被吊销(含被挤下线) |
| 403 | `csrf_failed` `forbidden` | CSRF 头不对;没有这个权限点 |
| 404 | `not_found` `phone_not_registered` | |
| 409 | `concurrent_session` | 已有其他设备在线(API3) |
| 409 | `quota_exhausted` `billing_changed` `plan_limit_exceeded` | 额度、计费、套餐限制 |
| 409 | `job_already_running` `job_finished` | 任务状态冲突 |
| 409 | `project_read_only` `invalid_transition` `run_superseded` `item_not_pending` `document_in_use` 等 | 业务状态不允许这个操作 |
| 412 | `precondition_failed` | If-Match 版本不一致 |
| 422 | `validation_failed` `note_required` `citation_required` `bulk_only_hint` | 输入不合法 |
| 422 | `file_format_unsupported` `file_encrypted` `file_corrupted` `bid_file_platform_encrypted` `upload_checksum_mismatch` | 上传的文件有问题,不扣点 |
| 429 | `rate_limited` | 验证码、登录太频繁;响应头带 Retry-After |
| 500 / 503 | `internal_error` `service_unavailable` | 不扣点;任务类的会自动退回 |
完整的错误码清单在每个接口的响应说明里;写后端时收进 `core/errors.py`,作为唯一的定义来源。
## 6. 契约里没写死、留给实现的
- 分片大小(建议 8MB)、上传会话的过期时间、下载地址的有效期(建议 5 分钟);
- 列表的默认排序以外的排序方式;
- 事件缓冲多长时间(建议 10 分钟);
- `next_action` 的具体推导规则,写在 project 模块里,和原型工作台的「下一步」一致。
## 7. 以后的版本怎么接
| 以后要加的 | 接口上的准备 |
|---|---|
| 团队版:项目成员、角色 | `ProjectDetail.members` 已经有了;以后加 `/projects/{id}/members` 的增删改,`If-Match` 改为必填 |
| 一人多企业、切换企业 | `POST /auth/switch-tenant` 换一个绑定到另一个成员身份的会话(§3.14) |
| 飞书、企业微信、SSO 登录 | `/auth/{provider}/start` 和 `/auth/{provider}/callback`,登录结果的格式不变 |
| 线上支付 | `/orders` 一组新接口,不改额度接口 |
| 标书生成(M4)、查重(M5) | 新的任务类型,复用任务、事件、扣点预览这套模式 |
## 8. 怎么用这份契约
- **前端**:`packages/api-client` 用 openapi-typescript 从 `openapi.yaml` 生成类型,用 openapi-fetch 调用。后端还没好时,用基于契约的 mock 服务器(如 Prism)开发页面。
- **后端**:按契约写 Pydantic 模型和路由。集成测试里比对 FastAPI 生成的文档和契约,有差异就失败。
- **契约检查**:现在只做了结构检查(引用都能解析、operationId 不重复、路径参数都声明了、必填字段都存在)。工程骨架里在 CI 加上 Redocly 或 Spectral 的完整校验。
## 9. 决策(已确认)
| # | 事项 | 结论 |
|---|---|---|
| API1 | 契约以谁为准 | **现在以 `openapi.yaml` 为准**,前后端据此同时开发。后端骨架搭好后,改为以 FastAPI 生成的文档为准:把生成结果存成 `openapi.json` 提交到仓库,CI 检查它是不是最新的;契约有改动时在合并请求里就能看到。之后 `openapi.yaml` 不再维护,和 `schema-draft.sql` 的处理方式一样 |
| API2 | 事件流怎么读 | 用 fetch 读 SSE 流,不用 EventSource。理由:EventSource 带不了 `Authorization` 头,桌面版用不了 |
| API3 | 异地多设备并发登录怎么「提示并限制」 | **新设备登录时提示「账号已在其他设备登录」,用户确认后挤掉旧设备**。另一种做法是旧设备不退出就不让新设备登录,但用户换了电脑、忘了退出时就登不上了 |
| API4 | 扣点说明和实际扣点不一致时 | **拒绝并刷新说明,不扣点**(§4.1)。用户看到「不扣点」,结果却扣了,这种情况必须杜绝 |
| API5 | 报告怎么生成 | **后台生成**,完成后推送事件。用 Chromium 生成 PDF 要几秒到十几秒,不放在请求里同步做(架构:api 只做短事务) |

View File

@@ -1,7 +1,7 @@
# 技术架构设计 v0.3 # 技术架构设计 v0.3
> 依据:PRD v0.9.1 第 7 章(非功能需求)、`docs/analysis/`(功能需求、领域模型、样本结论)、`docs/decisions.md`(T1 及各阶段决策)、参考项目分析 `ref-bid-engine-analysis.md`。 > 依据:PRD v0.9.1 第 7 章(非功能需求)、`docs/analysis/`(功能需求、领域模型、样本结论)、`docs/decisions.md`(T1 及各阶段决策)、参考项目分析 `ref-bid-engine-analysis.md`。
> 状态:v0.3。v0.2 的选型已全部确认;v0.3 新增 §3.14:MVP 的表结构先按多人、多登录方式建,给以后的团队版、企业版、飞书 / 企业微信登录留结构(AR11、AR12,已确认)。物理数据模型见 `data-model.md`(DM1–DM7,已确认)。 > 状态:v0.3。v0.2 的选型已全部确认;v0.3 新增 §3.14:MVP 的表结构先按多人、多登录方式建,给以后的团队版、企业版、飞书 / 企业微信登录留结构(AR11、AR12,已确认)。物理数据模型见 `data-model.md`(DM1–DM7,已确认);接口契约见 `api-design.md` + `openapi.yaml`(API1–API5,已确认)。
> - 已确认(2026-11-03):T2–T5;AR1、AR3、AR5–AR9 按建议;AR2 改为 Vue 3 + Vite。 > - 已确认(2026-11-03):T2–T5;AR1、AR3、AR5–AR9 按建议;AR2 改为 Vue 3 + Vite。
> - 已确认:AR4 修订后的建议(引入 Valkey,只存可以丢的数据,§6.1);AR10 不引入带样式的 UI 框架(§4.3)。至此选型全部确认。 > - 已确认:AR4 修订后的建议(引入 Valkey,只存可以丢的数据,§6.1);AR10 不引入带样式的 UI 框架(§4.3)。至此选型全部确认。
> - 确认后写入 `docs/decisions.md`;构建、运行、测试命令等工程骨架搭好后回填到 AGENTS.md。 > - 确认后写入 `docs/decisions.md`;构建、运行、测试命令等工程骨架搭好后回填到 AGENTS.md。
@@ -838,5 +838,5 @@ S1–S4 风险最高,建议先做;S8(桌面外壳 PoC)等桌面版立项
1. 做技术验证 S1–S4,结论写进本文件,并据此确定 FR-M2-05、FR-M2-06 的目标值。 1. 做技术验证 S1–S4,结论写进本文件,并据此确定 FR-M2-05、FR-M2-06 的目标值。
2. ~~物理数据建模~~:✅ 已完成,见 `data-model.md`、`schema-draft.sql`(DM1–DM7 已确认)。 2. ~~物理数据建模~~:✅ 已完成,见 `data-model.md`、`schema-draft.sql`(DM1–DM7 已确认)。
3. 定接口契约:先写核心接口的 OpenAPI 定义,前后端据此并行开发。 3. ~~定接口契约~~:✅ 已完成,见 `api-design.md`、`openapi.yaml`(API1–API5 已确认)。
4. 搭工程骨架:仓库结构、CI、Compose 一键启动、一个贯通「上传 → 解析 → 显示清单」的最小链路。 4. 搭工程骨架:仓库结构、CI、Compose 一键启动、一个贯通「上传 → 解析 → 显示清单」的最小链路。

View File

@@ -189,7 +189,7 @@ erDiagram
| `edited_fields` | 人工改过哪些字段。重新解析对齐时,这些字段不动 | | `edited_fields` | 人工改过哪些字段。重新解析对齐时,这些字段不动 |
- **类别**:`categories` 可以有多个标签。CHECK 约束保证 ★ 条款一定带「无效 / 废标」标签(FR-M2-05)。 - **类别**:`categories` 可以有多个标签。CHECK 约束保证 ★ 条款一定带「无效 / 废标」标签(FR-M2-05)。
- **类别特有字段**(05 §3.3)放在 `spec` 里,按类别用 Pydantic 校验。例如技术参数是 `{"param": "内存", "op": ">=", "value": 32, "unit": "GB", "device": "服务器"}`。规则引擎要用的字段都放在这里,常用的再提出来建索引。 - **类别特有字段**(05 §3.3)放在 `spec` 里,**按类别分组**:一条清单项有几个类别,就可以有几组,每组用对应的 Pydantic 模型校验。例如 ★ 技术参数是 `{"tech_param": {"param": "内存", "op": ">=", "value": "32", "unit": "GB", "device": "服务器"}, "invalidity": {"source": "substantive_marker"}}`。规则引擎要用的字段都放在这里,常用的再提出来建索引。完整定义见 `openapi.yaml` 的 `ItemSpec`。
- **重要性依据**:`importance_basis` 记下凭什么认定为 ★,可以是标注符号、法规列明或原文写明。`marking_rule_id` 指向文件自己的标注规则(07 F1)。 - **重要性依据**:`importance_basis` 记下凭什么认定为 ★,可以是标注符号、法规列明或原文写明。`marking_rule_id` 指向文件自己的标注规则(07 F1)。
- **区域**:`zone` 分待确认区和正式清单。进待确认区的原因写在 `pending_reason`:没有出处、置信度低、校验没通过、标注句没被覆盖(规则兜底生成的)、重新解析时没再出现。待确认区的条目不参与检查(FR-M2-07)。 - **区域**:`zone` 分待确认区和正式清单。进待确认区的原因写在 `pending_reason`:没有出处、置信度低、校验没通过、标注句没被覆盖(规则兜底生成的)、重新解析时没再出现。待确认区的条目不参与检查(FR-M2-07)。
- **补遗**: - **补遗**:
@@ -207,7 +207,7 @@ erDiagram
|---|---| |---|---|
| 总价限价 | `project_field`,字段 `ceiling_total` | | 总价限价 | `project_field`,字段 `ceiling_total` |
| 采购包限价 | `lot.ceiling_amount` | | 采购包限价 | `lot.ceiling_amount` |
| 分项限价、单价上限 | 清单项,类别是「报价」(DM7),`spec = {"kind": "price_ceiling", "target": "<分项名>", "op": "<=", "value": "..."}` | | 分项限价、单价上限 | 清单项,类别是「报价」(DM7),`spec = {"price": {"kind": "item_ceiling", "target": "<分项名>", "op": "<=", "value": "..."}}` |
报价形式检查读投标文件里的金额,和这三层比对。读到的金额写在问题的 `detail` 里,同时保留原文写法和出处。 报价形式检查读投标文件里的金额,和这三层比对。读到的金额写在问题的 `detail` 里,同时保留原文写法和出处。

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,20 @@
--- ---
## API1–API5 · 2026-11-03 · 接口契约的取舍:全部按建议
依据 `docs/architecture/api-design.md` §9;契约文件 `openapi.yaml`。
| # | 决策 | 理由 |
|---|---|---|
| API1 | 后端骨架搭好前以 `openapi.yaml` 为准,前后端据此同时开发;之后以 FastAPI 生成的文档为准,存成 `openapi.json` 提交,CI 检查是否最新,`openapi.yaml` 不再维护 | 避免两份契约长期并存、互相偏离;契约改动在合并请求里可见 |
| API2 | 事件流(SSE)用 fetch 读取,不用 EventSource | EventSource 带不了 Bearer 头,桌面版用不了 |
| API3 | 同一账号在其他设备已登录时,新设备登录先提示,用户确认后挤掉旧设备 | 落实 FR-M6-04「提示并限制」;「旧设备不退出就不让登录」会让换了电脑的用户登不上 |
| API4 | 会扣点的接口都带「预期扣点」,和服务端当下算的不一致就拒绝、不扣点,并返回最新的扣点预览 | 红线 9:用户看到的扣点说明和实际扣点必须一致 |
| API5 | 报告在后台生成,完成后推送事件 | 生成 PDF 要几秒到十几秒;api 只做短事务 |
- **影响**:架构 §12 第 3 步完成;`data-model.md` 的清单项 `spec` 改为按类别分组。
## DM1–DM7 · 2026-11-03 · 物理数据模型的取舍:全部按建议 ## DM1–DM7 · 2026-11-03 · 物理数据模型的取舍:全部按建议
依据 `docs/architecture/data-model.md` §9;建表脚本草案 `schema-draft.sql` 在 PostgreSQL 16 上跑通了 16 项行为测试。 依据 `docs/architecture/data-model.md` §9;建表脚本草案 `schema-draft.sql` 在 PostgreSQL 16 上跑通了 16 项行为测试。