///|
/// Metadata and runtime score for one deployable estimation model.
pub struct ModelCatalogEntry {
name : String
version : String
dimension : Int
metric : String
score : Double
latency_ms : Double
memory_bytes : Int
tags : Array[String]
mut enabled : Bool
mut uses : Int
} derive(Debug)
///|
pub fn ModelCatalogEntry::new(
name : String,
version : String,
dimension : Int,
metric : String,
score : Double,
latency_ms : Double,
memory_bytes : Int,
tags : Array[String],
) -> ModelCatalogEntry {
{
name,
version,
dimension: dimension.max(0),
metric,
score: if score.is_nan() || score.is_inf() {
0.0
} else {
score
},
latency_ms: latency_ms.max(0.0),
memory_bytes: memory_bytes.max(0),
tags: tags.copy(),
enabled: true,
uses: 0,
}
}
///|
pub fn ModelCatalogEntry::name(self : ModelCatalogEntry) -> String {
self.name
}
///|
pub fn ModelCatalogEntry::version(self : ModelCatalogEntry) -> String {
self.version
}
///|
pub fn ModelCatalogEntry::dimension(self : ModelCatalogEntry) -> Int {
self.dimension
}
///|
pub fn ModelCatalogEntry::metric(self : ModelCatalogEntry) -> String {
self.metric
}
///|
pub fn ModelCatalogEntry::score(self : ModelCatalogEntry) -> Double {
self.score
}
///|
pub fn ModelCatalogEntry::latency_ms(self : ModelCatalogEntry) -> Double {
self.latency_ms
}
///|
pub fn ModelCatalogEntry::memory_bytes(self : ModelCatalogEntry) -> Int {
self.memory_bytes
}
///|
pub fn ModelCatalogEntry::tags(self : ModelCatalogEntry) -> Array[String] {
self.tags.copy()
}
///|
pub fn ModelCatalogEntry::enabled(self : ModelCatalogEntry) -> Bool {
self.enabled
}
///|
pub fn ModelCatalogEntry::uses(self : ModelCatalogEntry) -> Int {
self.uses
}
///|
pub fn ModelCatalogEntry::set_enabled(
self : ModelCatalogEntry,
enabled : Bool,
) -> Unit {
self.enabled = enabled
}
///|
pub fn ModelCatalogEntry::record_use(self : ModelCatalogEntry) -> Unit {
self.uses = self.uses + 1
}
///|
pub fn ModelCatalogEntry::has_tag(
self : ModelCatalogEntry,
tag : String,
) -> Bool {
for value in self.tags {
if value == tag {
return true
}
}
false
}
///|
pub fn ModelCatalogEntry::compatible(
self : ModelCatalogEntry,
dimension : Int,
required_tags : Array[String],
) -> Bool {
if !self.enabled || self.dimension != dimension {
return false
}
for tag in required_tags {
if !self.has_tag(tag) {
return false
}
}
true
}
///|
pub fn ModelCatalogEntry::identity(self : ModelCatalogEntry) -> String {
self.name + "@" + self.version
}
///|
pub fn ModelCatalogEntry::with_score(
self : ModelCatalogEntry,
score : Double,
latency_ms : Double,
) -> ModelCatalogEntry {
{
name: self.name,
version: self.version,
dimension: self.dimension,
metric: self.metric,
score,
latency_ms: latency_ms.max(0.0),
memory_bytes: self.memory_bytes,
tags: self.tags.copy(),
enabled: self.enabled,
uses: self.uses,
}
}
///|
/// Selection policy used to turn offline metrics into a deployable choice.
pub struct ModelSelectionPolicy {
metric : String
higher_is_better : Bool
max_latency_ms : Double
max_memory_bytes : Int
minimum_score : Double
required_tags : Array[String]
dimension : Int?
} derive(Debug)
///|
pub fn ModelSelectionPolicy::new(
metric : String,
higher_is_better : Bool,
max_latency_ms : Double,
max_memory_bytes : Int,
minimum_score : Double,
) -> ModelSelectionPolicy {
{
metric,
higher_is_better,
max_latency_ms: max_latency_ms.max(0.0),
max_memory_bytes: max_memory_bytes.max(0),
minimum_score,
required_tags: [],
dimension: None,
}
}
///|
pub fn ModelSelectionPolicy::metric(self : ModelSelectionPolicy) -> String {
self.metric
}
///|
pub fn ModelSelectionPolicy::higher_is_better(
self : ModelSelectionPolicy,
) -> Bool {
self.higher_is_better
}
///|
pub fn ModelSelectionPolicy::max_latency_ms(
self : ModelSelectionPolicy,
) -> Double {
self.max_latency_ms
}
///|
pub fn ModelSelectionPolicy::max_memory_bytes(
self : ModelSelectionPolicy,
) -> Int {
self.max_memory_bytes
}
///|
pub fn ModelSelectionPolicy::minimum_score(
self : ModelSelectionPolicy,
) -> Double {
self.minimum_score
}
///|
pub fn ModelSelectionPolicy::required_tags(
self : ModelSelectionPolicy,
) -> Array[String] {
self.required_tags.copy()
}
///|
pub fn ModelSelectionPolicy::dimension(self : ModelSelectionPolicy) -> Int? {
self.dimension
}
///|
pub fn ModelSelectionPolicy::with_tags(
self : ModelSelectionPolicy,
tags : Array[String],
) -> ModelSelectionPolicy {
{
metric: self.metric,
higher_is_better: self.higher_is_better,
max_latency_ms: self.max_latency_ms,
max_memory_bytes: self.max_memory_bytes,
minimum_score: self.minimum_score,
required_tags: tags.copy(),
dimension: self.dimension,
}
}
///|
pub fn ModelSelectionPolicy::for_dimension(
self : ModelSelectionPolicy,
dimension : Int,
) -> ModelSelectionPolicy {
{
metric: self.metric,
higher_is_better: self.higher_is_better,
max_latency_ms: self.max_latency_ms,
max_memory_bytes: self.max_memory_bytes,
minimum_score: self.minimum_score,
required_tags: self.required_tags.copy(),
dimension: Some(dimension.max(0)),
}
}
///|
/// A scored evaluation returned by model selection.
pub struct ModelEvaluation {
identity : String
eligible : Bool
score : Double
normalized_score : Double
reasons : Array[String]
} derive(Debug)
///|
pub fn ModelEvaluation::new(
entry : ModelCatalogEntry,
eligible : Bool,
normalized_score : Double,
reasons : Array[String],
) -> ModelEvaluation {
{
identity: entry.identity(),
eligible,
score: entry.score(),
normalized_score: normalized_score.clamp(min=0.0, max=1.0),
reasons: reasons.copy(),
}
}
///|
pub fn ModelEvaluation::identity(self : ModelEvaluation) -> String {
self.identity
}
///|
pub fn ModelEvaluation::eligible(self : ModelEvaluation) -> Bool {
self.eligible
}
///|
pub fn ModelEvaluation::score(self : ModelEvaluation) -> Double {
self.score
}
///|
pub fn ModelEvaluation::normalized_score(self : ModelEvaluation) -> Double {
self.normalized_score
}
///|
pub fn ModelEvaluation::reasons(self : ModelEvaluation) -> Array[String] {
self.reasons.copy()
}
///|
/// Result of a selection pass, including explainability for rejected models.
pub struct ModelSelectionResult {
selected : ModelCatalogEntry?
evaluations : Array[ModelEvaluation]
fallback_used : Bool
reason : String
} derive(Debug)
///|
pub fn ModelSelectionResult::none(
evaluations : Array[ModelEvaluation],
reason : String,
) -> ModelSelectionResult {
{
selected: None,
evaluations: evaluations.copy(),
fallback_used: false,
reason,
}
}
///|
pub fn ModelSelectionResult::selected(
entry : ModelCatalogEntry,
evaluations : Array[ModelEvaluation],
fallback_used : Bool,
reason : String,
) -> ModelSelectionResult {
{
selected: Some(entry),
evaluations: evaluations.copy(),
fallback_used,
reason,
}
}
///|
pub fn ModelSelectionResult::selected_entry(
self : ModelSelectionResult,
) -> ModelCatalogEntry? {
self.selected
}
///|
pub fn ModelSelectionResult::evaluations(
self : ModelSelectionResult,
) -> Array[ModelEvaluation] {
self.evaluations.copy()
}
///|
pub fn ModelSelectionResult::fallback_used(self : ModelSelectionResult) -> Bool {
self.fallback_used
}
///|
pub fn ModelSelectionResult::reason(self : ModelSelectionResult) -> String {
self.reason
}
///|
/// Versioned model catalog with explicit activation and selection.
pub struct ModelCatalog {
entries : Array[ModelCatalogEntry]
mut active_identity : String?
max_entries : Int
mut registrations : Int
mut selections : Int
} derive(Debug)
///|
pub fn ModelCatalog::new(max_entries : Int) -> ModelCatalog {
{
entries: [],
active_identity: None,
max_entries: max_entries.max(1),
registrations: 0,
selections: 0,
}
}
///|
pub fn ModelCatalog::entries(self : ModelCatalog) -> Array[ModelCatalogEntry] {
self.entries.copy()
}
///|
pub fn ModelCatalog::length(self : ModelCatalog) -> Int {
self.entries.length()
}
///|
pub fn ModelCatalog::registrations(self : ModelCatalog) -> Int {
self.registrations
}
///|
pub fn ModelCatalog::selections(self : ModelCatalog) -> Int {
self.selections
}
///|
pub fn ModelCatalog::active_identity(self : ModelCatalog) -> String? {
self.active_identity
}
///|
fn ModelCatalog::model_catalog_find(
self : ModelCatalog,
identity : String,
) -> Int {
for i, entry in self.entries {
if entry.identity() == identity {
return i
}
}
-1
}
///|
pub fn ModelCatalog::register(
self : ModelCatalog,
entry : ModelCatalogEntry,
) -> Bool {
let identity = entry.identity()
let existing = self.model_catalog_find(identity)
if existing >= 0 {
self.entries[existing] = entry
self.registrations = self.registrations + 1
return true
}
if self.entries.length() >= self.max_entries {
return false
}
self.entries.push(entry)
self.registrations = self.registrations + 1
true
}
///|
pub fn ModelCatalog::unregister(self : ModelCatalog, identity : String) -> Bool {
let index = self.model_catalog_find(identity)
if index < 0 {
false
} else {
self.entries.remove(index) |> ignore
match self.active_identity {
Some(active) => if active == identity { self.active_identity = None }
None => ()
}
true
}
}
///|
pub fn ModelCatalog::find(
self : ModelCatalog,
identity : String,
) -> ModelCatalogEntry? {
let index = self.model_catalog_find(identity)
if index < 0 {
None
} else {
Some(self.entries[index])
}
}
///|
pub fn ModelCatalog::enable(
self : ModelCatalog,
identity : String,
enabled : Bool,
) -> Bool {
match self.find(identity) {
None => false
Some(entry) => {
entry.set_enabled(enabled)
true
}
}
}
///|
pub fn ModelCatalog::activate(self : ModelCatalog, identity : String) -> Bool {
match self.find(identity) {
None => false
Some(entry) =>
if entry.enabled() {
self.active_identity = Some(identity)
true
} else {
false
}
}
}
///|
fn model_selection_score(
entry : ModelCatalogEntry,
policy : ModelSelectionPolicy,
) -> Double {
if policy.higher_is_better() {
entry.score()
} else {
1.0 / (1.0 + entry.score().abs())
}
}
///|
fn model_selection_reasons(
entry : ModelCatalogEntry,
policy : ModelSelectionPolicy,
) -> (Bool, Array[String]) {
let reasons : Array[String] = []
let mut eligible = entry.enabled()
if !entry.enabled() {
reasons.push("disabled")
eligible = false
}
if entry.metric() != policy.metric() {
reasons.push("metric-mismatch")
eligible = false
}
if entry.latency_ms() > policy.max_latency_ms() &&
policy.max_latency_ms() > 0.0 {
reasons.push("latency-limit")
eligible = false
}
if entry.memory_bytes() > policy.max_memory_bytes() &&
policy.max_memory_bytes() > 0 {
reasons.push("memory-limit")
eligible = false
}
if entry.score() < policy.minimum_score() && policy.higher_is_better() {
reasons.push("score-floor")
eligible = false
}
match policy.dimension() {
Some(dimension) =>
if entry.dimension() != dimension {
reasons.push("dimension-mismatch")
eligible = false
}
None => ()
}
for tag in policy.required_tags() {
if !entry.has_tag(tag) {
reasons.push("tag-missing:" + tag)
eligible = false
}
}
(eligible, reasons)
}
///|
pub fn ModelCatalog::select(
self : ModelCatalog,
policy : ModelSelectionPolicy,
) -> ModelSelectionResult {
let evaluations : Array[ModelEvaluation] = []
let mut selected : ModelCatalogEntry? = None
let mut best_score = -1.0e300
for entry in self.entries {
let (eligible, reasons) = model_selection_reasons(entry, policy)
let score = model_selection_score(entry, policy)
evaluations.push(ModelEvaluation::new(entry, eligible, score, reasons))
if eligible && score > best_score {
best_score = score
selected = Some(entry)
}
}
self.selections = self.selections + 1
match selected {
None => ModelSelectionResult::none(evaluations, "no-compatible-model")
Some(entry) => {
entry.record_use()
ModelSelectionResult::selected(entry, evaluations, false, "best-score")
}
}
}
///|
pub fn ModelCatalog::select_or_active(
self : ModelCatalog,
policy : ModelSelectionPolicy,
) -> ModelSelectionResult {
let result = self.select(policy)
match result.selected_entry() {
Some(_) => result
None =>
match self.active_identity {
None => result
Some(identity) =>
match self.find(identity) {
None => result
Some(entry) =>
ModelSelectionResult::selected(
entry,
result.evaluations(),
true,
"active-fallback",
)
}
}
}
}
///|
pub fn model_catalog_best_by_latency(
catalog : ModelCatalog,
) -> ModelCatalogEntry? {
let mut selected : ModelCatalogEntry? = None
for entry in catalog.entries() {
if entry.enabled() {
match selected {
None => selected = Some(entry)
Some(previous) =>
if entry.latency_ms() < previous.latency_ms() {
selected = Some(entry)
}
}
}
}
selected
}
///|
pub fn model_catalog_total_memory(catalog : ModelCatalog) -> Int {
let mut total = 0
for entry in catalog.entries() {
if entry.enabled() {
total = total + entry.memory_bytes()
}
}
total
}
///|
pub fn model_catalog_average_score(catalog : ModelCatalog) -> Double {
let mut total = 0.0
let mut count = 0
for entry in catalog.entries() {
if entry.enabled() {
total = total + entry.score()
count = count + 1
}
}
if count == 0 {
0.0
} else {
total / count.to_double()
}
}
///|
pub fn model_catalog_compatible(
catalog : ModelCatalog,
dimension : Int,
tags : Array[String],
) -> Array[ModelCatalogEntry] {
let result : Array[ModelCatalogEntry] = []
for entry in catalog.entries() {
if entry.compatible(dimension, tags) {
result.push(entry)
}
}
result
}
///|
pub fn model_catalog_summary(catalog : ModelCatalog) -> String {
let active = match catalog.active_identity() {
None => "none"
Some(identity) => identity
}
"models=" +
catalog.length().to_string() +
",active=" +
active +
",mean_score=" +
model_catalog_average_score(catalog).to_string()
}
///|
/// Compare two entries with a lexicographic score/latency policy.
pub fn model_entry_preferred(
left : ModelCatalogEntry,
right : ModelCatalogEntry,
higher_is_better : Bool,
) -> Bool {
if !left.enabled() {
return false
}
if !right.enabled() {
return true
}
if left.score() == right.score() {
left.latency_ms() < right.latency_ms()
} else if higher_is_better {
left.score() > right.score()
} else {
left.score() < right.score()
}
}
///|
pub fn model_catalog_rank(
catalog : ModelCatalog,
policy : ModelSelectionPolicy,
) -> Array[ModelEvaluation] {
catalog.select(policy).evaluations()
}
///|
pub fn model_metric_regression_score(
baseline : Double,
candidate : Double,
higher_is_better : Bool,
) -> Double {
if baseline.is_nan() || candidate.is_nan() {
0.0
} else if higher_is_better {
(candidate / baseline.max(1.0e-12)).clamp(min=0.0, max=2.0) * 0.5
} else {
(baseline / candidate.max(1.0e-12)).clamp(min=0.0, max=2.0) * 0.5
}
}
///|
pub fn model_catalog_has_regression(
baseline : ModelCatalogEntry,
candidate : ModelCatalogEntry,
tolerance : Double,
higher_is_better : Bool,
) -> Bool {
let improvement = model_metric_regression_score(
baseline.score(),
candidate.score(),
higher_is_better,
)
improvement + tolerance.max(0.0) < 0.5
}