feat: 读标可以走完一份文件;审计待确认区,修规则缺陷、提示词出新版本
读标操作(FR-M2-07、08) - 后端:清单项详情(ETag)、修改(If-Match 选填,记 edited_fields 和 AI 原值)、 采纳(补出处或声明「人工确认、无出处」,可带修改)、删除 / 丢弃与恢复、人工新增、 替换出处、人工建出处(选同一页的句子)、修改记录(来自审计)、确认清单(不可变版本, 内容没变复用上一版)、版本列表;正式清单有变化时已确认的清单回到草稿;项目已递交只读 - 重新解析不推翻人工决定:丢弃的不再冒出来,采纳的留在正式清单,人工补过的出处不被覆盖 - 前端:待确认区采纳 / 补上出处后采纳 / 修改后采纳 / 丢弃,正式清单修改、补出处、删除 (可撤销)、修改记录、新增条目、确认清单;pdf.js 原文查看器(点出处跳页、按坐标框高亮、 选句补出处),整屏对照布局,J / K 上下条;跨页的一句话合成一处出处显示 待确认审计(架构 §13 SK11) - 规则只修真正的缺陷:表格框线不算下划线(文字层 v3);句号后的右括号并进同一句 (切句 s3-v3);中文编号小节和冒号编号条目的上级条款,依据沿上级链核对;取值认中文 数字、同一行单元格和评分因素标题;补几种无效写法;比对引文前去掉我们加进输入的标记 - 不放宽:引文最短 8 个字、编号必须对、证据全部通过 - 提示词新版本:substantive v3,qualification / scoring / schedule / tech_param v2; 引文复制整句、并列多项不拆、跨页只引上一行,除实质性条款外输入改成纯文本 - BD_AI_MODELS 按提示词声明的能力选模型;离线评测工具 evals/run_extract.py - 待确认:国动 46 → 4,S02 17 → 13,S06 174 → 57 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
@@ -12,14 +13,15 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from bid_doctor.audit import service as audit
|
||||
from bid_doctor.billing import service as billing
|
||||
from bid_doctor.core import keys, queue
|
||||
from bid_doctor.core.crypto import seal_object, sha256
|
||||
from bid_doctor.core.crypto import open_object, seal_object, sha256
|
||||
from bid_doctor.core.db import TenantContext
|
||||
from bid_doctor.core.errors import AppError
|
||||
from bid_doctor.core.ids import uuid7
|
||||
from bid_doctor.core.storage import project_prefix, storage
|
||||
from bid_doctor.document import uploads
|
||||
from bid_doctor.document import citations, uploads
|
||||
from bid_doctor.document.citations import document_label
|
||||
from bid_doctor.document.models import ParseJob, SourceDocument
|
||||
from bid_doctor.document.models import Citation, ParseJob, SourceDocument
|
||||
from bid_doctor.document.pipeline.text_layer import Page
|
||||
from bid_doctor.project import service as projects
|
||||
|
||||
PARSABLE = {"tender", "addendum", "clarification"}
|
||||
@@ -227,3 +229,27 @@ async def summaries(s: AsyncSession, project_ids: list[UUID]) -> dict[UUID, dict
|
||||
).scalars():
|
||||
out[job.project_id]["job"] = {"kind": "parse_job", "id": job.id, "progress": job.progress, "label": "解析中"}
|
||||
return out
|
||||
|
||||
|
||||
async def text_layer(s: AsyncSession, ctx: TenantContext, doc: SourceDocument) -> list[dict[str, Any]]:
|
||||
"""文字层(逐页的句子和坐标框),加密存在对象存储里(DM1)。"""
|
||||
if not doc.text_layer_key:
|
||||
raise AppError("document_not_ready")
|
||||
dk = await keys.data_key(s, ctx, doc.project_id)
|
||||
return json.loads(open_object(dk, storage().get(doc.text_layer_key), doc.text_layer_key))
|
||||
|
||||
|
||||
async def human_citation(
|
||||
s: AsyncSession, ctx: TenantContext, project_id: UUID, document_id: UUID, sentence_keys: list[str]
|
||||
) -> Citation:
|
||||
"""人工在阅读器里选中句子建出处(api-design §4.4):句子必须在这份文档的同一页上。"""
|
||||
doc = await get(s, document_id)
|
||||
if doc.project_id != project_id:
|
||||
raise AppError("not_found")
|
||||
pages = [Page.from_json(p) for p in await text_layer(s, ctx, doc)]
|
||||
by_key = {x.key: x for p in pages for x in p.sentences}
|
||||
chosen = [by_key[k] for k in dict.fromkeys(sentence_keys) if k in by_key]
|
||||
if len(chosen) != len(set(sentence_keys)) or len({x.page_no for x in chosen}) != 1:
|
||||
raise AppError("sentence_not_found")
|
||||
(c,) = await citations.from_sentences(s, ctx, doc, chosen, "human", [])
|
||||
return c
|
||||
|
||||
Reference in New Issue
Block a user