refactor: Attachment 表关联字段 NS 更名为 SN

数据库侧已将 Common.Attachment 的 NS 列重命名为 SN,同步更新代码:
- db.py: upsert_attachments 的 DELETE/INSERT 改用 [SN],distinct_sn 变量与
  相关注释、日志文案统一更新
- write_attachments.py: 模块 docstring 与 convert_results_to_rows 的行列说明
  由 (NS, ...) 改为 (SN, ...);写入目标描述 Common.Attachment.SN
- README.md: 落库约定与字段表中 NS 全部改为 SN

已用 --sn 26B10 实测写入并用 SELECT [SN] 验证数据正确落库。

Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
This commit is contained in:
Misaka_Company
2026-07-24 16:14:05 +08:00
parent ee566e3fbd
commit fd047e2143
3 changed files with 19 additions and 19 deletions

View File

@@ -270,16 +270,16 @@ python write_attachments.py --mode fine --enable-other
落库约定(与 `db.py` 常量、`write_attachments.py` 的转换逻辑保持一致):
- **有附件**`has_attachment=true``status=ok`):每个 `type` 写一行。
- `fine` 模式:`type` 形如 `大类:细分`,按首个冒号拆分为 `(NS, 大类, 细分)`。
- `coarse` 模式:`type` 是大类,小类统一填占位值 `` → `(NS, 大类, '无')`。
- `fine` 模式:`type` 形如 `大类:细分`,按首个冒号拆分为 `(SN, 大类, 细分)`。
- `coarse` 模式:`type` 是大类,小类统一填占位值 `` → `(SN, 大类, '无')`。
- **无附件**`has_attachment=false`,含 `empty_param` 与模型判无附件):写哨兵行
`(NS, '无附件', '无')`,下游用 `WHERE MajorCategory <> '无附件'` 取真实附件。
`(SN, '无附件', '无')`,下游用 `WHERE MajorCategory <> '无附件'` 取真实附件。
- **无法确定/失败**`has_attachment=null`,含 `not_found` / `llm_*_error` /
`db_error`):一律不写,既不当作无附件,也不留脏数据。
- **幂等**:写入时对同一总排号先删除旧行再插入本次结果(依赖 `NS, MajorCategory,
- **幂等**:写入时对同一总排号先删除旧行再插入本次结果(依赖 `SN, MajorCategory,
MinorCategory` 唯一索引),重跑安全。
目标表 `Common.Attachment` 字段:`NS`nvarchar(30),总排号/关联键)、
目标表 `Common.Attachment` 字段:`SN`nvarchar(30),总排号/关联键)、
`MajorCategory`nvarchar(40),附件大类)、`MinorCategory`nvarchar(40),附件小类),
三者均 `NOT NULL`。

22
db.py
View File

@@ -133,7 +133,7 @@ def fetch_param_by_id(
# 附件分类落库(目标表 Common.Attachment与源表 productionContractData 分属不同 schema
# ---------------------------------------------------------------------------
# 落库约定(与项目记忆 MEMORY.md 中写入约定一致,请勿在此处用字面量以外的值):
# - 无附件has_attachment=false写哨兵行 (NS, '无附件', '无')
# - 无附件has_attachment=false写哨兵行 (SN, '无附件', '无')
# - 无小类的行coarse 真实附件 / 哨兵行MinorCategory 统一填 '无'
ATTACHMENT_SCHEMA = "Common"
ATTACHMENT_TABLE = "Attachment"
@@ -175,9 +175,9 @@ def upsert_attachments(
) -> tuple[int, int]:
"""幂等写入 Common.Attachment。
rows: 本次要写入的 (NS, MajorCategory, MinorCategory) 列表。
对出现的每个 NS 先 DELETE 其旧行,再批量 INSERT——保证重跑总是反映
最新分类结果,不会因唯一索引 (NS, 大类, 小类) 冲突而失败。
rows: 本次要写入的 (SN, MajorCategory, MinorCategory) 列表。
对出现的每个 SN 先 DELETE 其旧行,再批量 INSERT——保证重跑总是反映
最新分类结果,不会因唯一索引 (SN, 大类, 小类) 冲突而失败。
返回 (deleted_rows, inserted_rows) 计数。
"""
@@ -185,30 +185,30 @@ def upsert_attachments(
return (0, 0)
qualified = f"[{ATTACHMENT_SCHEMA}].[{ATTACHMENT_TABLE}]"
distinct_ns = sorted({r[0] for r in rows})
distinct_sn = sorted({r[0] for r in rows})
conn_str = _build_conn_str(db_cfg)
try:
with pyodbc.connect(conn_str, timeout=db_cfg["connect_timeout"]) as conn:
conn.timeout = db_cfg["query_timeout"]
cursor = conn.cursor()
# 1) 删除本批所有 NS 的旧行
del_ph = ",".join("?" for _ in distinct_ns)
# 1) 删除本批所有 SN 的旧行
del_ph = ",".join("?" for _ in distinct_sn)
cursor.execute(
f"DELETE FROM {qualified} WHERE [NS] IN ({del_ph})", distinct_ns
f"DELETE FROM {qualified} WHERE [SN] IN ({del_ph})", distinct_sn
)
deleted = cursor.rowcount
# 2) 插入新行
cursor.executemany(
f"INSERT INTO {qualified} ([NS], [MajorCategory], [MinorCategory]) "
f"INSERT INTO {qualified} ([SN], [MajorCategory], [MinorCategory]) "
f"VALUES (?, ?, ?)",
rows,
)
inserted = len(rows)
conn.commit()
logger.info(
"upsert_attachments: 删除 %d 行, 插入 %d 行, 涉及 %dNS",
deleted, inserted, len(distinct_ns),
"upsert_attachments: 删除 %d 行, 插入 %d 行, 涉及 %d 个 SN",
deleted, inserted, len(distinct_sn),
)
return (deleted, inserted)
except pyodbc.Error as e:

View File

@@ -6,7 +6,7 @@
- has_attachment=true (status=ok):
每个 type 一行。fine 模式 type 形如 "大类:细分",按首个冒号拆成 (大类, 小类)
coarse 模式 type 是大类,小类统一填 ''
- has_attachment=false (无附件): 写哨兵行 (NS, '无附件', '')。
- has_attachment=false (无附件): 写哨兵行 (SN, '无附件', '')。
- has_attachment=null (not_found / llm_error / db_error): 不写(属"无法确定/失败")。
用法:
@@ -25,7 +25,7 @@
--id 数据库真实 ID 列(id_field),如 802
--sn 总排号列(id_column),如 26B742即原先 --id 的语义)
--ids-file 每行一个总排号(键类型 sn)
无论用哪种方式,结果均按回查到的总排号写入 Common.Attachment.NS。
无论用哪种方式,结果均按回查到的总排号写入 Common.Attachment.SN
"""
from __future__ import annotations
@@ -54,7 +54,7 @@ def convert_results_to_rows(results: list[dict], mode: str) -> tuple[list[tuple[
"""将 classify_batch 的结果列表转成可写入 Common.Attachment 的行。
返回 (rows, skipped)
rows - list[(NS, MajorCategory, MinorCategory)]
rows - list[(SN, MajorCategory, MinorCategory)]
skipped- list[总排号]has_attachment 为 Nonenot_found/失败)未写入的
"""
rows: list[tuple[str, str, str]] = []