feat: update assessment portal
This commit is contained in:
+74
-17
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user