Report

platform

Report

Definition

A report is a structured output that aggregates, summarizes, and presents data for human interpretation and decision-making. Reports transform raw data (logs, metrics, scan results, test outputs) into actionable information by applying formatting, filtering, aggregation, and contextual explanation. They serve as the communication bridge between automated systems and human stakeholders.

In the Prismatic Platform, reports are generated at multiple levels: quality gate reports summarize codebase health, OSINT execution reports document intelligence findings, Perimeter reports present security ratings and compliance status, DD pipeline reports consolidate due diligence analysis, and session reports capture development activity for continuity across conversations. Reports are the primary output artifact of nearly every platform subsystem -- they are the "deliverable" that makes automated analysis useful.

The platform treats reports as first-class artifacts: they are versioned, immutably stored, and cross-referenced with their generating commands and input data. This auditability is critical for compliance-oriented use cases like Czech CER (Critical Entity Regulation) reporting and M&A due diligence where report provenance must be traceable.

Core Concepts

Report Taxonomy

The platform generates several distinct report categories, each serving different stakeholders:

Report CategoryGenerated ByAudienceFormatStorage
Quality Gatemix quality.gatesDevelopersMarkdown/JSON.claude/reports/
Health Scoremix health.scoreCI/CD pipelineJSONstdout
Doctrine Compliancemix check.doctrinesCI/CD + developersText/JSONstdout
OSINT ExecutionOSINT tool adapterAnalystsJSON/HTMLETS audit trail
DD ContradictionDD analysis pipelineInvestigatorsHTML~/Desktop/
Security RatingPerimeter EASMSecurity teamJSON/PDFDatabase
Session Debrief/debrief commandDeveloper (next session)Markdown.claude/session-context/
Regression TestBug fix workflowCode reviewersMarkdownCommit message
CER Compliance/cer-report commandCompliance officersPDF/HTMLAudit archive
M&A Due Diligence/ma-report commandDeal teamHTML/PDFDeal workspace
Promo Enhancementmix promo.enhanceContent teamMarkdownstdout
Statistics Validationmix prismatic.stats.validatePlatform maintainersTextstdout

Report Pipeline Architecture

Every report follows the same four-stage pipeline:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   COLLECT    │ -> │  AGGREGATE  │ -> │   FORMAT    │ -> │   DELIVER   │
│              │    │             │    │             │    │             │
│ PostgreSQL   │    │ Statistics  │    │ Markdown    │    │ File system │
│ ETS tables   │    │ Trends      │    │ JSON        │    │ stdout      │
│ Git history  │    │ Comparisons │    │ HTML        │    │ Email       │
│ File system  │    │ Rankings    │    │ PDF         │    │ Dashboard   │
│ Meilisearch  │    │ Anomalies   │    │ CSV         │    │ API         │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘

Each stage is independent and composable. The same collected data can be aggregated differently for different audiences, formatted in multiple output formats, and delivered through various channels -- all without modifying the collection logic.

Report Immutability

Reports are immutable once generated. This is a fundamental architectural decision:

PropertyRationaleImplementation
Immutable contentAudit trail integrityWrite-once storage, no update APIs
TimestampedTemporal context for findingsgenerated_at in every report
Input-referencedReproducibilityStore input parameters and data hashes
Version-taggedSchema evolutionreport_version field in all reports
Author-attributedAccountabilityGenerator module and trigger recorded

Technical Deep Dive

Report Builder Architecture

The platform's report generation follows a builder pattern with composable sections:

defmodule PrismaticReporting.ReportBuilder do
  @moduledoc """
  Builds structured reports from platform data sources.
  Supports multiple output formats and delivery targets.

  Reports are immutable artifacts with full provenance tracking.
  Each report records its generating module, input parameters,
  and data hashes for reproducibility.

  ## Examples

      iex> sections = [%{title: "Summary", content: "All checks passed", data: %{}}]
      iex> {:ok, report} = PrismaticReporting.ReportBuilder.build("Quality Report", sections)
      iex> String.starts_with?(report, "# Quality Report")
      true
  """

  require Logger

  @type format :: :markdown | :json | :html | :csv
  @type section :: %{
    title: String.t(),
    content: String.t(),
    data: term(),
    severity: :info | :warning | :critical | nil
  }

  @type report_metadata :: %{
    generated_at: DateTime.t(),
    generator: module(),
    version: String.t(),
    input_hash: String.t(),
    format: format()
  }

  @spec build(String.t(), list(section()), format(), keyword()) :: {:ok, String.t()}
  def build(title, sections, format \\ :markdown, opts \\ []) do
    generator = Keyword.get(opts, :generator, __MODULE__)
    version = Keyword.get(opts, :version, "1.0.0")

    metadata = %{
      generated_at: DateTime.utc_now(),
      generator: generator,
      version: version,
      input_hash: compute_input_hash(sections),
      format: format
    }

    header = format_header(title, metadata, format)
    body = Enum.map_join(sections, "\n\n", &format_section(&1, format))
    footer = format_footer(metadata, format)

    report = header <> "\n\n" <> body <> "\n\n" <> footer

    Logger.info("Report generated",
      title: title,
      sections: length(sections),
      format: format,
      size_bytes: byte_size(report)
    )

    :telemetry.execute(
      [:prismatic, :report, :generated],
      %{sections: length(sections), size_bytes: byte_size(report)},
      %{title: title, format: format, generator: generator}
    )

    {:ok, report}
  end

  @spec build_multi_format(String.t(), list(section()), list(format())) :: %{format() => String.t()}
  def build_multi_format(title, sections, formats) do
    Map.new(formats, fn fmt ->
      {:ok, report} = build(title, sections, fmt)
      {fmt, report}
    end)
  end

  defp format_header(title, metadata, :markdown) do
    """
    # #{title}

    **Generated**: #{DateTime.to_iso8601(metadata.generated_at)}
    **Generator**: `#{inspect(metadata.generator)}`
    **Version**: #{metadata.version}
    **Input Hash**: `#{metadata.input_hash}`
    """
  end

  defp format_header(title, metadata, :json) do
    Jason.encode!(%{
      title: title,
      metadata: %{
        generated_at: metadata.generated_at,
        generator: inspect(metadata.generator),
        version: metadata.version,
        input_hash: metadata.input_hash
      }
    })
  end

  defp format_header(title, metadata, :html) do
    """
    <header>
      <h1>#{html_escape(title)}</h1>
      <div class="text-sm text-gray-400">
        <p>Generated: #{DateTime.to_iso8601(metadata.generated_at)}</p>
        <p>Version: #{metadata.version}</p>
      </div>
    </header>
    """
  end

  defp format_header(title, _metadata, :csv) do
    "# #{title}\n"
  end

  defp format_section(%{title: t, content: c, severity: severity}, :markdown) do
    severity_badge = case severity do
      :critical -> "**[CRITICAL]** "
      :warning -> "**[WARNING]** "
      _ -> ""
    end

    "## #{severity_badge}#{t}\n\n#{c}"
  end

  defp format_section(%{title: t, content: c, data: d}, :json) do
    Jason.encode!(%{section: t, content: c, data: d})
  end

  defp format_section(%{title: t, content: c, severity: severity}, :html) do
    severity_class = case severity do
      :critical -> "border-red-500 bg-red-900/20"
      :warning -> "border-yellow-500 bg-yellow-900/20"
      _ -> "border-gray-700 bg-gray-800"
    end

    """
    <section class="p-4 border rounded-lg mb-4 #{severity_class}">
      <h2 class="text-lg font-bold">#{html_escape(t)}</h2>
      <div class="prose">#{c}</div>
    </section>
    """
  end

  defp format_section(%{title: t, content: c}, :csv) do
    "\"#{t}\",\"#{String.replace(c, "\"", "\"\"")}\"\n"
  end

  defp format_footer(_metadata, :markdown), do: "---\n*Report generated by Prismatic Platform*"
  defp format_footer(_metadata, :json), do: ""
  defp format_footer(_metadata, :html), do: "<footer class=\"text-sm text-gray-500 mt-8\"><hr/><p>Report generated by Prismatic Platform</p></footer>"
  defp format_footer(_metadata, :csv), do: ""

  defp compute_input_hash(sections) do
    :crypto.hash(:sha256, :erlang.term_to_binary(sections))
    |> Base.encode16(case: :lower)
    |> binary_part(0, 12)
  end

  defp html_escape(text) do
    text
    |> String.replace("&", "&amp;")
    |> String.replace("<", "&lt;")
    |> String.replace(">", "&gt;")
  end
end

Specialized Report Types

Quality Gate Report

defmodule PrismaticReporting.QualityGateReport do
  @moduledoc """
  Generates quality gate assessment reports across all platform domains.
  Integrates with mix quality.gates for automated reporting.
  """

  alias PrismaticReporting.ReportBuilder

  @quality_domains ~w(compilation credo tests coverage docs specs forbidden_patterns
                      naming structure complexity documentation performance)a

  @spec generate(keyword()) :: {:ok, String.t()}
  def generate(opts \\ []) do
    format = Keyword.get(opts, :format, :markdown)

    sections =
      @quality_domains
      |> Enum.map(&assess_domain/1)
      |> Enum.sort_by(fn s -> severity_order(s.severity) end)

    summary = build_summary(sections)
    all_sections = [summary | sections]

    ReportBuilder.build("Quality Gate Assessment", all_sections, format,
      generator: __MODULE__,
      version: "2.0.0"
    )
  end

  defp assess_domain(domain) do
    # Domain-specific assessment logic
    %{
      title: "#{domain |> to_string() |> String.capitalize()} Domain",
      content: "Assessment for #{domain}",
      data: %{domain: domain, score: 0.0, issues: []},
      severity: :info
    }
  end

  defp build_summary(sections) do
    critical = Enum.count(sections, &(&1.severity == :critical))
    warnings = Enum.count(sections, &(&1.severity == :warning))
    passed = Enum.count(sections, &(&1.severity == :info))

    %{
      title: "Executive Summary",
      content: """
      | Status | Count |
      |--------|-------|
      | Passed | #{passed} |
      | Warnings | #{warnings} |
      | Critical | #{critical} |
      | **Total** | **#{length(sections)}** |
      """,
      data: %{critical: critical, warnings: warnings, passed: passed},
      severity: if(critical > 0, do: :critical, else: if(warnings > 0, do: :warning, else: :info))
    }
  end

  defp severity_order(:critical), do: 0
  defp severity_order(:warning), do: 1
  defp severity_order(_), do: 2
end

Regression Test Report

The mandatory regression test report is required after every bug fix per NO MERCY doctrine:

defmodule PrismaticReporting.RegressionReport do
  @moduledoc """
  Generates mandatory regression test reports for bug fixes.
  Required by NO MERCY doctrine -- every bug fix must include:
  - Bug description and root cause
  - Test file and test name
  - Validation that test fails before fix, passes after
  - NMND/NWB/NCLB compliance markers
  """

  @spec generate(map()) :: String.t()
  def generate(%{bug: bug, test_file: test_file, test_name: test_name} = report) do
    """
    ## Regression Test Report

    | Field | Value |
    |-------|-------|
    | **Bug Fixed** | #{bug} |
    | **Root Cause** | #{Map.get(report, :root_cause, "N/A")} |
    | **Test Added** | `#{test_file}` :: `#{test_name}` |
    | **Validation** | Test fails before fix, passes after fix |
    | **NMND Compliance** | No shortcuts, full implementation |
    | **NWB Compliance** | Permanent fix, no backwards compatibility |
    | **NCLB Compliance** | All claims validated, evidence updated |
    | **Evidence Status** | Claims automatically synchronized |
    """
  end
end

Session Debrief Report

defmodule PrismaticReporting.SessionDebrief do
  @moduledoc """
  Generates session debrief reports for development continuity.
  Stored in .claude/session-context/ with standardized naming.
  """

  @spec generate(map()) :: {:ok, String.t(), Path.t()}
  def generate(%{date: date, description: description} = session) do
    filename = "#{Date.to_iso8601(date)}-#{slugify(description)}-session.md"
    path = Path.join([".claude", "session-context", filename])

    content = """
    # Session Debrief: #{description}
    **Date**: #{Date.to_iso8601(date)}
    **Duration**: #{Map.get(session, :duration, "N/A")}

    ## Objectives
    #{format_list(Map.get(session, :objectives, []))}

    ## Actions Taken
    #{format_list(Map.get(session, :actions, []))}

    ## Files Modified
    #{format_list(Map.get(session, :files_modified, []))}

    ## Decisions Made
    #{format_list(Map.get(session, :decisions, []))}

    ## Recommended Next Steps
    #{format_list(Map.get(session, :next_steps, []))}

    ## Commits
    #{format_list(Map.get(session, :commits, []))}

    ---
    *Auto-generated by Prismatic Platform session debrief*
    """

    {:ok, content, path}
  end

  defp format_list([]), do: "- None recorded"
  defp format_list(items), do: Enum.map_join(items, "\n", &("- #{&1}"))

  defp slugify(text) do
    text
    |> String.downcase()
    |> String.replace(~r/[^a-z0-9]+/, "-")
    |> String.trim("-")
  end
end

Report Storage and Retrieval

defmodule PrismaticReporting.Storage do
  @moduledoc """
  Immutable report storage with provenance tracking.
  Reports are write-once artifacts stored with metadata.
  """

  require Logger

  @reports_dir ".claude/reports"

  @spec store(String.t(), String.t(), keyword()) :: {:ok, Path.t()} | {:error, term()}
  def store(category, content, opts \\ []) do
    timestamp = DateTime.utc_now() |> DateTime.to_unix()
    format = Keyword.get(opts, :format, :markdown)
    ext = format_extension(format)
    filename = "#{category}-#{timestamp}#{ext}"
    path = Path.join(@reports_dir, filename)

    with :ok <- File.mkdir_p(Path.dirname(path)),
         :ok <- File.write(path, content) do
      Logger.info("Report stored", path: path, size: byte_size(content))
      {:ok, path}
    end
  end

  @spec list(String.t()) :: list(Path.t())
  def list(category) do
    case File.ls(@reports_dir) do
      {:ok, files} ->
        files
        |> Enum.filter(&String.starts_with?(&1, category))
        |> Enum.sort(:desc)
        |> Enum.map(&Path.join(@reports_dir, &1))

      {:error, _} -> []
    end
  end

  @spec latest(String.t()) :: {:ok, String.t()} | {:error, :not_found}
  def latest(category) do
    case list(category) do
      [latest | _] -> File.read(latest)
      [] -> {:error, :not_found}
    end
  end

  defp format_extension(:markdown), do: ".md"
  defp format_extension(:json), do: ".json"
  defp format_extension(:html), do: ".html"
  defp format_extension(:csv), do: ".csv"
end

Usage in Prismatic Platform

Automated Report Generation

The platform generates reports at multiple trigger points:

TriggerReport TypeCommand
Pre-commit hookDoctrine compliancemix check.doctrines --changed
CI pipelineFull quality gatemix quality.gates
Manual invocationHealth scoremix health.score
Bug fix workflowRegression testEmbedded in commit
Session endDebrief/debrief command
OSINT executionTool resultsAutomatic on completion
DD analysisContradiction report/dd-run command
DeploymentSmoke testjust smoke-test production
ScheduledStatistics validationmix prismatic.stats.validate

OSINT Execution Reports

OSINT tool executions automatically generate structured reports stored in the ETS audit trail:

# OSINT execution produces a report automatically
{:ok, result} = PrismaticOsintCore.Toolbox.execute("czech-business-register", %{ico: "12345678"})
# Result includes: findings, execution_time, source_urls, confidence_scores
# Report stored in ETS with execution_id for audit retrieval

DD Contradiction Reports

The DD analysis pipeline generates HTML contradiction reports with cross-referenced findings:

# DD contradiction analysis produces comprehensive HTML report
{:ok, report_path} = PrismaticDd.ContradictionAnalyzer.analyze_and_report(case_id,
  output_dir: "~/Desktop/",
  format: :html,
  include_osint_verification: true
)
# Output: DD-Contradiction-Report-{case_name}.html

Common Pitfalls

PitfallProblemSolution
Manual report creationInconsistent format, missed sectionsAutomate with ReportBuilder
Mutable reportsAudit trail broken, can't reproduceWrite-once storage, no update APIs
Reports without actionable itemsData dump nobody readsEvery finding must include next steps
Missing provenanceCan't reproduce or verify reportInclude generator, version, input hash
Reports too broadOverwhelming, stakeholders ignoreScope to specific audience and purpose
No severity classificationCan't prioritize findingsTag sections as info/warning/critical

Best Practices

  1. Automate report generation -- manual reports introduce delay and inconsistency; trigger reports as part of CI/CD and session lifecycle.
  2. Include actionable recommendations -- data without interpretation creates work; each finding should suggest next steps.
  3. Version report formats -- when report structure changes, version the format so consumers can adapt.
  4. Store reports immutably -- audit trails require that historical reports cannot be modified after generation.
  5. Keep reports focused -- a report that covers everything communicates nothing; scope each report to a specific audience and purpose.
  6. Include provenance -- every report must record its generator, input data hash, and generation timestamp.
  7. Use severity classification -- tag sections as info/warning/critical so readers can triage effectively.
  8. Support multiple formats -- the same data should be renderable as Markdown (dev), HTML (stakeholder), JSON (automation), and CSV (analysis).
  9. Cross-reference with evidence -- link findings to source data, commits, or telemetry events for verification.
  10. Track report generation metrics -- monitor how often reports are generated, their size, and which sections contain findings.
  • Structured Log -- the raw data that feeds into reports
  • Visualization -- graphical representation complementing textual reports
  • Statistics -- the numerical summaries reports present
  • Triage -- the prioritization process reports inform
  • Dashboard -- real-time visualization complementing periodic reports
  • Telemetry -- metrics collection feeding report aggregation
  • KPI -- key performance indicators highlighted in reports
  • Audit Logging -- event recording that reports summarize
  • Quality Floor -- quality threshold enforced through gate reports
  • Regression -- mandatory report format for bug fixes

See Also


Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL