feat: update assessment portal

This commit is contained in:
2026-07-06 16:53:06 +08:00
parent a75e857c71
commit f385f9747b
17 changed files with 1866 additions and 884 deletions
+74 -17
View File
@@ -23,6 +23,7 @@ from .config import IMAGE_DIR, UPLOAD_DIR
CHINESE_FONT_CANDIDATES = [
"Noto Sans CJK SC",
"Noto Sans SC",
"Noto Sans CJK JP",
"Noto Sans CJK TC",
"Source Han Sans SC",
@@ -32,6 +33,17 @@ CHINESE_FONT_CANDIDATES = [
"Arial Unicode MS",
]
CHINESE_FONT_FILES = [
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/truetype/noto/NotoSansSC-Regular.ttf",
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
"/usr/local/share/fonts/NotoSansCJK-Regular.ttc",
"/mnt/c/Windows/Fonts/NotoSansSC-VF.ttf",
"/mnt/c/Windows/Fonts/msyh.ttc",
"/mnt/c/Windows/Fonts/simhei.ttf",
"/mnt/c/Windows/Fonts/simsun.ttc",
]
FEATURES = [
"管材",
"管径",
@@ -47,6 +59,7 @@ FEATURES = [
ID_COLUMN = "管道编号"
PIPE_AGE_COLUMN = "管龄"
SUPPORTED_EXTENSIONS = {".csv", ".xls", ".xlsx"}
CHINESE_FONT_PROP = None
class PredictionError(Exception):
@@ -69,14 +82,34 @@ class PredictionArtifacts:
analysis_text: str
def configure_matplotlib_fonts() -> None:
def configure_matplotlib_fonts():
for font_path in CHINESE_FONT_FILES:
path = Path(font_path)
if path.exists():
font_manager.fontManager.addfont(str(path))
prop = font_manager.FontProperties(fname=str(path))
rcParams["font.family"] = [prop.get_name(), "sans-serif"]
rcParams["font.sans-serif"] = [prop.get_name(), *CHINESE_FONT_CANDIDATES, "DejaVu Sans"]
rcParams["axes.unicode_minus"] = False
return prop
available_fonts = {font.name for font in font_manager.fontManager.ttflist}
selected_fonts = [font for font in CHINESE_FONT_CANDIDATES if font in available_fonts]
if selected_fonts:
rcParams["font.family"] = [selected_fonts[0], "sans-serif"]
rcParams["font.sans-serif"] = selected_fonts + ["DejaVu Sans"]
rcParams["axes.unicode_minus"] = False
if selected_fonts:
return font_manager.FontProperties(family=selected_fonts[0])
logging.warning("未找到中文字体,生成的图表中文可能无法显示。")
return None
configure_matplotlib_fonts()
CHINESE_FONT_PROP = configure_matplotlib_fonts()
def chinese_font_kwargs() -> dict[str, Any]:
return {"fontproperties": CHINESE_FONT_PROP} if CHINESE_FONT_PROP else {}
def load_model(model_path: str):
@@ -105,7 +138,7 @@ def safe_unlink(path: Path) -> None:
def secure_upload_name(original_filename: str, run_id: str) -> tuple[str, str]:
suffix = Path(original_filename).suffix.lower()
if suffix not in SUPPORTED_EXTENSIONS:
raise PredictionError("不支持的格式,仅支持 CSV / XLS / XLSX")
raise PredictionError("不支持的格式,仅支持逗号分隔值文件或电子表格文件")
safe_full_name = secure_filename(original_filename)
safe_stem = Path(safe_full_name).stem if safe_full_name else ""
@@ -228,10 +261,14 @@ def render_importance_chart(values: np.ndarray, save_path: Path) -> None:
fig, ax = plt.subplots(figsize=(9, max(3.0, 0.62 * n + 1.6)))
cmap = LinearSegmentedColormap.from_list("brand", ["#7fb2e6", "#005EB8", "#0c4188"])
colors = cmap(np.linspace(0.15, 1.0, n)) if n else None
bars = ax.barh(sorted_feats, sorted_vals, color=colors, height=0.66, edgecolor="white", linewidth=0.8, zorder=3)
y_pos = np.arange(n)
bars = ax.barh(y_pos, sorted_vals, color=colors, height=0.66, edgecolor="white", linewidth=0.8, zorder=3)
ax.set_xlabel("相对重要性", fontsize=11, color="#475569")
ax.set_title("模型输入因素重要性排序", fontsize=15, fontweight="bold", color="#0f172a", pad=14)
font_kwargs = chinese_font_kwargs()
ax.set_xlabel("相对重要性", fontsize=11, color="#475569", **font_kwargs)
ax.set_title("模型输入因素重要性排序", fontsize=15, fontweight="bold", color="#0f172a", pad=14, **font_kwargs)
ax.set_yticks(y_pos)
ax.set_yticklabels(sorted_feats, **font_kwargs)
ax.grid(axis="x", color="#e2e8f0", linewidth=1, zorder=0)
ax.set_axisbelow(True)
for spine in ("top", "right", "left"):
@@ -239,6 +276,9 @@ def render_importance_chart(values: np.ndarray, save_path: Path) -> None:
ax.spines["bottom"].set_color("#cbd5e1")
ax.tick_params(axis="y", length=0, labelsize=11)
ax.tick_params(axis="x", colors="#94a3b8", labelsize=9)
if CHINESE_FONT_PROP:
for label in [*ax.get_yticklabels(), *ax.get_xticklabels()]:
label.set_fontproperties(CHINESE_FONT_PROP)
max_val = float(sorted_vals.max()) if n else 0.0
for bar, value in zip(bars, sorted_vals):
@@ -251,6 +291,7 @@ def render_importance_chart(values: np.ndarray, save_path: Path) -> None:
fontsize=10,
fontweight="bold",
color="#1e293b",
**font_kwargs,
)
if max_val > 0:
ax.set_xlim(0, max_val * 1.18)
@@ -352,12 +393,17 @@ def render_survival_chart(df: pd.DataFrame, curves, image_path: Path) -> tuple[l
)
plt.step(times, probs, where="post", linewidth=2, label=pipe_id)
plt.xlabel("预测时间轴(年)")
plt.ylabel("生存概率")
plt.title("预测分析图")
font_kwargs = chinese_font_kwargs()
plt.xlabel("预测时间轴(年)", **font_kwargs)
plt.ylabel("生存概率", **font_kwargs)
plt.title("预测分析图", **font_kwargs)
plt.grid(alpha=0.18)
if len(summary_rows) <= 12:
plt.legend(loc="best", fontsize=8)
plt.legend(loc="best", fontsize=8, prop=CHINESE_FONT_PROP)
ax = plt.gca()
if CHINESE_FONT_PROP:
for label in [*ax.get_xticklabels(), *ax.get_yticklabels()]:
label.set_fontproperties(CHINESE_FONT_PROP)
plt.tight_layout()
plt.savefig(image_path, dpi=160, bbox_inches="tight")
plt.close()
@@ -370,11 +416,22 @@ def write_prediction_workbook(
summary_rows: list[dict[str, Any]],
summary_sheet_rows: list[dict[str, Any]],
) -> None:
with pd.ExcelWriter(excel_path, engine="xlsxwriter") as writer:
sample_data_rows: list[dict[str, Any]] = []
for i, curve in enumerate(curves):
times = [float(x) for x in list(curve.x)]
probs = [float(y) for y in list(curve.y)]
pipe_id = summary_rows[i]["pipe_id"]
for time, probability in zip(times, probs):
sample_data_rows.append(
{
"管道编号": pipe_id,
"样本序号": i + 1,
"时间(年)": time,
"生存概率": probability,
"风险概率": 1 - probability,
}
)
with pd.ExcelWriter(excel_path, engine="openpyxl") as writer:
pd.DataFrame(summary_sheet_rows).to_excel(writer, sheet_name="结果摘要", index=False)
for i, curve in enumerate(curves):
times = [float(x) for x in list(curve.x)]
probs = [float(y) for y in list(curve.y)]
pipe_id = summary_rows[i]["pipe_id"]
out_df = pd.DataFrame({"时间(年)": times, f"{pipe_id}生存概率": probs})
out_df.to_excel(writer, sheet_name=f"样本{i+1}", index=False)
pd.DataFrame(sample_data_rows).to_excel(writer, sheet_name="样本数据", index=False)