///|
/// Units supported by business continuity time targets.
pub(all) enum TimeUnit {
Minutes
Hours
Days
}
///|
/// A non-negative duration with an explicit unit.
pub(all) struct TimeSpan {
amount : Int
unit : TimeUnit
}
///|
/// Creates a duration and rejects negative amounts.
pub fn TimeSpan::new(
amount~ : Int,
unit~ : TimeUnit,
) -> TimeSpan raise ModelError {
if amount < 0 {
raise ModelError::InvalidDuration("duration.amount must be non-negative")
}
{ amount, unit, }
}
///|
/// Converts a duration to minutes for comparing recovery targets.
pub fn TimeSpan::as_minutes(self : TimeSpan) -> Int {
match self.unit {
Minutes => self.amount
Hours => self.amount * 60
Days => self.amount * 24 * 60
}
}
///|
/// Business impact levels derived from an impact score.
pub(all) enum ImpactLevel {
Low
Medium
High
Critical
} derive(Debug, Eq)
///|
/// Keep derived trait methods explicit for the current MoonBit toolchain.
pub extend ImpactLevel with @moonbitlang/core/debug.Debug::{to_repr}
///|
pub extend ImpactLevel with Eq::{not_equal, equal}
///|
/// Applies the platform's inclusive score bands to an impact score.
pub fn ImpactLevel::from_score(score : Int) -> ImpactLevel raise ModelError {
if score < 0 || score > 100 {
raise ModelError::InvalidImpactScore(
"impact_score must be between 0 and 100",
)
}
if score < 25 {
Low
} else if score < 50 {
Medium
} else if score < 75 {
High
} else {
Critical
}
}
///|
fn validate_assessment_duration(
field : String,
duration : TimeSpan,
positive : Bool,
) -> Unit raise ModelError {
if duration.amount < 0 {
raise ModelError::InvalidDuration("\{field}.amount must be non-negative")
}
if positive && duration.amount == 0 {
raise ModelError::InvalidDuration("\{field} must be positive")
}
}
///|
/// Business impact targets and their derived criticality for one service.
pub(all) struct BusinessImpactAssessment {
service_id : ServiceId
impact_score : Int
impact_level : ImpactLevel
rto : TimeSpan
rpo : TimeSpan
max_tolerable_downtime : TimeSpan
}
///|
/// Creates a validated business impact assessment.
pub fn BusinessImpactAssessment::new(
service_id~ : ServiceId,
impact_score~ : Int,
rto~ : TimeSpan,
rpo~ : TimeSpan,
max_tolerable_downtime~ : TimeSpan,
) -> BusinessImpactAssessment raise ModelError {
if service_id.value == "" {
raise ModelError::EmptyField("service_id")
}
let impact_level = ImpactLevel::from_score(impact_score)
validate_assessment_duration("rto", rto, false)
validate_assessment_duration("rpo", rpo, false)
validate_assessment_duration(
"max_tolerable_downtime", max_tolerable_downtime, true,
)
if rto.as_minutes() > max_tolerable_downtime.as_minutes() {
raise ModelError::InvalidBia("rto must not exceed max_tolerable_downtime")
}
if rpo.as_minutes() > max_tolerable_downtime.as_minutes() {
raise ModelError::InvalidBia("rpo must not exceed max_tolerable_downtime")
}
{ service_id, impact_score, impact_level, rto, rpo, max_tolerable_downtime, }
}