feat(export): 函数图像静态渲染(function-plot → SVG)

- 新增 app/plot 包:白名单表达式解析(ast 无 eval)+ FunctionPlot 模型 + 静态 SVG 渲染
- HtmlExporter 的 function_plot 节点解析并内嵌 SVG,解析失败回退占位并转诊断
- 新增 test_plot.py(13 个测试)覆盖表达式安全、指令解析、SVG 输出与导出链路集成

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
yxx
2026-09-04 22:41:35 +08:00
co-authored by Claude Code
parent 64af1f5165
commit f49d1245a1
6 changed files with 804 additions and 5 deletions
+55
View File
@@ -0,0 +1,55 @@
"""Function Plot 内部数据模型。
契约 §12.2 的 FunctionPlot 结构与 §10.4 的 StaticRenderResult 只在导出链路的后端内部
流转,不进入 HTTP 契约,因此与 Document AST 一样放在独立包内,不进 contracts.py。
"""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
class FunctionPlotExpression(BaseModel):
"""单条函数表达式;expression 为数学表达式文本(不含 ``y =`` 前缀)。"""
expression: str
label: str | None = None
color: str | None = None
class PlotAxes(BaseModel):
xlabel: str | None = None
ylabel: str | None = None
grid: bool = True
class FunctionPlot(BaseModel):
version: int = 1
expressions: list[FunctionPlotExpression]
domain: tuple[float, float] = (-10.0, 10.0)
range: tuple[float, float] | None = None
axes: PlotAxes = Field(default_factory=PlotAxes)
class PlotDiagnostic(BaseModel):
severity: Literal["warning", "error"]
code: str
message: str
line: int | None = None
class FunctionPlotParseResult(BaseModel):
"""解析结果:任一表达式 error 时 plot 为 None(整块回退占位),仅 warning 时 plot 有效。"""
plot: FunctionPlot | None = None
diagnostics: list[PlotDiagnostic] = Field(default_factory=list)
class StaticRenderResult(BaseModel):
content: str
mime_type: str = "image/svg+xml"
width: int
height: int
warnings: list[str] = Field(default_factory=list)