feat: formula report AI analysis per distribution map with configurable limit in UI panel
This commit is contained in:
@ -14,7 +14,7 @@ from src.gui.panels._step_path_resolver import resolve_subdir
|
|||||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
||||||
QLabel, QCheckBox, QPushButton, QLineEdit, QComboBox,
|
QLabel, QCheckBox, QPushButton, QLineEdit, QComboBox, QSpinBox,
|
||||||
QMessageBox, QFileDialog, QProgressBar,
|
QMessageBox, QFileDialog, QProgressBar,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,13 +34,14 @@ class ReportWorkerThread(QThread):
|
|||||||
error = pyqtSignal(str)
|
error = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, work_dir: str, output_dir: Optional[str], report_title: 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__()
|
super().__init__()
|
||||||
self.work_dir = work_dir
|
self.work_dir = work_dir
|
||||||
self.output_dir = output_dir
|
self.output_dir = output_dir
|
||||||
self.report_title = report_title
|
self.report_title = report_title
|
||||||
self.enable_ai = enable_ai
|
self.enable_ai = enable_ai
|
||||||
self.report_mode = report_mode
|
self.report_mode = report_mode
|
||||||
|
self.ai_maps_limit = ai_maps_limit
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
@ -79,6 +80,7 @@ class ReportWorkerThread(QThread):
|
|||||||
work_dir=self.work_dir,
|
work_dir=self.work_dir,
|
||||||
report_title=self.report_title or "水质参数反演分析报告",
|
report_title=self.report_title or "水质参数反演分析报告",
|
||||||
report_mode=self.report_mode,
|
report_mode=self.report_mode,
|
||||||
|
ai_maps_limit=self.ai_maps_limit,
|
||||||
on_progress=lambda pct, text: self.progress.emit(int(pct), str(text)),
|
on_progress=lambda pct, text: self.progress.emit(int(pct), str(text)),
|
||||||
)
|
)
|
||||||
self.finished.emit(str(out_path))
|
self.finished.emit(str(out_path))
|
||||||
@ -275,6 +277,22 @@ class Step13ReportPanel(QWidget):
|
|||||||
mode_row.addWidget(self.report_mode_cb, 1)
|
mode_row.addWidget(self.report_mode_cb, 1)
|
||||||
execute_layout.addLayout(mode_row)
|
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 = QHBoxLayout()
|
||||||
action_layout.addStretch()
|
action_layout.addStretch()
|
||||||
|
|
||||||
@ -413,7 +431,8 @@ class Step13ReportPanel(QWidget):
|
|||||||
self.progress_label.setText("正在准备生成…")
|
self.progress_label.setText("正在准备生成…")
|
||||||
|
|
||||||
report_mode = self.report_mode_cb.currentData()
|
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.progress.connect(self._on_progress, Qt.QueuedConnection)
|
||||||
self._report_thread.finished.connect(self._on_finished, Qt.QueuedConnection)
|
self._report_thread.finished.connect(self._on_finished, Qt.QueuedConnection)
|
||||||
self._report_thread.error.connect(self._on_error, Qt.QueuedConnection)
|
self._report_thread.error.connect(self._on_error, Qt.QueuedConnection)
|
||||||
|
|||||||
@ -779,6 +779,7 @@ class WaterQualityReportGenerator:
|
|||||||
report_title: str = "水质参数反演分析报告",
|
report_title: str = "水质参数反演分析报告",
|
||||||
output_path: Optional[str] = None,
|
output_path: Optional[str] = None,
|
||||||
report_mode: str = 'ml',
|
report_mode: str = 'ml',
|
||||||
|
ai_maps_limit: int = 999,
|
||||||
on_progress=None) -> str:
|
on_progress=None) -> str:
|
||||||
"""
|
"""
|
||||||
生成 Word 报告(双轨制入口)
|
生成 Word 报告(双轨制入口)
|
||||||
@ -808,7 +809,8 @@ class WaterQualityReportGenerator:
|
|||||||
print(f"[报告] 报告模式: {report_mode}")
|
print(f"[报告] 报告模式: {report_mode}")
|
||||||
|
|
||||||
if report_mode == 'formula':
|
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:
|
else:
|
||||||
return self._generate_ml_report(parameters, report_title, output_path, on_progress)
|
return self._generate_ml_report(parameters, report_title, output_path, on_progress)
|
||||||
|
|
||||||
@ -941,7 +943,8 @@ class WaterQualityReportGenerator:
|
|||||||
|
|
||||||
return str(output_path)
|
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)"""
|
"""水色指数与物理经验公式反演报告 (Step 10/11)"""
|
||||||
from docx.shared import Inches, Pt, Cm, RGBColor
|
from docx.shared import Inches, Pt, Cm, RGBColor
|
||||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
@ -1094,16 +1097,22 @@ class WaterQualityReportGenerator:
|
|||||||
figure_num += 1
|
figure_num += 1
|
||||||
maps_found = 0
|
maps_found = 0
|
||||||
if tif_files:
|
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:
|
try:
|
||||||
param_name = tf.stem.split('_')[0] if '_' in tf.stem else tf.stem
|
param_name = tf.stem.split('_')[0] if '_' in tf.stem else tf.stem
|
||||||
caption = f"图{figure_num} {param_name} 空间分布图"
|
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
|
figure_num += 1
|
||||||
maps_found += 1
|
maps_found += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
doc.add_paragraph(f"[专题图插入失败: {tf.name} — {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:
|
if len(tif_files) > 20:
|
||||||
doc.add_paragraph(f"... 共 {len(tif_files)} 张专题图,此处仅展示前 20 张。")
|
doc.add_paragraph(f"... 共 {len(tif_files)} 张专题图,此处仅展示前 20 张。")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user