调整列名并添加输入验证
This commit is contained in:
+13
-13
@@ -1,16 +1,16 @@
|
||||
__pycache__/
|
||||
.git
|
||||
.agents
|
||||
.codex
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
.git/
|
||||
.agents/
|
||||
.codex/
|
||||
node_modules/
|
||||
.env
|
||||
|
||||
app.log
|
||||
server.out.log
|
||||
server.err.log
|
||||
data/
|
||||
.pytest_cache
|
||||
node_modules
|
||||
data
|
||||
uploads
|
||||
static/images
|
||||
pipe_survival_0331.db
|
||||
uploads/
|
||||
static/images/
|
||||
app.log
|
||||
server.err.log
|
||||
server.out.log
|
||||
.env
|
||||
|
||||
@@ -22,6 +22,7 @@ COPY templates ./templates
|
||||
COPY static ./static
|
||||
COPY main.py .
|
||||
COPY example.xlsx .
|
||||
COPY 20260630标准文本——供水管道健康状态与剩余寿命评估技术导则.pdf .
|
||||
COPY my_survival_forest_model_quxi-10-0331.joblib .
|
||||
|
||||
RUN mkdir -p data static/images uploads
|
||||
|
||||
+24
-7
@@ -57,7 +57,17 @@ FEATURES = [
|
||||
]
|
||||
|
||||
ID_COLUMN = "管道编号"
|
||||
PIPE_AGE_COLUMN = "管龄"
|
||||
PIPE_AGE_COLUMN = "管龄(年)"
|
||||
LEGACY_PIPE_AGE_COLUMN = "管龄"
|
||||
STATUS_COLUMN = "状态"
|
||||
EVENT_AGE_COLUMN = "事件/观察管龄(年)"
|
||||
INPUT_COLUMNS = [
|
||||
ID_COLUMN,
|
||||
PIPE_AGE_COLUMN,
|
||||
STATUS_COLUMN,
|
||||
EVENT_AGE_COLUMN,
|
||||
*FEATURES,
|
||||
]
|
||||
MATERIAL_COLUMN = "管材"
|
||||
MATERIAL_CODE_OPTIONS = [
|
||||
(1, "镀锌"),
|
||||
@@ -183,11 +193,19 @@ def secure_upload_name(original_filename: str, run_id: str) -> tuple[str, str]:
|
||||
def read_input_file(path: Path, suffix: str) -> pd.DataFrame:
|
||||
try:
|
||||
if suffix == ".csv":
|
||||
return pd.read_csv(path, dtype={ID_COLUMN: "string"})
|
||||
return pd.read_excel(path, dtype={ID_COLUMN: "string"})
|
||||
df = pd.read_csv(path, dtype={ID_COLUMN: "string"})
|
||||
else:
|
||||
df = pd.read_excel(path, dtype={ID_COLUMN: "string"})
|
||||
except Exception as exc:
|
||||
logging.exception("文件解析失败: %s", exc)
|
||||
raise PredictionError("文件解析失败,请检查编码或表格格式。")
|
||||
return normalize_input_columns(df)
|
||||
|
||||
|
||||
def normalize_input_columns(df: pd.DataFrame) -> pd.DataFrame:
|
||||
if PIPE_AGE_COLUMN not in df.columns and LEGACY_PIPE_AGE_COLUMN in df.columns:
|
||||
return df.rename(columns={LEGACY_PIPE_AGE_COLUMN: PIPE_AGE_COLUMN})
|
||||
return df
|
||||
|
||||
|
||||
def normalize_id_value(value: Any, fallback: str) -> str:
|
||||
@@ -201,8 +219,7 @@ def normalize_id_value(value: Any, fallback: str) -> str:
|
||||
def validate_input_frame(df: pd.DataFrame) -> None:
|
||||
if df.empty:
|
||||
raise PredictionError("上传文件没有可预测的数据。")
|
||||
required_columns = [ID_COLUMN, *FEATURES]
|
||||
missing = [col for col in required_columns if col not in df.columns]
|
||||
missing = [col for col in INPUT_COLUMNS if col not in df.columns]
|
||||
if missing:
|
||||
raise PredictionError(f"缺少必要字段: {', '.join(missing)}")
|
||||
|
||||
@@ -447,8 +464,8 @@ def render_survival_chart(df: pd.DataFrame, curves, image_path: Path) -> tuple[l
|
||||
)
|
||||
summary_sheet_rows.append(
|
||||
{
|
||||
"管道编号": pipe_id,
|
||||
"管龄": pipe_age,
|
||||
ID_COLUMN: pipe_id,
|
||||
PIPE_AGE_COLUMN: pipe_age,
|
||||
"健康等级": grade_label,
|
||||
}
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -11,6 +11,7 @@ from openpyxl import load_workbook
|
||||
from app.prediction import (
|
||||
FEATURES,
|
||||
ID_COLUMN,
|
||||
INPUT_COLUMNS,
|
||||
PIPE_AGE_COLUMN,
|
||||
PredictionError,
|
||||
estimate_remaining_life,
|
||||
@@ -59,6 +60,18 @@ class PredictionHelpersTest(unittest.TestCase):
|
||||
|
||||
self.assertIn("缺少必要字段", ctx.exception.message)
|
||||
|
||||
def test_validate_input_frame_accepts_current_template_columns(self) -> None:
|
||||
df = pd.DataFrame([{column: 1 for column in INPUT_COLUMNS}])
|
||||
|
||||
validate_input_frame(df)
|
||||
|
||||
def test_example_workbook_columns_match_backend_contract(self) -> None:
|
||||
workbook = load_workbook(Path(__file__).resolve().parents[1] / "example.xlsx", read_only=True)
|
||||
worksheet = workbook["Template"]
|
||||
headers = [cell.value for cell in next(worksheet.iter_rows(min_row=1, max_row=1))]
|
||||
|
||||
self.assertEqual(headers, INPUT_COLUMNS)
|
||||
|
||||
def test_read_input_file_preserves_text_pipe_ids(self) -> None:
|
||||
with TemporaryDirectory() as temp_dir:
|
||||
output_path = Path(temp_dir) / "input.xlsx"
|
||||
@@ -97,8 +110,8 @@ class PredictionHelpersTest(unittest.TestCase):
|
||||
]
|
||||
summary_rows = [{"pipe_id": "P001"}, {"pipe_id": "P002"}]
|
||||
summary_sheet_rows = [
|
||||
{"管道编号": "P001", "管龄": "10 年", "健康概率": 0.7, "预计剩余寿命": 2.0, "健康等级": "IV级"},
|
||||
{"管道编号": "P002", "管龄": "12 年", "健康概率": 0.6, "预计剩余寿命": 1.5, "健康等级": "III级"},
|
||||
{ID_COLUMN: "P001", PIPE_AGE_COLUMN: "10 年", "健康概率": 0.7, "预计剩余寿命": 2.0, "健康等级": "IV级"},
|
||||
{ID_COLUMN: "P002", PIPE_AGE_COLUMN: "12 年", "健康概率": 0.6, "预计剩余寿命": 1.5, "健康等级": "III级"},
|
||||
]
|
||||
|
||||
with TemporaryDirectory() as temp_dir:
|
||||
|
||||
Reference in New Issue
Block a user