提交 407f7bc5 authored 作者: lidongxu's avatar lidongxu

完成重构版本:实现清洗售点稽查低价数据的清洗增加入库以及编辑和删除等查询操作

上级 49e50691
......@@ -3,7 +3,7 @@
from datetime import date
from decimal import Decimal
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, field_validator
class CleanRequestBody(BaseModel):
......@@ -49,3 +49,10 @@ class RiskAuditVisitReplaceBody(BaseModel):
fresh_status: str | None = Field(..., max_length=20)
large_date_status: str | None = Field(..., max_length=20)
large_date_rectify: str | None = Field(..., max_length=100)
@field_validator("price", "low_price_diff", mode="before")
@classmethod
def _blank_decimal_to_none(cls, v):
if v is None or v == "":
return None
return v
......@@ -80,6 +80,10 @@ _RAV_EXCEL_SPECS: list[tuple[str, str, Any]] = [
# 与表 uk_biz 列一致:audit_date,source,store_name,channel_type,series,taste,weight,dealer_name
_RAV_UK_BIZ_INDICES = (0, 1, 7, 13, 14, 15, 16, 5)
# MySQL 唯一索引中 NULL 互不相等,会导致 ON DUPLICATE KEY 无法合并;空源数据先填占位再入库
_RAV_UK_BIZ_DEFAULT_DATE = date(1900, 1, 1)
_RAV_UK_BIZ_DEFAULT_STR = "-"
_RAV_UK_BIZ_STR_INDICES = (1, 7, 13, 14, 15, 16, 5) # uk 中 varchar 列下标(与 _RAV_DB_COLS 对齐)
_RAV_INSERT_SQL = (
f"INSERT INTO risk_audit_visit ({','.join(_RAV_DB_COLS)}) VALUES "
......@@ -131,6 +135,19 @@ def _rav_int(val: Any) -> int | None:
return int(x)
def _fill_uk_biz_defaults(tup: tuple[Any, ...]) -> tuple[Any, ...]:
lst = list(tup)
if lst[0] is None:
lst[0] = _RAV_UK_BIZ_DEFAULT_DATE
for i in _RAV_UK_BIZ_STR_INDICES:
if lst[i] is None:
spec = _RAV_EXCEL_SPECS[i][2]
mx = spec if isinstance(spec, int) else 20
s = _RAV_UK_BIZ_DEFAULT_STR[:mx]
lst[i] = s
return tuple(lst)
def _row_to_rav_tuple(row: pd.Series) -> tuple[Any, ...]:
out: list[Any] = []
for _, zh, spec in _RAV_EXCEL_SPECS:
......@@ -143,7 +160,7 @@ def _row_to_rav_tuple(row: pd.Series) -> tuple[Any, ...]:
out.append(_rav_int(raw))
else:
out.append(_rav_str(raw, spec))
return tuple(out)
return _fill_uk_biz_defaults(tuple(out))
def _uk_biz_from_rav_tuple(tup: tuple[Any, ...]) -> tuple[Any, ...]:
......
......@@ -29,7 +29,7 @@
- **GET `/api/v1/risk-audit-visit`**:分页列表。查询参数 `page`(默认 1)、`page_size`(默认 20,最大 200)。成功时 `data``items`(行字典数组,含 `rav_id` 与全部业务列)、`total``page``page_size`;按 `rav_id` 降序。
- **GET `/api/v1/risk-audit-visit/{rav_id}`**:单条详情,返回该主键下**全部字段**`ApiEnvelope.data` 为一条完整记录)。不存在 → **404**
- **PUT `/api/v1/risk-audit-visit/{rav_id}`**:按主键**整行覆盖**更新。请求体为 **`RiskAuditVisitReplaceBody`**(与表业务列一致,**不含** `rav_id`;须提交全部字段键,值为 JSON `null` 表示写入 NULL)。不存在 → **404**;违反唯一键 `uk_biz`**409**;库连接等失败 → **502**
- **PUT `/api/v1/risk-audit-visit/{rav_id}`**:按主键**整行覆盖**更新。请求体为 **`RiskAuditVisitReplaceBody`**(与表业务列一致,**不含** `rav_id`;须提交全部字段键,值为 JSON `null` 表示写入 NULL)。`price``low_price_diff` 若前端传空字符串 `""`,与 `null` 同等视为写入 NULL。不存在 → **404**;违反唯一键 `uk_biz`**409**;库连接等失败 → **502**
- **DELETE `/api/v1/risk-audit-visit/{rav_id}`**:按主键删除。不存在 → **404**
连接参数同清洗写库:`Settings.mysql_connect_kwargs()`(见 [database.md](database.md))。
......
......@@ -2,8 +2,14 @@
按时间倒序记录重要约定与行为变化,便于以后查阅;细节仍以各模块文档与代码为准。
## 2026-03-28
- **`PUT /api/v1/risk-audit-visit/{rav_id}`**`RiskAuditVisitReplaceBody``price``low_price_diff` 在 Pydantic 层将 JSON 空字符串 `""` 规范为 `None`,避免前端用空串占位时触发 `decimal_parsing` 422;语义与 `null` 一致(写入 NULL)。
## 2026-03-27
- **`risk_audit_visit` 写入**`code/py_/audit/point_sale/low_price.py` 在组行入库前,对唯一键 `uk_biz` 八列中空值填占位(日期 `1900-01-01`、字符串 `-`),避免 MySQL 唯一索引对 `NULL` 不判等导致重复插入。
- **`POST /api/v1/clean` 统计语义**`merged_rows` 仍为**合并后物理总行数**`risk_audit_visit_rows` 表示按 `uk_biz` 去重后的有效入库行数(同键多行仅最后一行留在库);`risk_audit_visit_collapsed_duplicate_rows` 为「合并待写总行数 − 去重键数」。
- **脚本清理**:删除一次性 Navicat 备份转换脚本 **`scripts/transform_rav_backup_sql.py`**(仓库内无其它引用)。
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论