refactor: 引入 SQLAlchemy 多数据库抽象(PostgreSQL + SQL Server)

- 新增 orm.py:按 db_type 构建引擎(postgresql+psycopg2 / mssql+pyodbc),
  声明式 Attachment 模型,init_schema 幂等建表
- 重写 db.py 为 SQLAlchemy Core 实现(动态 Table + quote 跨库正确引用、
  id/sn 双键回查、跨库分页、先删后插幂等),对外签名不变
- 配置:config.yaml 默认 PostgreSQL,新增 config.mssql.yaml 保留 SQL Server,
  config_loader 支持可选 driver 与 db_type
- write_attachments.py 新增 --init-db
- 依赖 requirements.txt 增 sqlalchemy / psycopg2-binary(保留 pyodbc)
- README 对齐:修正目标表字段描述并新增数据库抽象层小节
This commit is contained in:
pengq
2026-07-26 11:59:55 +08:00
parent 70b6fb3767
commit a014a945af
7 changed files with 420 additions and 155 deletions

View File

@@ -1,16 +1,23 @@
# 布莱迪压力表 - 订单附件识别工具
根据"总排号"从 SQL Server 查询"新参数"字段,调用大语言模型判断该订单是否携带
根据"总排号"从数据库查询"新参数"字段,调用大语言模型判断该订单是否携带
附件,并以 JSON 输出结果。支持粗分类(资料/配件/耗材)和精分类("大类:细分
类目",具体到针型阀、说明书等)两种粒度。
> 数据访问层基于 **SQLAlchemy** 抽象,支持 **SQL Servermssql+pyodbc** 与
> **PostgreSQLpostgresql+psycopg2** 两种数据库,通过 `config.database.db_type`
> 切换。源表/目标表的标识符引用由 SQLAlchemy 按方言自动处理PG 用 `"名"`
> MSSQL 用 `[名]`),无需改代码。
## 安装
```bash
pip install -r requirements.txt
```
`pyodbc` 需要系统已安装对应的 ODBC 驱动。本项目 `config.yaml` 中使用的版本为
依赖包含 `sqlalchemy``psycopg2-binary`PostgreSQL 驱动,已自带 libpq
`pyodbc`SQL Server 驱动)。使用 **PostgreSQL** 无需额外系统组件;使用
**SQL Server** 需要系统已安装对应的 ODBC 驱动,本项目 `config.yaml` 中使用的版本为
"ODBC Driver 18 for SQL Server",请按服务器实际安装的驱动版本填写 `database.driver`
若服务器上已有 SQL Server 管理工具/客户端环境,通常已包含该驱动;否则需自行
安装 Microsoft 官方 ODBC Driver。
@@ -20,9 +27,17 @@ pip install -r requirements.txt
编辑 `config.yaml`,填入以下三部分(首次使用需替换为真实值,**请勿将含真实
数据库密码 / API Key 的配置文件提交到版本库**
- `database`SQL Server 连接信息(含 `schema`)、表名、字段名。其中
`id_column` 为**总排号**列(接口 `--sn` 使用),`id_field` 为数据库**真实 ID**
列(接口 `--id` 使用);二者需按源表实际列名填写
- `database`数据库连接信息与源表/字段名。
- `db_type`:数据库类型,`postgresql``mssql`(缺省按 `driver` 是否含
"SQL Server" 推断PostgreSQL 无需 `driver`)。
- `server` / `port` / `database` / `username` / `password`:连接信息(两种库通用)。
- `schema` / `table` / `id_column` / `id_field` / `param_column`:源表的
schema、表名与列名`id_column` 为**总排号**列,对应 `--sn``id_field`
数据库**真实 ID** 列,对应 `--id``param_column` 为**新参数**列)。这些名称
按源表实际填写SQL Server / PostgreSQL 两端保持一致即可。
-`mssql` 需要:`driver``trust_server_certificate`
- 切换到 SQL Server 时可直接用 `--config config.mssql.yaml`(已内置原 SQL Server
连接信息)。
- `llm`OpenAI 兼容接口的 `base_url``api_key``model`
- `business`:并发数、日志级别、默认分类模式(`default_mode`)、日志目录(`log_dir`)、
是否启用"其他"兜底类目(`enable_other_category`,默认关闭)
@@ -269,6 +284,10 @@ python write_attachments.py --mode coarse
# 先预览将写入/跳过的行,不真正落库
python write_attachments.py --dry-run
# 首次在目标库建表(幂等:仅创建 Common schema 与 Attachment 表,已存在则跳过;
# 切换数据库或新环境首次部署前先执行一次)
python write_attachments.py --init-db
# 允许"其他"兜底类目
python write_attachments.py --mode fine --enable-other
```
@@ -285,22 +304,53 @@ python write_attachments.py --mode fine --enable-other
`(SN, '无附件', '无')`,下游用 `WHERE MajorCategory <> '无附件'` 取真实附件。
- **无法确定/失败**`has_attachment=null`,含 `not_found` / `llm_*_error` /
`db_error`):一律不写,既不当作无附件,也不留脏数据。
- **幂等**:写入时对同一总排号先删除旧行再插入本次结果(依赖 `SN, MajorCategory,
MinorCategory` 唯一索引),重跑安全。
- **幂等**:写入时对同一总排号先删除旧行再插入本次结果(依赖 `(SN, MajorCategory,
MinorCategory)` 复合主键保证唯一),重跑安全。
目标表 `Common.Attachment` 字段`SN`nvarchar(30),总排号/关联键)、
`MajorCategory`nvarchar(40),附件大类)、`MinorCategory`nvarchar(40),附件小类),
三者均 `NOT NULL`。
目标表 `Common.Attachment` 字段(三列**复合主键**、均 `NOT NULL`;列类型由 ORM 模型
`String(30)` / `String(40)` 按方言统一生成PostgreSQL 端为 `varchar`SQL Server 端为
`nvarchar`
- `SN`:总排号/关联键
- `MajorCategory`:附件大类
- `MinorCategory`:附件小类
## 数据库抽象层(多库支持)
数据访问层已重构为 SQLAlchemy由两层组成业务代码`classifier.py` /
`write_attachments.py`)只调用 `db.py` 的 4 个函数,无需感知底层方言:
- `orm.py`:方言无关的底层。
- `build_engine` / `get_engine`:按 `config.database.db_type` 生成 SQLAlchemy Engine
`postgresql+psycopg2` 或 `mssql+pyodbc`),含连接池复用(`pool_pre_ping`)与
登录/语句超时;`get_engine` 按连接信息缓存 Engine避免重复建池。
- `Attachment`:目标表 `Common.Attachment` 的声明式 ORM 模型(三列复合主键,
`quote=True` 保留大小写),跨库统一的建表/读写入口。
- `init_schema`:方言感知地 `CREATE SCHEMA IF NOT EXISTS "Common"` + `create_all`
供 `--init-db` 幂等建表。
- `db.py`:基于 SQLAlchemy Core 的查询/落库实现。
- 源表(表名/列名含中文、由配置驱动)用动态 `Table(..., quote=True,
quote_schema=True)` 构造,标识符引用由 SQLAlchemy 按方言生成PG 用 `"名"`、
MSSQL 用 `[名]`),彻底摆脱手写引号拼接。
- 对外 4 个函数签名与旧版完全一致:`fetch_params_by_ids`(支持 `--id` 整型真实 ID
与 `--sn` 总排号双键,并回查总排号)、`fetch_param_by_id`、`fetch_all_ids`
`distinct()` + `order_by()` + `limit()/offset(0)`,分页语法跨库自动适配)、
`upsert_attachments`(先删后插,依赖复合主键幂等)。
**切换数据库**:默认 `config.yaml` 指向 PostgreSQL切回 SQL Server 只需
`python write_attachments.py --config config.mssql.yaml`(或 `main.py --config ...`)。
两库源表 schema/表名/列名一致,仅需改连接信息与 `db_type`,无需改代码。
## 项目结构
```
├── config.yaml # 配置文件
├── config.yaml # 配置文件(默认 PostgreSQL含 db_type 切换)
├── config.mssql.yaml # SQL Server 版配置(数据库不可达时切换用,--config 指定)
├── main.py # 命令行入口:分类并输出 JSON Lines支持 --summary 在 stderr 打印批汇总;直接 import 同目录各模块)
├── write_attachments.py # 写入入口:分类结果落库到 Common.Attachment全表扫描支持 --limit/--order/--range 按真实 ID 排序与范围过滤;--id/--sn/--ids-file/--mode/--dry-run/--enable-other/--config运行后打印 token/缓存汇总)
├── write_attachments.py # 写入入口:分类结果落库到 Common.Attachment全表扫描支持 --limit/--order/--range 按真实 ID 排序与范围过滤;--id/--sn/--ids-file/--mode/--dry-run/--enable-other/--init-db/--config运行后打印 token/缓存汇总)
├── requirements.txt
├── config_loader.py # YAML 配置读取与校验
├── db.py # SQL Server 查询与落库fetch_params_by_ids 支持按 id/sn 双键查询、fetch_all_ids / upsert_attachmentspyodbc
├── orm.py # 数据库抽象层SQLAlchemy 引擎构建mssql/postgresql、Attachment ORM 模型、init_schema 建表
├── db.py # 基于 SQLAlchemy 的查询与落库fetch_params_by_ids 支持按 id/sn 双键查询、fetch_all_ids / upsert_attachments跨库无关
├── prompts.py # 提示词与细分类目枚举(唯一需要改分类边界时编辑的文件)
├── llm_client.py # LLM 调用 (OpenAI 兼容接口)
├── parser.py # LLM 输出格式校验与清洗 → 结构化数据