第九课:报告生成——从数据到决策,把算法结果翻译成人类行动
第九课:报告生成——从数据到决策,把算法结果翻译成人类行动
为什么排第九?
前八课的终点是什么?是第七课的 12 类几何特征、是第六课的曲率数值、是第三课的拓扑关系。但这些数据躺在内存里,用户看不见、看不懂、更不知道该做什么。
“你有 3 处薄壁、2 个孔洞、1 处悬垂”——这是数据。
“薄壁区域壁厚 0.6mm,低于最低可打印厚度 0.8mm,建议加厚至 1.0mm;悬垂面朝下面积 23cm²,预计需要 15g 支撑材料”——这是决策。
报告生成 = 把数据翻译成决策。这是整个管线的最后一公里,也是你理念 1.6(软实力)的集中体现——技术再强,说不清楚就等于零。
费曼学习法 Step 1:用大白话解释它是什么
想象你是医生:
血常规报告上有 30 个数值(= 曲率、面积、厚度……)
你不会把 30 个数值直接甩给病人
你会说:”白细胞偏高,可能是感染,建议进一步检查”——数值→判断→行动
在 Huhb3D 中:
30 个数值 = 聚类的面积、边界、曲率类型、壁厚、法线方向……
判断 = “这是孔洞”、”这是薄壁”、”这是悬垂面”
行动 = “建议加厚”、”需要支撑”、”不可打印,请修改设计”
费曼学习法 Step 2:逐个拆解核心知识点
📦 A. 报告的数据源——前八课的成果汇总
A1. 报告需要什么数据?从哪来?
┌──────────────────────────────────────────────────────────────┐
│ 报告的数据来源 │
├──────────────┬───────────────────┬───────────────────────────┤
│ 数据 │ 来源 │ 报告中的用途 │
├──────────────┼───────────────────┼───────────────────────────┤
│ 三角形数量 │ 第3课 STL解析 │ 模型规模描述 │
│ 顶点数量 │ 第3课 顶点合并 │ 同上 │
│ 网格边界数 │ 第3课 边哈希表 │ 流形完整性判定 │
│ 表面积 │ 第3课 三角形面积 │ 材料用量估算 │
│ 包围盒 │ 第4课 AABB │ 打印机尺寸适配 │
│ 壁厚 │ 第5课 光线投射 │ 薄壁风险 │
│ 高斯曲率 K │ 第6课 曲率计算 │ 曲面特征判定 │
│ 平均曲率 H │ 第6课 曲率计算 │ 凸凹判定 │
│ 聚类列表 │ 第7课 DFS聚类 │ 区域级分析 │
│ 几何特征分类 │ 第7课 12类分类 │ 问题区域标注 │
│ 法线方向分类 │ 第7课 6类法线 │ 悬垂面检测 │
│ 配置参数 │ 第8课 JSON解析 │ 阈值可追溯 │
└──────────────┴───────────────────┴───────────────────────────┘
A2. 数据分三层——从”原始”到”加工”
Layer 1: 原始数据(算法直接输出)
triangle_count, vertex_count, surface_area,
每个三角形的 K, H, thickness, normal
Layer 2: 聚合数据(第七课的 Cluster)
cluster_id, curvature_type, geometry_type,
area, boundary_edges, avg_thickness, normal_class
Layer 3: 诊断数据(本课生成)
severity, description, recommendation,
affected_area, risk_level
关键认知: 用户只看 Layer 3,但 Layer 3 的每一句话都能追溯到 Layer 1 的具体数值。可追溯 = 可信任。
📦 B. 严重度分级——不是所有问题都一样紧急
B1. 四级严重度
enum class Severity {
INFO, // 信息:无需行动
WARNING, // 警告:建议关注
CRITICAL, // 严重:必须处理
FATAL // 致命:不可打印
};
B2. 严重度映射规则
Severity get_severity(const Cluster& cluster, const Config& cfg) {
// ★ FATAL 优先——有一个致命问题就够了
if (cluster.geometry_type == 9 && // 薄壁
cluster.avg_thickness < cfg.thin_wall_threshold * 0.5f) {
return Severity::FATAL; // 壁厚不到阈值的一半→不可打印
}
if (cluster.is_non_manifold) {
return Severity::FATAL; // 非流形→无法切片
}
// ★ CRITICAL——必须处理但可以修
if (cluster.geometry_type == 9 && // 薄壁
cluster.avg_thickness < cfg.thin_wall_threshold) {
return Severity::CRITICAL;
}
if (cluster.geometry_type == 10) { // 悬垂面
return Severity::CRITICAL;
}
if (cluster.geometry_type == 8) { // 孔洞
return Severity::CRITICAL;
}
// ★ WARNING——建议关注
if (cluster.geometry_type == 2) { // 凸起
return Severity::WARNING; // 可能过热
}
if (cluster.geometry_type == 3) { // 凹陷
return Severity::WARNING; // 可能积水
}
if (cluster.geometry_type == 7) { // 鞍面凹谷
return Severity::WARNING; // 可能开裂
}
// ★ INFO——没事
return Severity::INFO; // 平面、凸台等
}
B3. ★ 为什么 FATAL 排最前?——又见决策瀑布
跟第七课的几何分类一样,严重度判定也是有优先级的瀑布:
致命问题(薄壁/非流形)→ 堵死一切,不可打印
严重问题(悬垂/孔洞) → 可以修,但不修就出事
警告问题(凸起/凹陷) → 建议关注,不修也大概率能打
信息问题(平面/凸台) → 无需行动
跟第七课的架构一脉相承: 先查致命的,再查严重的,最后查普通的。这是同一个设计模式在不同层级的应用。
AI坑点: AI 会把严重度写成平铺的 if-else,没有优先级概念:
// AI 的写法——没有优先级,薄壁0.3mm 和薄壁0.7mm 同等严重
if (cluster.geometry_type == 9) return Severity::CRITICAL;
薄壁 0.3mm(不可打印)和薄壁 0.7mm(勉强能打),AI 给了同样的 CRITICAL——用户无法区分”必须改”和”建议改”。
📦 C. 描述生成——从枚举值到人类语言
C1. 描述模板系统
struct IssueDescription {
string title; // 一句话标题
string detail; // 详细描述
string location; // 位置信息
string recommendation; // 建议行动
};
IssueDescription describe_cluster(const Cluster& c, const Config& cfg) {
IssueDescription desc;
// ★ title:用人类语言替代枚举值
switch (c.geometry_type) {
case 0: desc.title = "碎片区域"; break;
case 1: desc.title = "平面区域"; break;
case 2: desc.title = "凸起区域"; break;
case 3: desc.title = "凹陷区域"; break;
case 8: desc.title = "孔洞"; break;
case 9: desc.title = "薄壁区域"; break;
case 10: desc.title = "悬垂面"; break;
case 11: desc.title = "凸台"; break;
// ...
}
// ★ detail:填入具体数值
desc.detail = format(
"面积: %.1f mm², 平均壁厚: %.2f mm, 边界边: %d 条",
c.area, c.avg_thickness, c.boundary_edges
);
// ★ location:用包围盒描述位置
AABB box = compute_cluster_aabb(c);
desc.location = format(
"位于 (%.1f, %.1f, %.1f) 至 (%.1f, %.1f, %.1f)",
box.min_x, box.min_y, box.min_z,
box.max_x, box.max_y, box.max_z
);
// ★ recommendation:严重度决定语气
Severity sev = get_severity(c, cfg);
switch (c.geometry_type) {
case 9: // 薄壁
if (sev == Severity::FATAL)
desc.recommendation = format(
"壁厚 %.2f mm 远低于最低要求 %.1f mm,"
"当前设计不可打印,建议加厚至 %.1f mm 以上",
c.avg_thickness, cfg.thin_wall_threshold,
cfg.thin_wall_threshold * 1.2f);
else
desc.recommendation = format(
"壁厚 %.2f mm 低于建议值 %.1f mm,"
"可能导致强度不足,建议加厚至 %.1f mm",
c.avg_thickness, cfg.thin_wall_threshold,
cfg.thin_wall_threshold * 1.2f);
break;
case 10: // 悬垂面
desc.recommendation = format(
"检测到 %.1f mm² 朝下面,需要添加支撑结构,"
"预计支撑材料用量约 %.1f g",
c.area, estimate_support_weight(c));
break;
case 8: // 孔洞
desc.recommendation = "孔洞区域需后处理(钻孔/攻丝),"
"建议预留加工余量 0.2mm";
break;
// ...
}
return desc;
}
C2. ★ “语气”随严重度变化
严重度 语气 示例
INFO 中性陈述 “平面区域,面积 120mm²”
WARNING 建议语气 “凹陷区域可能积水,建议检查排水设计”
CRITICAL 祈使语气 “必须添加支撑结构”
FATAL 禁止语气 “当前设计不可打印,壁厚远低于最低要求”
这又是”出题人”的权力——你定义了”什么语气对应什么严重度”,AI 只负责填词。
AI坑点: AI 生成的描述语气千篇一律——全是”建议检查”,壁厚 0.3mm 不可打印和壁厚 0.7mm 勉强能打,AI 给的是同一种”建议”。用户看了不知道哪些必须改、哪些可以不管。
📦 D. 支撑材料估算——从面积到成本
D1. 为什么需要估算支撑用量?
3D打印的用户最关心两个成本:时间和材料。悬垂面需要支撑结构,支撑结构消耗额外材料,而且事后要拆除——拆除时间 + 材料浪费 + 表面质量下降。
D2. 估算公式
float estimate_support_weight(const Cluster& cluster) {
// 简化模型:支撑体积 = 悬垂面积 × 平均支撑高度 × 填充率
// 支撑高度 ≈ 从悬垂面到打印平台(Y=0)的距离
// 填充率 ≈ 15%(网格状支撑)
const float support_density = 0.15f; // 支撑填充率
const float material_density = 1.25f; // PLA 密度 g/cm³
// 平均支撑高度 = 悬垂面中心 Y 坐标
float avg_y = cluster.centroid_y;
float support_height = max(avg_y, 1.0f); // 最低 1mm
// 支撑体积 (mm³ → cm³)
float support_volume_mm3 = cluster.area * support_height * support_density;
float support_volume_cm3 = support_volume_mm3 / 1000.0f;
return support_volume_cm3 * material_density;
}
D3. ★ “够用就可以”的工程判断
这个估算极其粗糙——真实支撑结构是树状/网格状,高度是变化的,密度也不是常数。但估算的目的不是精确计算,而是给用户一个量级概念:
0.5g → “基本不需要额外材料”
15g → “多费一点材料,问题不大”
80g → “支撑材料占比很高,考虑调整打印方向”
量级对了,决策就对。精确到 0.01g 的支撑估算没有意义——打印过程本身的误差就远超这个精度。
📦 E. 报告结构——信息架构
E1. 报告的层次结构
┌──────────────────────────────────────────────────────────────┐
│ Huhb3D 分析报告 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 一、模型概览 │
│ ├── 文件名、文件大小、格式 │
│ ├── 三角形数量、顶点数量 │
│ ├── 包围盒尺寸 │
│ ├── 总表面积、估算体积 │
│ └── 可打印性总判定:✅可打印 / ⚠️有风险 / ❌不可打印 │
│ │
│ 二、问题总览 │
│ ├── 严重度分布:FATAL ×0, CRITICAL ×2, WARNING ×3, INFO ×5│
│ ├── 问题类型分布:薄壁×1, 悬垂×1, 凹陷×2, 碎片×1… │
│ └── 风险地图(3D可视化标注,后续课展开) │
│ │
│ 三、问题详情 │
│ ├── ❌ FATAL(如果有) │
│ │ └── [问题1] 薄壁区域 - 壁厚0.3mm… │
│ ├── 🔴 CRITICAL │
│ │ ├── [问题2] 悬垂面 - 面积23mm²… │
│ │ └── [问题3] 孔洞 - 直径5mm… │
│ ├── 🟡 WARNING │
│ │ ├── [问题4] 凹陷区域 - 可能积水… │
│ │ └── [问题5] 鞍面凹谷 - 应力集中… │
│ └── 🟢 INFO │
│ ├── [问题6] 平面区域 - 主体面… │
│ └── … │
│ │
│ 四、建议行动清单 │
│ ├── [必须] 加厚薄壁区域至1.0mm以上 │
│ ├── [必须] 为悬垂面添加支撑结构(预计15g支撑材料) │
│ ├── [建议] 检查孔洞后处理方案 │
│ └── [可选] 优化凹陷区域排水设计 │
│ │
│ 五、参数记录 │
│ ├── 使用阈值:K=0.5, H=0.01, thin_wall=0.8mm │
│ └── 分析时间:2.3s │
└──────────────────────────────────────────────────────────────┘
E2. ★ 倒金字塔结构——最重要的放最前
新闻报道的”倒金字塔”:最重要的信息在第一段,细节在后。
报告同理:
可打印性总判定——用户第一个要看的就是”能不能打”
问题总览——“有几种问题,严重程度如何”
问题详情——“每个问题的具体数据”
建议行动——“我应该做什么”
参数记录——技术人员回头看用的
AI坑点: AI 生成的报告喜欢按”技术模块”排列——先写”曲率分析”,再写”拓扑分析”,再写”壁厚分析”。用户根本不关心技术模块,关心的是”我该改哪里”。
📦 F. 行动清单——从”诊断”到”处方”
F1. 行动优先级排序
struct Action {
string imperative; // 祈使句:”加厚薄壁区域”
string target; // 目标位置:”Y轴12-15mm处”
string metric; // 量化指标:”当前0.3mm → 建议1.0mm”
Priority priority; // MUST / SHOULD / COULD
float estimated_effort; // 估算修改难度 1-5
};
vector
const Config& cfg) {
vector
for (const auto& c : clusters) {
Severity sev = get_severity(c, cfg);
if (sev == Severity::INFO) continue; // INFO 不生成行动
Action act;
act.target = format_location(c);
switch (c.geometry_type) {
case 9: // 薄壁
act.imperative = "加厚薄壁区域";
act.metric = format("当前 %.2fmm → 建议 %.1fmm",
c.avg_thickness, cfg.thin_wall_threshold * 1.2f);
act.priority = (sev == Severity::FATAL) ?
Priority::MUST : Priority::SHOULD;
act.estimated_effort = (sev == Severity::FATAL) ? 4.0f : 2.0f;
break;
case 10: // 悬垂面
act.imperative = "添加支撑结构";
act.metric = format("悬垂面积 %.1fmm²,"
"预计支撑材料 %.1fg",
c.area, estimate_support_weight(c));
act.priority = Priority::MUST;
act.estimated_effort = 1.0f; // 加支撑很简单
break;
case 8: // 孔洞
act.imperative = "确认孔洞加工方案";
act.metric = format("孔洞面积 %.1fmm²,"
"建议预留 %.1fmm 加工余量",
c.area, 0.2f);
act.priority = Priority::SHOULD;
act.estimated_effort = 2.0f;
break;
// ... 其他类型
}
actions.push_back(act);
}
// ★ 按优先级 + 修改难度排序
sort(actions.begin(), actions.end(),
[](const Action& a, const Action& b) {
if (a.priority != b.priority)
return a.priority < b.priority; // MUST 排最前
return a.estimated_effort < b.estimated_effort; // 简单的排前
});
return actions;
}
F2. ★ 行动排序的业务逻辑
MUST + 简单 → 最先做(加支撑,一键搞定)
MUST + 复杂 → 其次做(加厚薄壁,需要改设计)
SHOULD + 简单 → 建议做(确认孔洞方案)
SHOULD + 复杂 → 可以不做(优化排水设计)
COULD + 任意 → 锦上添花
这又是你作为出题人的设计——你定义了”必须/建议/可选”的边界,你定义了”简单/复杂”的度量。AI 负责执行排序,但排序规则是你定的。
📦 G. 输出格式——一份报告,多种皮肤
G1. 三种输出格式
enum class ReportFormat {
JSON, // 给程序读(API/AI Agent 消费)
HTML, // 给人看(浏览器可视化)
TEXT // 给终端/日志用(快速查看)
};
G2. 同一份报告数据,三种表达
// 报告的内部表示(格式无关)
struct Report {
ModelOverview overview;
vector
vector
Config parameters;
float analysis_time;
};
// 格式化器接口
class ReportFormatter {
public:
virtual string format(const Report& report) = 0;
};
// JSON 格式——机器友好
class JsonFormatter : public ReportFormatter {
string format(const Report& r) override {
// 输出结构化 JSON
// { “overview”: {…}, “issues”: […], “actions”: […] }
// AI Agent 可以直接解析并做后续决策
}
};
// HTML 格式——人类友好
class HtmlFormatter : public ReportFormatter {
string format(const Report& r) override {
// 输出带样式的 HTML
// 严重度用颜色编码:FATAL=红, CRITICAL=橙, WARNING=黄, INFO=绿
// 行动清单用复选框样式
// 3D模型标注(后续课展开)
}
};
// TEXT 格式——终端友好
class TextFormatter : public ReportFormatter {
string format(const Report& r) override {
// 纯文本 + Unicode 符号
// ❌ FATAL: 薄壁区域
// 🔴 CRITICAL: 悬垂面
// 🟡 WARNING: 凹陷区域
}
};
G3. ★ 策略模式——为什么要做格式分离?
不分离(AI 的写法):
generate_report() {
// 写 HTML 字符串拼接
html += “
html += issue.title;
html += “
// 想加 JSON 输出?改整个函数…
}
分离(设计模式):
Report data = analyze(mesh, config); // 生成数据
string output = formatter->format(data); // 格式化
// 想加新格式?加一个 Formatter 子类,不改数据逻辑
这是开闭原则——对扩展开放(加新格式),对修改关闭(不改已有代码)。
AI坑点: AI 默认把格式化逻辑写在分析逻辑里,HTML 字符串和业务代码混在一起。想加 JSON 输出就得改分析函数,改一个格式所有格式都可能出 bug。
📦 H. 可打印性总判定——报告的”一句话结论”
H1. 判定规则
Printability assess_printability(const Report& report) {
int fatal_count = 0;
int critical_count = 0;
for (const auto& issue : report.issues) {
if (issue.severity == Severity::FATAL) fatal_count++;
if (issue.severity == Severity::CRITICAL) critical_count++;
}
if (fatal_count > 0) {
return {
.verdict = Verdict::NOT_PRINTABLE,
.summary = format(
"检测到 %d 个致命问题,当前设计不可打印。"
"请优先解决:", fatal_count),
.icon = "❌"
};
}
if (critical_count > 0) {
return {
.verdict = Verdict::RISKY,
.summary = format(
"检测到 %d 个严重问题,打印前必须处理。"
"预计需要额外材料 %.1fg 用于支撑。",
critical_count, total_support_weight),
.icon = "⚠️"
};
}
int warning_count = count_warnings(report);
if (warning_count > 0) {
return {
.verdict = Verdict::PRINTABLE_WITH_CAUTION,
.summary = format(
"未发现严重问题,但有 %d 个警告项建议关注。",
warning_count),
.icon = "🟡"
};
}
return {
.verdict = Verdict::SAFE_TO_PRINT,
.summary = "模型分析通过,可以安全打印。",
.icon = "✅"
};
}
H2. ★ 这个判定是整个系统的”出口”
用户可能不看报告的任何细节,只看这一行:
❌ 不可打印 → 用户改设计,重来
⚠️ 有风险 → 用户看建议,决定要不要改
🟡 需注意 → 用户继续打印,心里有数
✅ 可打印 → 用户放心打印
这就是**”定义什么方案更符合人性”**(理念 1.1)的最终体现——你定义了”可打印”的标准,整个系统十几万行代码,最终浓缩成这四个字。
费曼学习法 Step 3:完整流水线
┌──────────────────────────────────────────────────────────────┐
│ 前置:前八课的所有成果 │
│ │
│ Layer 1: 原始数据 (三角形/顶点/边/曲率/壁厚/法线) │
│ Layer 2: 聚合数据 (Cluster 列表 + 几何特征分类) │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 1: 严重度分级 │
│ │
│ 每个 Cluster → Severity (FATAL/CRITICAL/WARNING/INFO) │
│ 决策瀑布: 致命薄壁? → 非流形? → 严重薄壁? → 悬垂? → 孔洞? │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 2: 描述生成 │
│ │
│ 每个问题 → IssueDescription {title, detail, location, rec} │
│ 语气随严重度变化:禁止 / 祈使 / 建议 / 中性 │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 3: 行动清单生成 │
│ │
│ 每个非 INFO 问题 → Action {imperative, metric, priority} │
│ 支撑材料估算 → 量化成本 │
│ 按优先级 × 修改难度排序 │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 4: 可打印性总判定 │
│ │
│ 有 FATAL? → ❌不可打印 │
│ 有 CRITICAL? → ⚠️有风险 │
│ 有 WARNING? → 🟡需注意 │
│ 全 INFO? → ✅可打印 │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 5: 格式化输出 │
│ │
│ Report 内部表示 → Formatter → JSON / HTML / TEXT │
│ 策略模式:数据与格式分离,扩展不加改 │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ 用户 / AI Agent │
│ │
│ 看到总判定 → 做决策 │
│ 看到行动清单 → 知道怎么改│
│ 看到详情 → 理解为什么 │
└────────────────────────┘
费曼学习法 Step 4:识别你的知识缺口
# 自检问题 如果你答不出
1 报告的三层数据(原始/聚合/诊断)有什么区别?为什么需要分层? 原始数据机器用,聚合数据算法用,诊断数据人用——每层面向不同消费者
2 薄壁 0.3mm 和薄壁 0.7mm 为什么不能给同一个严重度? 用户需要知道”必须改”和”建议改”的区别——严重度是行动指引,不是分类标签
3 描述的语气为什么要随严重度变化? 语气=紧迫感——所有问题都用”建议”语气,用户分不清轻重缓急
4 支撑材料估算那么粗糙,为什么还要算? 量级对了决策就对——0.5g 和 80g 的决策完全不同,不需要精确到 0.01g
5 报告为什么用倒金字塔而不是按技术模块排列? 用户关心”我该做什么”,不关心”你用了什么算法”——以用户需求组织信息
6 策略模式在这个场景解决什么问题? 数据逻辑和格式逻辑分离——加 JSON 输出不改分析代码
7 可打印性总判定的四个级别,背后的决策逻辑是什么? FATAL 数量 → 决定能不能打,CRITICAL 数量 → 决定要不要额外准备,WARNING → 提醒,全绿 → 放心
费曼学习法 Step 5:用你的话复述
“前八课产出了大量数据——曲率、面积、壁厚、聚类、特征分类。但用户不要数据,要决策。报告生成就是把数据翻译成决策的最后一公里。
具体做法:先给每个问题定严重度(FATAL/CRITICAL/WARNING/INFO),用决策瀑布——致命问题优先;然后生成人类语言的描述,语气随严重度变化——FATAL用禁止语气,WARNING用建议语气;再生成行动清单,按’必须/建议/可选’排序,最简单且最紧急的排最前;最后给出可打印性总判定——四个级别,用户可能只看这一行。
输出用策略模式:同一份 Report 数据,JSON 给机器读,HTML 给人看,TEXT 给终端用。加新格式不改分析逻辑。
支撑材料估算是’够用就可以’的典型——粗糙但量级对,决策就对。整个报告的信息架构是倒金字塔:总判定最前,详情在后,参数最后。用户从上往下看,看到任何一层都能做决策。”
📌 本课与你的 AI 时代理念的映射
你的理念 本课体现
1.1 当出题人 严重度定义、语气规则、行动优先级、可打印性标准——全是出题人的设计。”什么是不可打印”是你定义的
1.2 技术是切入点 策略模式、决策瀑布是技术,但”为什么报告用倒金字塔”是产品思维——技术为用户服务
1.4 架构能力 三层数据分离、策略模式解耦、报告信息架构——每一层为谁服务、怎么组织,是架构设计
1.5 AI代码有坑 AI 平铺严重度(不分FATAL/CRITICAL)、语气千篇一律、格式和分析耦合、按技术模块排列报告——每个都”能看但没用”
1.6 软实力 报告本身就是软实力——把”椭圆凹 K<0 H<0 boundary_edges=47”翻译成”孔洞,需要后处理”,这是跟用户对话的能力
1.3 技术够用就可以 支撑估算不追求精确、TEXT格式只有基础排版——够用就行,过度设计是浪费


