///|
/// Bounded metric history for dashboards and regression tests.
pub struct MetricSeries {
name : String
capacity : Int
values : Array[Double]
mut seen : Int
}
///|
pub fn MetricSeries::new(name : String, capacity? : Int = 256) -> MetricSeries {
{
name,
capacity: if capacity < 1 {
1
} else {
capacity
},
values: [],
seen: 0,
}
}
///|
pub fn MetricSeries::record(self : MetricSeries, value : Double) -> Unit {
self.values.push(value)
self.seen += 1
if self.values.length() > self.capacity {
let _ = self.values.remove(0)
}
}
///|
pub fn MetricSeries::name(self : MetricSeries) -> String {
self.name
}
///|
pub fn MetricSeries::values(self : MetricSeries) -> Array[Double] {
copy_vector(self.values)
}
///|
pub fn MetricSeries::seen(self : MetricSeries) -> Int {
self.seen
}
///|
pub fn MetricSeries::last(self : MetricSeries) -> Double? {
self.values.last()
}
///|
pub fn MetricSeries::mean(self : MetricSeries) -> Double {
mean_values(self.values)
}
///|
pub fn MetricSeries::minimum(self : MetricSeries) -> Double {
if self.values.is_empty() {
0.0
} else {
self.values.fold(init=self.values[0], (current, value) => {
if value < current {
value
} else {
current
}
})
}
}
///|
pub fn MetricSeries::maximum(self : MetricSeries) -> Double {
if self.values.is_empty() {
0.0
} else {
self.values.fold(init=self.values[0], (current, value) => {
if value > current {
value
} else {
current
}
})
}
}
///|
pub fn MetricSeries::trend(self : MetricSeries) -> Double {
if self.values.length() < 2 {
0.0
} else {
self.values[self.values.length() - 1] - self.values[0]
}
}
///|
pub fn MetricSeries::reset(self : MetricSeries) -> Unit {
self.values.clear()
self.seen = 0
}
///|
pub struct ModelHealth {
mut accepted : Int
mut rejected : Int
mut prediction_errors : Int
mut inference_failures : Int
latency : MetricSeries
loss : MetricSeries
}
///|
pub fn ModelHealth::new() -> ModelHealth {
{
accepted: 0,
rejected: 0,
prediction_errors: 0,
inference_failures: 0,
latency: MetricSeries::new("latency_ms"),
loss: MetricSeries::new("loss"),
}
}
///|
pub fn ModelHealth::record_sample(
self : ModelHealth,
accepted : Bool,
loss : Double,
latency_ms : Double,
) -> Unit {
if accepted {
self.accepted += 1
} else {
self.rejected += 1
}
self.loss.record(loss)
self.latency.record(latency_ms)
}
///|
pub fn ModelHealth::record_error(self : ModelHealth) -> Unit {
self.inference_failures += 1
}
///|
pub fn ModelHealth::record_prediction_error(self : ModelHealth) -> Unit {
self.prediction_errors += 1
}
///|
pub fn ModelHealth::accepted(self : ModelHealth) -> Int {
self.accepted
}
///|
pub fn ModelHealth::rejected(self : ModelHealth) -> Int {
self.rejected
}
///|
pub fn ModelHealth::inference_failures(self : ModelHealth) -> Int {
self.inference_failures
}
///|
pub fn ModelHealth::acceptance_rate(self : ModelHealth) -> Double {
let total = self.accepted + self.rejected
if total == 0 {
0.0
} else {
self.accepted.to_double() / total.to_double()
}
}
///|
pub fn ModelHealth::mean_loss(self : ModelHealth) -> Double {
self.loss.mean()
}
///|
pub fn ModelHealth::p95_latency(self : ModelHealth) -> Double {
let values = self.latency.values()
if values.is_empty() {
0.0
} else {
values.sort()
values[(0.95 * (values.length() - 1).to_double()).to_int()]
}
}
///|
pub fn ModelHealth::reset(self : ModelHealth) -> Unit {
self.accepted = 0
self.rejected = 0
self.prediction_errors = 0
self.inference_failures = 0
self.latency.reset()
self.loss.reset()
}
///|
pub struct ModelCard {
name : String
version : String
task : String
owner : String
features : Array[String]
guarantees : Array[String]
notes : Array[String]
}
///|
pub fn ModelCard::new(
name : String,
version : String,
task : String,
owner : String,
) -> ModelCard {
{ name, version, task, owner, features: [], guarantees: [], notes: [] }
}
///|
pub fn ModelCard::add_feature(self : ModelCard, feature : String) -> Unit {
self.features.push(feature)
}
///|
pub fn ModelCard::add_guarantee(self : ModelCard, guarantee : String) -> Unit {
self.guarantees.push(guarantee)
}
///|
pub fn ModelCard::add_note(self : ModelCard, note : String) -> Unit {
self.notes.push(note)
}
///|
pub fn ModelCard::name(self : ModelCard) -> String {
self.name
}
///|
pub fn ModelCard::version(self : ModelCard) -> String {
self.version
}
///|
pub fn ModelCard::task(self : ModelCard) -> String {
self.task
}
///|
pub fn ModelCard::owner(self : ModelCard) -> String {
self.owner
}
///|
pub fn ModelCard::features(self : ModelCard) -> Array[String] {
self.features.map(value => value)
}
///|
pub fn ModelCard::guarantees(self : ModelCard) -> Array[String] {
self.guarantees.map(value => value)
}
///|
pub fn ModelCard::notes(self : ModelCard) -> Array[String] {
self.notes.map(value => value)
}