///|
/// Composable production report for quality, drift, risk and forecast review.
pub struct AnalyticsReport {
title : String
generated_at : String
sections : Array[ReportSection]
alerts : Int
score : Double
}
///|
pub struct ReportSection {
name : String
status : String
score : Double
metrics : Array[ReportMetric]
notes : Array[String]
}
///|
pub struct ReportMetric {
name : String
value : Double
unit : String
healthy : Bool
}
///|
pub fn report_metric(
name : String,
value : Double,
unit : String,
healthy : Bool,
) -> ReportMetric {
{ name, value, unit, healthy }
}
///|
pub fn report_section(
name : String,
status : String,
score : Double,
metrics : Array[ReportMetric],
notes : Array[String],
) -> ReportSection {
{ name, status, score, metrics, notes }
}
///|
pub fn analytics_report(
title : String,
generated_at : String,
) -> AnalyticsReport {
{ title, generated_at, sections: [], alerts: 0, score: 1.0 }
}
///|
fn report_recalculate(report : AnalyticsReport) -> AnalyticsReport {
if report.sections.length() == 0 {
return { ..report, score: 1.0 }
}
let mut total = 0.0
let mut alerts = 0
for section in report.sections {
total += section.score
if section.status != "healthy" {
alerts += 1
}
}
{ ..report, alerts, score: total / report.sections.length().to_double() }
}
///|
pub fn report_add_section(
report : AnalyticsReport,
section : ReportSection,
) -> AnalyticsReport {
report.sections.push(section)
report_recalculate(report)
}
///|
pub fn report_add_quality(
report : AnalyticsReport,
quality : QualityReport,
) -> AnalyticsReport {
let metrics = [
report_metric(
"completeness",
quality.completeness,
"ratio",
quality.completeness >= 0.99,
),
report_metric(
"duplicate_fraction",
quality.duplicate_fraction,
"ratio",
quality.duplicate_fraction <= 0.01,
),
report_metric(
"quality_score",
quality.quality_score,
"ratio",
quality.quality_score >= 0.95,
),
]
report_add_section(
report,
report_section(
"quality",
if quality.quality_score >= 0.95 {
"healthy"
} else {
"review"
},
quality.quality_score,
metrics,
[],
),
)
}
///|
pub fn report_add_drift(
report : AnalyticsReport,
drift : DriftReport,
) -> AnalyticsReport {
let score = 1.0 - transform_clip(drift.psi, 0.0, 1.0)
let metrics = [
report_metric("psi", drift.psi, "score", drift.psi < 0.2),
report_metric(
"js_divergence",
drift.js_divergence,
"score",
drift.js_divergence < 0.1,
),
report_metric(
"ks_statistic",
drift.ks_statistic,
"score",
drift.ks_statistic < 0.1,
),
report_metric(
"wasserstein",
drift.wasserstein,
"distance",
drift.wasserstein < 1.0,
),
]
report_add_section(
report,
report_section(
"drift",
if score >= 0.8 {
"healthy"
} else {
"review"
},
score,
metrics,
[],
),
)
}
///|
pub fn report_add_risk(
report : AnalyticsReport,
risk : RiskSnapshot,
) -> AnalyticsReport {
let score = transform_clip(1.0 - risk.maximum_drawdown, 0.0, 1.0)
let metrics = [
report_metric("center", risk.center, "return", true),
report_metric("scale", risk.scale, "return", risk.scale < 1.0),
report_metric(
"value_at_risk",
risk.value_at_risk,
"return",
risk.value_at_risk < 0.2,
),
report_metric(
"conditional_var",
risk.conditional_var,
"return",
risk.conditional_var < 0.2,
),
report_metric(
"max_drawdown",
risk.maximum_drawdown,
"ratio",
risk.maximum_drawdown < 0.2,
),
report_metric("sharpe", risk.sharpe, "ratio", risk.sharpe >= 0.0),
]
report_add_section(
report,
report_section(
"risk",
if score >= 0.8 {
"healthy"
} else {
"review"
},
score,
metrics,
[],
),
)
}
///|
pub fn report_add_forecast(
report : AnalyticsReport,
forecast : ForecastReport,
) -> AnalyticsReport {
let score = transform_clip(forecast.coverage, 0.0, 1.0)
let metrics = [
report_metric("mae", forecast.mae, "error", forecast.mae >= 0.0),
report_metric("rmse", forecast.rmse, "error", forecast.rmse >= 0.0),
report_metric(
"coverage",
forecast.coverage,
"ratio",
forecast.coverage >= 0.8,
),
report_metric(
"bias",
forecast.bias,
"error",
abs_double(forecast.bias) < forecast.rmse + 1.0e-12,
),
]
report_add_section(
report,
report_section(
"forecast",
if forecast.coverage >= 0.8 {
"healthy"
} else {
"review"
},
score,
metrics,
[],
),
)
}
///|
pub fn report_add_validation(
report : AnalyticsReport,
validation : ValidationReport,
) -> AnalyticsReport {
let status = if validation_has_errors(validation) {
"review"
} else {
"healthy"
}
let metrics = [
report_metric(
"validation_score",
validation.score,
"ratio",
validation.score >= 0.95,
),
report_metric(
"failed_checks",
validation.failed.to_double(),
"count",
validation.failed == 0,
),
]
report_add_section(
report,
report_section(
"validation",
status,
validation.score,
metrics,
validation_report_lines(validation),
),
)
}
///|
pub fn report_section_status(section : ReportSection) -> String {
section.status
}
///|
pub fn report_healthy_sections(report : AnalyticsReport) -> Int {
let mut count = 0
for section in report.sections {
if section.status == "healthy" {
count += 1
}
}
count
}
///|
pub fn report_metric_count(report : AnalyticsReport) -> Int {
let mut count = 0
for section in report.sections {
count += section.metrics.length()
}
count
}
///|
pub fn report_alert_count(report : AnalyticsReport) -> Int {
report.alerts
}
///|
pub fn report_metric_rows(report : AnalyticsReport) -> Array[Array[String]] {
let rows = [["section", "metric", "value", "unit", "healthy"]]
for section in report.sections {
for metric in section.metrics {
rows.push([
section.name,
metric.name,
metric.value.to_string(),
metric.unit,
metric.healthy.to_string(),
])
}
}
rows
}
///|
pub fn report_metric_table(report : AnalyticsReport) -> String {
let lines = []
for row in report_metric_rows(report) {
lines.push(row.join("\t"))
}
lines.join("\n")
}
///|
pub fn report_summary_lines(report : AnalyticsReport) -> Array[String] {
[
"title=" + report.title,
"generated_at=" + report.generated_at,
"sections=" + report.sections.length().to_string(),
"healthy_sections=" + report_healthy_sections(report).to_string(),
"alerts=" + report.alerts.to_string(),
"score=" + report.score.to_string(),
]
}
///|
pub fn report_lines(report : AnalyticsReport) -> Array[String] {
let lines = report_summary_lines(report)
for section in report.sections {
lines.push(
"[" +
section.name +
"] status=" +
section.status +
" score=" +
section.score.to_string(),
)
for metric in section.metrics {
lines.push(
"metric=" +
metric.name +
" value=" +
metric.value.to_string() +
" unit=" +
metric.unit +
" healthy=" +
metric.healthy.to_string(),
)
}
for note in section.notes {
lines.push("note=" + note)
}
}
lines
}
///|
pub fn report_string(report : AnalyticsReport) -> String {
report_lines(report).join("\n")
}
///|
pub fn report_quality_score(report : AnalyticsReport) -> Double {
report.score
}
///|
pub fn report_is_healthy(report : AnalyticsReport, threshold : Double) -> Bool {
report.score >= threshold && report.alerts == 0
}
///|
pub fn report_section_scores(report : AnalyticsReport) -> Array[Double] {
let result = []
for section in report.sections {
result.push(section.score)
}
result
}
///|
pub fn report_section_names(report : AnalyticsReport) -> Array[String] {
let result = []
for section in report.sections {
result.push(section.name)
}
result
}
///|
pub fn report_compare(
left : AnalyticsReport,
right : AnalyticsReport,
) -> Array[Double] {
[
left.score,
right.score,
right.score - left.score,
left.alerts.to_double(),
right.alerts.to_double(),
]
}
///|
pub fn report_from_data(data : Array[Double], name : String) -> AnalyticsReport {
let quality = quality_report(data, quality_default_rule())
let validation = validation_summary(data, name)
report_add_validation(
report_add_quality(analytics_report(name, "local"), quality),
validation,
)
}
///|
pub fn report_from_baseline(
baseline : Array[Double],
current : Array[Double],
name : String,
) -> AnalyticsReport {
let first = report_from_data(current, name)
let drift = drift_report(baseline, current, drift_default_rule())
report_add_drift(first, drift)
}
///|
pub fn report_alert_messages(report : AnalyticsReport) -> Array[String] {
let result = []
for section in report.sections {
if section.status != "healthy" {
result.push(section.name + " requires review")
}
for metric in section.metrics {
if !metric.healthy {
result.push(section.name + ":" + metric.name)
}
}
}
result
}
///|
pub fn report_digest(report : AnalyticsReport) -> Array[Double] {
[
report.score,
report.sections.length().to_double(),
report_healthy_sections(report).to_double(),
report.alerts.to_double(),
report_metric_count(report).to_double(),
]
}
///|
pub fn report_render_markdown(report : AnalyticsReport) -> String {
let lines = [
"# " + report.title,
"",
"- Generated: " + report.generated_at,
"- Score: " + report.score.to_string(),
"- Alerts: " + report.alerts.to_string(),
"",
]
for section in report.sections {
lines.push("## " + section.name)
lines.push("Status: " + section.status + " ")
lines.push("Score: " + section.score.to_string())
for metric in section.metrics {
lines.push(
"- " + metric.name + ": " + metric.value.to_string() + " " + metric.unit,
)
}
lines.push("")
}
lines.join("\n")
}
///|
pub fn report_render_json_fields(report : AnalyticsReport) -> Array[String] {
[
"title=" + report.title,
"generated_at=" + report.generated_at,
"score=" + report.score.to_string(),
"alerts=" + report.alerts.to_string(),
"metric_count=" + report_metric_count(report).to_string(),
]
}