"""
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"""
🏆 推荐批次: {top_batch.batch_name}
评分: {top_batch.score}
理由: {top_batch.reason}
"""
return f"""
核心结论
{summary_text}
{highlights}
"""
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'推荐' if item.rank == 1 else ""
score_cell = f"{item.score} | " if show_scores else ""
rows.append(f"""
| {item.rank} {rank_badge} |
{item.batch_name} |
{score_cell}
{item.reason} |
""")
score_header = "评分 | " if show_scores else ""
return f"""
批次排名
| 排名 |
批次名称 |
{score_header}
评价理由 |
{''.join(rows)}
"""
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"""
| {timepoint} |
{pred.point_estimate:.2f}% |
{pred.CI_lower:.2f}% - {pred.CI_upper:.2f}% |
{status} |
""")
return f"""
货架期预测
| 时间点 |
点预测 |
95% 置信区间 |
状态 |
{''.join(rows)}
"""
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"""
| {condition} |
{fit.model_type} |
{k_str} |
{r2_str} |
{se_str} |
{confidence} |
""")
if not rows:
return ""
return f"""
动力学建模结果
模型: 零级降解动力学 y(t) = y₀ + k·t
| 条件 |
模型 |
k (%/月) |
R² |
SE(k) |
置信度 |
{''.join(rows)}
"""
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"""
趋势对比可视化
图表生成中...(整合现有 SVG 绘图逻辑)
"""
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"""
"""
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"""
"""
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"""
法规合规性
ICH Q1E: 本报告遵循 ICH Q1E 稳定性数据评价指导原则。
本分析符合以下法规要求:
- ICH Q1A(R2): 稳定性试验设计
- ICH Q1E: 稳定性数据评价
- WHO TRS 953: 统计学方法
"""
# 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,
}