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