Preformu / utils /report_sections.py
Kevinshh's picture
Upload report_sections.py
1123211 verified
"""
Modular section renderers for dynamic report generation.
Each function renders one type of report section as HTML.
"""
from typing import Dict, Any, Optional
from schemas.analysis_intent import AnalysisIntent
from schemas.decision_result import RegulatoryDecisionResult
def render_executive_summary(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render executive summary with LLM-generated narrative."""
summary_text = explanations.get("executive_summary", "分析结果待生成")
focus = config.get("focus", "general")
# Add focus-specific highlights
highlights = ""
if focus == "top_batch_recommendation" and result and result.batch_ranking:
top_batch = result.batch_ranking[0]
highlights = f"""
<div class="alert alert-success">
<strong>🏆 推荐批次:</strong> {top_batch.batch_name}<br>
<strong>评分:</strong> {top_batch.score}<br>
<strong>理由:</strong> {top_batch.reason}
</div>
"""
return f"""
<div class="section">
<div class="section-title">核心结论</div>
<p>{summary_text}</p>
{highlights}
</div>
"""
def render_ranking_table(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render batch ranking table."""
if not result or not result.batch_ranking:
return ""
show_scores = config.get("show_scores", True)
rows = []
for item in result.batch_ranking:
rank_badge = f'<span class="badge badge-success">推荐</span>' if item.rank == 1 else ""
score_cell = f"<td>{item.score}</td>" if show_scores else ""
rows.append(f"""
<tr style="{'background-color: #d4edda; font-weight: bold;' if item.rank == 1 else ''}">
<td>{item.rank} {rank_badge}</td>
<td>{item.batch_name}</td>
{score_cell}
<td>{item.reason}</td>
</tr>
""")
score_header = "<th>评分</th>" if show_scores else ""
return f"""
<div class="section">
<div class="section-title">批次排名</div>
<table>
<thead>
<tr>
<th style="width:15%">排名</th>
<th style="width:25%">批次名称</th>
{score_header}
<th>评价理由</th>
</tr>
</thead>
<tbody>
{''.join(rows)}
</tbody>
</table>
</div>
"""
def render_prediction_table(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render shelf-life predictions table."""
if not result or not result.predictions:
return ""
# Only show valid predictions
valid_preds = {
tp: pred for tp, pred in result.predictions.items()
if pred.is_valid
}
if not valid_preds:
return render_data_quality_warning(
intent, result, explanations,
{"message": "由于数据不足或外推限制,无法生成有效的货架期预测。"}
)
rows = []
for timepoint, pred in valid_preds.items():
status = "合规" if pred.is_compliant() else "超标"
status_class = "badge-success" if pred.is_compliant() else "badge-warning"
rows.append(f"""
<tr>
<td>{timepoint}</td>
<td>{pred.point_estimate:.2f}%</td>
<td>{pred.CI_lower:.2f}% - {pred.CI_upper:.2f}%</td>
<td><span class="badge {status_class}">{status}</span></td>
</tr>
""")
return f"""
<div class="section">
<div class="section-title">货架期预测</div>
<table>
<thead>
<tr>
<th>时间点</th>
<th>点预测</th>
<th>95% 置信区间</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{''.join(rows)}
</tbody>
</table>
</div>
"""
def render_kinetic_modeling(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render kinetic modeling results."""
if not result or not result.kinetic_fits:
return ""
# Only show fits with meaningful data (not None)
rows = []
for condition, fit in result.kinetic_fits.items():
# Skip if no data
if fit.k is None and fit.R2 is None:
continue
k_str = f"{fit.k:.4f}" if fit.k is not None else "N/A"
r2_str = f"{fit.R2:.4f}" if fit.R2 is not None else "N/A"
se_str = f"{fit.SE_k:.3f}" if fit.SE_k is not None else "N/A"
# Confidence based on R²
if fit.R2 is not None:
if fit.R2 >= 0.95:
confidence = "高"
elif fit.R2 >= 0.8:
confidence = "中"
else:
confidence = "低"
else:
confidence = "低"
rows.append(f"""
<tr>
<td>{condition}</td>
<td>{fit.model_type}</td>
<td>{k_str}</td>
<td>{r2_str}</td>
<td>{se_str}</td>
<td>{confidence}</td>
</tr>
""")
if not rows:
return ""
return f"""
<div class="section">
<div class="section-title">动力学建模结果</div>
<div class="alert alert-info">
<strong>模型:</strong> 零级降解动力学 y(t) = y₀ + k·t
</div>
<table>
<thead>
<tr>
<th>条件</th>
<th>模型</th>
<th>k (%/月)</th>
<th>R²</th>
<th>SE(k)</th>
<th>置信度</th>
</tr>
</thead>
<tbody>
{''.join(rows)}
</tbody>
</table>
</div>
"""
def render_trend_visualization(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render trend comparison chart (placeholder - reuse existing SVG logic)."""
# This would integrate with existing chart generation
# For now, return a placeholder
return f"""
<div class="section">
<div class="section-title">趋势对比可视化</div>
<div class="alert alert-info">
图表生成中...(整合现有 SVG 绘图逻辑)
</div>
</div>
"""
def render_data_quality_warning(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render data quality warning."""
message = config.get("message", "数据质量存在限制,请谨慎使用分析结果。")
return f"""
<div class="section">
<div class="alert alert-warning">
<strong>⚠️ 数据质量提示:</strong><br>
{message}
</div>
</div>
"""
def render_recommendations(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render actionable recommendations."""
recs_text = explanations.get("recommendations", "建议进行进一步的稳定性研究。")
return f"""
<div class="section">
<div class="section-title">行动建议</div>
<div class="alert alert-success">
{recs_text}
</div>
</div>
"""
def render_regulatory_compliance(
intent: Optional[AnalysisIntent],
result: Optional[RegulatoryDecisionResult],
explanations: Dict[str, str],
config: Dict[str, Any]
) -> str:
"""Render regulatory compliance statement."""
return f"""
<div class="section">
<div class="section-title">法规合规性</div>
<div class="alert alert-info">
<strong>ICH Q1E:</strong> 本报告遵循 ICH Q1E 稳定性数据评价指导原则。
</div>
<p>本分析符合以下法规要求:</p>
<ul>
<li><strong>ICH Q1A(R2):</strong> 稳定性试验设计</li>
<li><strong>ICH Q1E:</strong> 稳定性数据评价</li>
<li><strong>WHO TRS 953:</strong> 统计学方法</li>
</ul>
</div>
"""
# Registry of all section renderers
SECTION_RENDERERS = {
"executive_summary": render_executive_summary,
"ranking_table": render_ranking_table,
"prediction_table": render_prediction_table,
"kinetic_modeling": render_kinetic_modeling,
"trend_visualization": render_trend_visualization,
"data_quality_warning": render_data_quality_warning,
"recommendations": render_recommendations,
"regulatory_compliance": render_regulatory_compliance,
}