///|
pub fn research_csv(
  report : Json,
  field? : String = "rows",
) -> String raise TopologyError {
  let f = batch_object(report, "research report")
  let rows : Array[Json] = match f.get(field) {
    None => []
    Some(Null) => []
    Some(v) =>
      @json.from_json(v) catch {
        _ => raise TopologyError("report field is not rows")
      }
  }
  if rows.is_empty() {
    return ""
  }
  let first = batch_object(rows[0], "report row")
  let columns : Array[String] = []
  for key in first.keys() {
    match first[key] {
      Object(_) | Array(_) => ()
      _ => columns.push(key)
    }
  }
  columns.sort()
  let output : Array[Array[Json]] = []
  for row in rows {
    let fields = batch_object(row, "report row")
    output.push(
      columns.map(fn(key) {
        match fields.get(key) {
          Some(value) => value
          None => Json::null()
        }
      }),
    )
  }
  research_csv_rows(columns, output)
}

///|
/// Offline display only: statistics and segmentation are precomputed in MoonBit.
pub fn research_html(report : Json) -> String {
  let data = report.stringify().replace_all(old="<", new="\\u003c")
  let head =
    #|MoonTopoLens 研究报告
    #|

MoonTopoLens 研究报告

离线查看计算结果、原始数据位置与分析条件。

分析设置与摘要

#|

结果图

#|

结果明细

选择一行可查看对应原始数据、区间或空间区域;完整结果保存在 report.json 和配套 CSV 中。

accessible_report(head + data + tail) } ///| /// Named files are computed before creating an output directory in the CLI. pub fn research_files( report : Json, ) -> Array[(String, String)] raise TopologyError { let f = batch_object(report, "research report") let files = [ ("report.json", report.stringify()), ("rows.csv", research_csv(report)), ("report.html", research_html(report)), ] if f.contains("request") { files.push(("study-request.json", required_field(f, "request").stringify())) } for field in [ "features", "envelopes", "samples", "anomaly_segments", "invalid_windows", "evaluation", "clearance_connectivity", ] { if f.get(field) is Some(Array(_)) { files.push((field + ".csv", research_csv(report, field~))) } } if batch_string(f, "kind") == "pores" { files.push( ( "labels.json", ( { "shape_xyz": required_field(f, "shape_xyz"), "spacing": required_field(f, "spacing"), "origin": required_field(f, "origin"), "unit": required_field(f, "unit"), "labels": required_field(f, "labels"), } : Json).stringify(), ), ) if integer_field(f, "surface_faces", 0) <= 20000 { files.push(("pores.obj", pore_surface_obj(report))) } else { files.push( ( "mesh-status.json", "{\"exported\":false,\"reason\":\"surface exceeds 20000-face export budget\"}", ), ) } } files }