///|
pub enum DiagnosticSeverity {
DiagHint
DiagWarning
DiagError
DiagBlocker
} derive(Debug, Eq)
///|
pub struct Diagnostic {
code : String
severity : DiagnosticSeverity
path : String
message : String
remediation : String
} derive(Debug, Eq)
///|
pub fn diagnostic(
code : String,
severity : DiagnosticSeverity,
path : String,
message : String,
remediation : String,
) -> Diagnostic {
{ code, severity, path, message, remediation }
}
///|
pub fn Diagnostic::is_blocking(self : Diagnostic) -> Bool {
self.severity is DiagError || self.severity is DiagBlocker
}
///|
pub fn Diagnostic::level(self : Diagnostic) -> String {
match self.severity {
DiagHint => "hint"
DiagWarning => "warning"
DiagError => "error"
DiagBlocker => "blocker"
}
}
///|
pub struct DiagnosticReport {
diagnostics : Array[Diagnostic]
passed : Bool
} derive(Debug, Eq)
///|
pub fn diagnostic_report(diagnostics : Array[Diagnostic]) -> DiagnosticReport {
let mut passed = true
for item in diagnostics {
if item.is_blocking() {
passed = false
}
}
{ diagnostics, passed }
}
///|
pub fn DiagnosticReport::count(self : DiagnosticReport) -> Int {
self.diagnostics.length()
}
///|
pub fn DiagnosticReport::blocking_count(self : DiagnosticReport) -> Int {
let mut count = 0
for item in self.diagnostics {
if item.is_blocking() {
count = count + 1
}
}
count
}
///|
pub fn DiagnosticReport::warnings(self : DiagnosticReport) -> Array[Diagnostic] {
let result : Array[Diagnostic] = []
for item in self.diagnostics {
if item.severity is DiagWarning {
result.push(item)
}
}
result
}
///|
pub fn DiagnosticReport::errors(self : DiagnosticReport) -> Array[Diagnostic] {
let result : Array[Diagnostic] = []
for item in self.diagnostics {
if item.is_blocking() {
result.push(item)
}
}
result
}
///|
pub fn DiagnosticReport::to_markdown(self : DiagnosticReport) -> String {
let lines : Array[String] = [
"| Code | Level | Path | Message | Remediation |", "| --- | --- | --- | --- | --- |",
]
for item in self.diagnostics {
lines.push(
"| \{item.code} | \{item.level()} | \{item.path} | \{item.message} | \{item.remediation} |",
)
}
lines.join("\n")
}
///|
pub fn diagnose_report(report : ChemReport) -> DiagnosticReport {
let diagnostics : Array[Diagnostic] = []
for issue in report.validate() {
diagnostics.push(
diagnostic(
"REPORT-VALIDATION",
DiagError,
issue.path,
issue.message,
"complete the missing report field",
),
)
}
for warning in report.warnings {
if warning.severity is Critical {
diagnostics.push(
diagnostic(
warning.code,
DiagBlocker,
"warnings",
warning.summary,
warning.action,
),
)
} else if warning.severity is Warning {
diagnostics.push(
diagnostic(
warning.code,
DiagWarning,
"warnings",
warning.summary,
warning.action,
),
)
}
}
diagnostic_report(diagnostics)
}
///|
pub fn ChemReport::diagnostics(self : ChemReport) -> DiagnosticReport {
diagnose_report(self)
}
///|
pub fn ChemReport::has_blocking_diagnostics(self : ChemReport) -> Bool {
!self.diagnostics().passed
}
///|
pub fn compare_reports(
left : ChemReport,
right : ChemReport,
) -> Array[Diagnostic] {
let result : Array[Diagnostic] = []
if left.metadata.process_unit != right.metadata.process_unit {
result.push(
diagnostic(
"DIFF-PROCESS-UNIT",
DiagHint,
"metadata.process_unit",
"process units differ",
"compare only like-for-like reports",
),
)
}
if left.results.length() != right.results.length() {
result.push(
diagnostic(
"DIFF-RESULT-COUNT",
DiagWarning,
"results",
"result counts differ",
"review changed calculation scope",
),
)
}
if left.warnings.length() != right.warnings.length() {
result.push(
diagnostic(
"DIFF-WARNING-COUNT",
DiagWarning,
"warnings",
"warning counts differ",
"review new risk signals",
),
)
}
result
}
///|
pub fn regression_gate(baseline : ChemReport, candidate : ChemReport) -> Bool {
compare_reports(baseline, candidate)
.filter(fn(item) {
item.severity is DiagBlocker || item.severity is DiagError
})
.length() ==
0
}
///|
pub fn required_sections(report : ChemReport) -> Array[String] {
let result : Array[String] = []
if report.assumptions.length() == 0 {
result.push("assumptions")
}
if report.formulas.length() == 0 {
result.push("formulas")
}
if report.sources.length() == 0 {
result.push("sources")
}
if report.sections.length() == 0 {
result.push("sections")
}
result
}
///|
pub fn completeness_score(report : ChemReport) -> Float {
let total : Float = 8.0
let mut present : Float = 0.0
if report.metadata.title.trim().length() > 0 {
present = present + 1.0
}
if report.summary.trim().length() > 0 {
present = present + 1.0
}
if report.assumptions.length() > 0 {
present = present + 1.0
}
if report.inputs.length() > 0 {
present = present + 1.0
}
if report.formulas.length() > 0 {
present = present + 1.0
}
if report.results.length() > 0 {
present = present + 1.0
}
if report.warnings.length() > 0 {
present = present + 1.0
}
if report.sources.length() > 0 {
present = present + 1.0
}
present / total
}