diff --git a/src/gui/panels/step13_report_panel.py b/src/gui/panels/step13_report_panel.py index f16a4df..9bfda38 100644 --- a/src/gui/panels/step13_report_panel.py +++ b/src/gui/panels/step13_report_panel.py @@ -14,7 +14,7 @@ from src.gui.panels._step_path_resolver import resolve_subdir from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout, - QLabel, QCheckBox, QPushButton, QLineEdit, QComboBox, + QLabel, QCheckBox, QPushButton, QLineEdit, QComboBox, QSpinBox, QMessageBox, QFileDialog, QProgressBar, ) @@ -34,13 +34,14 @@ class ReportWorkerThread(QThread): error = pyqtSignal(str) def __init__(self, work_dir: str, output_dir: Optional[str], report_title: str, - enable_ai: bool, report_mode: str = 'ml'): + enable_ai: bool, report_mode: str = 'ml', ai_maps_limit: int = 999): super().__init__() self.work_dir = work_dir self.output_dir = output_dir self.report_title = report_title self.enable_ai = enable_ai self.report_mode = report_mode + self.ai_maps_limit = ai_maps_limit def run(self): try: @@ -79,6 +80,7 @@ class ReportWorkerThread(QThread): work_dir=self.work_dir, report_title=self.report_title or "水质参数反演分析报告", report_mode=self.report_mode, + ai_maps_limit=self.ai_maps_limit, on_progress=lambda pct, text: self.progress.emit(int(pct), str(text)), ) self.finished.emit(str(out_path)) @@ -275,6 +277,22 @@ class Step13ReportPanel(QWidget): mode_row.addWidget(self.report_mode_cb, 1) execute_layout.addLayout(mode_row) + # AI 专题图分析数量(仅公式模式生效) + ai_maps_row = QHBoxLayout() + ai_maps_row.setContentsMargins(0, 0, 0, 0) + ai_maps_row.setSpacing(10) + ai_maps_label = QLabel("AI分析专题图数:") + ai_maps_label.setMinimumWidth(120) + ai_maps_label.setMaximumWidth(120) + ai_maps_row.addWidget(ai_maps_label) + self.ai_maps_spin = QSpinBox() + self.ai_maps_spin.setRange(0, 999) + self.ai_maps_spin.setValue(999) + self.ai_maps_spin.setToolTip("公式报告中启用AI分析的分布图数量上限(0=全部跳过,999=全部分析)") + self.ai_maps_spin.setStyleSheet(common_lineedit_css) + ai_maps_row.addWidget(self.ai_maps_spin, 1) + execute_layout.addLayout(ai_maps_row) + action_layout = QHBoxLayout() action_layout.addStretch() @@ -413,7 +431,8 @@ class Step13ReportPanel(QWidget): self.progress_label.setText("正在准备生成…") report_mode = self.report_mode_cb.currentData() - self._report_thread = ReportWorkerThread(wd, out, title, enable_ai, report_mode) + ai_maps_limit = self.ai_maps_spin.value() + self._report_thread = ReportWorkerThread(wd, out, title, enable_ai, report_mode, ai_maps_limit) self._report_thread.progress.connect(self._on_progress, Qt.QueuedConnection) self._report_thread.finished.connect(self._on_finished, Qt.QueuedConnection) self._report_thread.error.connect(self._on_error, Qt.QueuedConnection) diff --git a/src/postprocessing/report_word.py b/src/postprocessing/report_word.py index 008b155..86f72b6 100644 --- a/src/postprocessing/report_word.py +++ b/src/postprocessing/report_word.py @@ -779,6 +779,7 @@ class WaterQualityReportGenerator: report_title: str = "水质参数反演分析报告", output_path: Optional[str] = None, report_mode: str = 'ml', + ai_maps_limit: int = 999, on_progress=None) -> str: """ 生成 Word 报告(双轨制入口) @@ -808,7 +809,8 @@ class WaterQualityReportGenerator: print(f"[报告] 报告模式: {report_mode}") if report_mode == 'formula': - return self._generate_formula_report(parameters, report_title, output_path, on_progress) + return self._generate_formula_report(parameters, report_title, output_path, + ai_maps_limit=ai_maps_limit, on_progress=on_progress) else: return self._generate_ml_report(parameters, report_title, output_path, on_progress) @@ -941,7 +943,8 @@ class WaterQualityReportGenerator: return str(output_path) - def _generate_formula_report(self, parameters, report_title, output_path, on_progress): + def _generate_formula_report(self, parameters, report_title, output_path, + ai_maps_limit=999, on_progress=None): """水色指数与物理经验公式反演报告 (Step 10/11)""" from docx.shared import Inches, Pt, Cm, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH @@ -1094,16 +1097,22 @@ class WaterQualityReportGenerator: figure_num += 1 maps_found = 0 if tif_files: - for i_t, tf in enumerate(tif_files[:20]): + n_show = min(len(tif_files), 20) + for i_t, tf in enumerate(tif_files[:n_show]): try: param_name = tf.stem.split('_')[0] if '_' in tf.stem else tf.stem caption = f"图{figure_num} {param_name} 空间分布图" - self._add_image_with_caption(doc, str(tf), caption, width=Inches(5.5)) + if self._add_image_with_caption(doc, str(tf), caption, width=Inches(5.5)): + if self.enable_ai_analysis and i_t < ai_maps_limit: + ai_text = self._analyze_and_cache_image( + image_path=tf, image_type="distribution", + param=param_name, figure_num=figure_num) + self._add_ai_analysis_paragraph(doc, ai_text) figure_num += 1 maps_found += 1 except Exception as e: doc.add_paragraph(f"[专题图插入失败: {tf.name} — {e}]") - _next(f"专题图 {i_t+1}/{min(len(tif_files), 20)}") + _next(f"专题图 {i_t+1}/{n_show}") if len(tif_files) > 20: doc.add_paragraph(f"... 共 {len(tif_files)} 张专题图,此处仅展示前 20 张。") else: