fix: 修复数值字段导入截断错误
This commit is contained in:
+23
-28
@@ -105,6 +105,7 @@ class DataProcessor:
|
|||||||
"numeric": "float",
|
"numeric": "float",
|
||||||
}
|
}
|
||||||
MYSQL_NUMERIC_PATTERN = r"^-?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][+-]?[0-9]+)?$"
|
MYSQL_NUMERIC_PATTERN = r"^-?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][+-]?[0-9]+)?$"
|
||||||
|
NUMERIC_NULL_TEXTS = {"", "-", "--", "—", "–", "NA", "N/A", "NULL", "NONE", "NAN", "\\N"}
|
||||||
|
|
||||||
def __init__(self, config: AppConfig, work_dir: Path, logger: ProcessLogger):
|
def __init__(self, config: AppConfig, work_dir: Path, logger: ProcessLogger):
|
||||||
self.config = config
|
self.config = config
|
||||||
@@ -647,44 +648,37 @@ class DataProcessor:
|
|||||||
def _convert_int_column(self, series: pd.Series) -> pd.Series:
|
def _convert_int_column(self, series: pd.Series) -> pd.Series:
|
||||||
"""转换整数列"""
|
"""转换整数列"""
|
||||||
try:
|
try:
|
||||||
# 检测是否包含百分号
|
numeric = self._numeric_series(series)
|
||||||
text = series.astype("string")
|
|
||||||
has_percent = text.str.contains('%', regex=False, na=False)
|
|
||||||
|
|
||||||
# 去除格式字符,保留正常数值含义
|
|
||||||
cleaned = self._clean_numeric_text(text)
|
|
||||||
# 转换为数值
|
|
||||||
numeric = pd.to_numeric(cleaned, errors='coerce')
|
|
||||||
|
|
||||||
# 如果有百分号,除以100转换为小数
|
|
||||||
numeric[has_percent] = numeric[has_percent] / 100
|
|
||||||
|
|
||||||
# 四舍五入并转为整数字符串,空值保留为 NULL,正常 0 不再误判为空值
|
|
||||||
rounded = numeric.round()
|
rounded = numeric.round()
|
||||||
return rounded.astype("Int64").astype("string").where(rounded.notna(), None)
|
return pd.Series(
|
||||||
|
[None if pd.isna(value) else int(value) for value in rounded],
|
||||||
|
index=series.index,
|
||||||
|
dtype=object,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return series
|
return series
|
||||||
|
|
||||||
def _convert_float_column(self, series: pd.Series) -> pd.Series:
|
def _convert_float_column(self, series: pd.Series) -> pd.Series:
|
||||||
"""转换浮点数列"""
|
"""转换浮点数列"""
|
||||||
try:
|
try:
|
||||||
# 检测是否包含百分号
|
numeric = self._numeric_series(series)
|
||||||
text = series.astype("string")
|
return pd.Series(
|
||||||
has_percent = text.str.contains('%', regex=False, na=False)
|
[None if pd.isna(value) else float(value) for value in numeric],
|
||||||
|
index=series.index,
|
||||||
# 去除格式字符,保留正常数值含义
|
dtype=object,
|
||||||
cleaned = self._clean_numeric_text(text)
|
)
|
||||||
# 转换为数值
|
|
||||||
numeric = pd.to_numeric(cleaned, errors='coerce')
|
|
||||||
|
|
||||||
# 如果有百分号,除以100转换为小数(例如:95% → 0.95)
|
|
||||||
numeric[has_percent] = numeric[has_percent] / 100
|
|
||||||
|
|
||||||
# 保留小数,空值保留为 NULL,正常 0 不再误判为空值
|
|
||||||
return numeric.astype("string").where(numeric.notna(), None)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return series
|
return series
|
||||||
|
|
||||||
|
def _numeric_series(self, series: pd.Series) -> pd.Series:
|
||||||
|
text = series.astype("string")
|
||||||
|
has_percent = text.str.contains(r"[%%]", regex=True, na=False)
|
||||||
|
cleaned = self._clean_numeric_text(text)
|
||||||
|
empty_mask = cleaned.isna() | cleaned.str.upper().isin(self.NUMERIC_NULL_TEXTS)
|
||||||
|
numeric = pd.to_numeric(cleaned.mask(empty_mask, pd.NA), errors="coerce")
|
||||||
|
numeric[has_percent & numeric.notna()] = numeric[has_percent & numeric.notna()] / 100
|
||||||
|
return numeric
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _clean_numeric_text(series: pd.Series) -> pd.Series:
|
def _clean_numeric_text(series: pd.Series) -> pd.Series:
|
||||||
"""清理数值文本中的格式字符,不改变正常数字。"""
|
"""清理数值文本中的格式字符,不改变正常数字。"""
|
||||||
@@ -693,6 +687,7 @@ class DataProcessor:
|
|||||||
.str.replace(',', '', regex=False)
|
.str.replace(',', '', regex=False)
|
||||||
.str.replace(',', '', regex=False)
|
.str.replace(',', '', regex=False)
|
||||||
.str.replace('%', '', regex=False)
|
.str.replace('%', '', regex=False)
|
||||||
|
.str.replace('%', '', regex=False)
|
||||||
.str.replace('\t', '', regex=False)
|
.str.replace('\t', '', regex=False)
|
||||||
.str.replace(' ', '', regex=False)
|
.str.replace(' ', '', regex=False)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# 项目上下文记录
|
# 项目上下文记录
|
||||||
|
|
||||||
|
## 2026-05-19:修复 CSV 导入阶段数值截断错误
|
||||||
|
|
||||||
|
- `DataProcessor` 仍会根据 `ReportScript.sql` 的 `MODIFY COLUMN` 提前把业务数值字段建成 `INT/FLOAT`,但数值清洗改为返回真正的 Python `None/int/float`,避免 pandas `<NA>` 或异常文本被 PyMySQL 当作字符串写入数值列。
|
||||||
|
- 数值字段导入前会把空串、`-`、`--`、长短横线、`NA/N/A/NULL/NONE/NAN/\N` 等源 CSV 占位符转为数据库 `NULL`,正常 `0` 保留为 `0`;逗号/全角逗号、半角/全角百分号和空白仍按数值格式清理。
|
||||||
|
- 该问题本质是新版提前按 SQL 类型建表后,MySQL 严格模式会在 CSV 导入阶段拒绝脏数值;旧版多为字符串先落库,所以不会在导入阶段出现 `Data truncated for column`。
|
||||||
|
- 已执行数值转换样例验证、`.venv\Scripts\python.exe -m compileall app` 和 `uvx --offline ruff check .`,均通过。
|
||||||
|
|
||||||
## 2026-05-19:优化处理进度阶段显示和日志跟随
|
## 2026-05-19:优化处理进度阶段显示和日志跟随
|
||||||
|
|
||||||
- `ProcessLogger` 新增轻量阶段回调,`DataProcessor.process()` 会在远程下载后依次上报 `extracting`、`converting`、`importing`、`scripting`、`completed/failed` 阶段。
|
- `ProcessLogger` 新增轻量阶段回调,`DataProcessor.process()` 会在远程下载后依次上报 `extracting`、`converting`、`importing`、`scripting`、`completed/failed` 阶段。
|
||||||
|
|||||||
Reference in New Issue
Block a user