///|
pub(all) enum PassportBand {
Excellent
Solid
Watchlist
Rework
} derive(Eq, Debug)
///|
pub(all) struct RetrofitPassport {
site_name : String
event_name : String
band : PassportBand
overall_score : Int
footprint_area_m2 : Double
runoff_volume_m3 : Double
captured_volume_m3 : Double
bypass_volume_m3 : Double
capture_ratio : Double
capital_cost : Double
annual_maintenance_cost : Double
maintenance_priority : MaintenancePriority
maintenance_interval_days : Int
public_summary : String
field_actions : Array[String]
assumptions : Array[String]
} derive(Eq, Debug)
///|
pub fn build_retrofit_passport(
site_name : String,
garden : GardenPlan,
train : TreatmentTrainPlan,
cost : TrainCostSummary,
maintenance : MaintenancePlan,
) -> RetrofitPassport {
let score = passport_score(garden, train, cost, maintenance)
let assumptions = passport_assumptions(garden, train, cost)
let actions = passport_actions(train, cost, maintenance)
let band = passport_band(
score,
train.capture_ratio,
train.bypass_volume_m3,
maintenance.priority,
)
{
site_name,
event_name: garden.event_name,
band,
overall_score: score,
footprint_area_m2: train.footprint_area_m2,
runoff_volume_m3: garden.runoff_volume_m3,
captured_volume_m3: train.total_captured_m3,
bypass_volume_m3: train.bypass_volume_m3,
capture_ratio: train.capture_ratio,
capital_cost: cost.total_capital_cost,
annual_maintenance_cost: cost.annual_maintenance_cost,
maintenance_priority: maintenance.priority,
maintenance_interval_days: maintenance.interval_days,
public_summary: passport_summary(
site_name,
band,
train.capture_ratio,
train.bypass_volume_m3,
),
field_actions: actions,
assumptions,
}
}
///|
pub fn scenario_passport(
site_name : String,
scenario : ScenarioResult,
) -> RetrofitPassport {
let band = passport_band(
scenario.overall_score,
scenario.treatment_capture_ratio,
scenario.treatment_bypass_m3,
scenario.maintenance_priority,
)
let actions : Array[String] = []
if scenario.status != Ready {
actions.push("review scenario before using it as the preferred plan")
}
if scenario.treatment_bypass_m3 > 0.0 {
actions.push("identify a safe overflow route for remaining bypass")
}
if scenario.maintenance_priority == ServiceSoon ||
scenario.maintenance_priority == Restore {
actions.push("schedule maintenance before relying on the scenario")
}
if scenario.budget_fit_score < 50 {
actions.push("revise scope or budget assumptions")
}
if actions.length() == 0 {
actions.push("record baseline photos after construction")
}
{
site_name,
event_name: scenario.name,
band,
overall_score: scenario.overall_score,
footprint_area_m2: scenario.recommended_area_m2,
runoff_volume_m3: scenario.adjusted_runoff_m3,
captured_volume_m3: scenario.adjusted_runoff_m3 *
scenario.treatment_capture_ratio,
bypass_volume_m3: scenario.treatment_bypass_m3,
capture_ratio: scenario.treatment_capture_ratio,
capital_cost: scenario.total_capital_cost,
annual_maintenance_cost: 0.0,
maintenance_priority: scenario.maintenance_priority,
maintenance_interval_days: passport_interval_from_priority(
scenario.maintenance_priority,
),
public_summary: passport_summary(
site_name,
band,
scenario.treatment_capture_ratio,
scenario.treatment_bypass_m3,
),
field_actions: actions,
assumptions: scenario.notes,
}
}
///|
pub fn passport_followup_days(passport : RetrofitPassport) -> Int {
let base = passport.maintenance_interval_days
if passport.band == Rework {
passport_min_int(base, 14)
} else if passport.band == Watchlist {
passport_min_int(base, 30)
} else if passport.maintenance_priority == Watch {
passport_min_int(base, 45)
} else {
base
}
}
///|
pub fn passport_handoff_checklist(passport : RetrofitPassport) -> Array[String] {
let checklist : Array[String] = []
checklist.push("save the design storm and catchment assumptions")
checklist.push("photograph inlet, overflow, and vegetation zones")
if passport.bypass_volume_m3 > 0.0 {
checklist.push("mark the safe bypass path on the site sketch")
}
if passport.capital_cost > 0.0 {
checklist.push("attach the cost profile used for this passport")
}
if passport.maintenance_priority != Routine {
checklist.push("schedule the first maintenance follow-up")
}
for action in passport.field_actions {
checklist.push(action)
}
checklist
}
///|
pub fn PassportBand::label(self : PassportBand) -> String {
match self {
Excellent => "excellent"
Solid => "solid"
Watchlist => "watchlist"
Rework => "rework"
}
}
///|
pub fn RetrofitPassport::to_json(self : RetrofitPassport) -> String {
let out = StringBuilder::new()
out.write_string("{")
out.write_string("\"site\":\"" + escape_json(self.site_name) + "\",")
out.write_string("\"event\":\"" + escape_json(self.event_name) + "\",")
out.write_string("\"band\":\"" + self.band.label() + "\",")
out.write_string("\"overall_score\":" + self.overall_score.to_string() + ",")
out.write_string(
"\"footprint_area_m2\":" + self.footprint_area_m2.to_string() + ",",
)
out.write_string(
"\"runoff_volume_m3\":" + self.runoff_volume_m3.to_string() + ",",
)
out.write_string(
"\"captured_volume_m3\":" + self.captured_volume_m3.to_string() + ",",
)
out.write_string(
"\"bypass_volume_m3\":" + self.bypass_volume_m3.to_string() + ",",
)
out.write_string("\"capture_ratio\":" + self.capture_ratio.to_string() + ",")
out.write_string("\"capital_cost\":" + self.capital_cost.to_string() + ",")
out.write_string(
"\"annual_maintenance_cost\":" +
self.annual_maintenance_cost.to_string() +
",",
)
out.write_string(
"\"maintenance_priority\":\"" + self.maintenance_priority.label() + "\",",
)
out.write_string(
"\"maintenance_interval_days\":" +
self.maintenance_interval_days.to_string() +
",",
)
out.write_string(
"\"public_summary\":\"" + escape_json(self.public_summary) + "\",",
)
out.write_string(
"\"field_actions\":" +
passport_string_array_to_json(self.field_actions) +
",",
)
out.write_string(
"\"assumptions\":" + passport_string_array_to_json(self.assumptions),
)
out.write_string("}")
out.to_string()
}
///|
pub fn RetrofitPassport::to_markdown(self : RetrofitPassport) -> String {
let out = StringBuilder::new()
out.write_string("# SpongeKit Retrofit Passport\n\n")
out.write_string("- Site: " + self.site_name + "\n")
out.write_string("- Event: " + self.event_name + "\n")
out.write_string("- Band: " + self.band.label() + "\n")
out.write_string("- Score: " + self.overall_score.to_string() + "\n")
out.write_string(
"- Footprint: " + self.footprint_area_m2.to_string() + " m2\n",
)
out.write_string(
"- Captured: " + self.captured_volume_m3.to_string() + " m3\n",
)
out.write_string("- Bypass: " + self.bypass_volume_m3.to_string() + " m3\n")
out.write_string(
"- Maintenance: " + self.maintenance_priority.label() + "\n\n",
)
out.write_string(self.public_summary + "\n\n")
out.write_string("## Field Actions\n\n")
for action in self.field_actions {
out.write_string("- " + action + "\n")
}
if self.assumptions.length() > 0 {
out.write_string("\n## Assumptions\n\n")
for assumption in self.assumptions {
out.write_string("- " + assumption + "\n")
}
}
out.to_string()
}
///|
fn passport_score(
garden : GardenPlan,
train : TreatmentTrainPlan,
cost : TrainCostSummary,
maintenance : MaintenancePlan,
) -> Int {
let capture_points = (passport_clamp_double(train.capture_ratio, 0.0, 1.0) *
42.0).to_int()
let readiness_points = if garden.status == Ready && train.status == Ready {
18
} else if garden.status != Invalid && train.status != Invalid {
8
} else {
0
}
let cost_points = if cost.cost_per_captured_m3 <= 12000.0 {
18
} else if cost.cost_per_captured_m3 <= 25000.0 {
12
} else if cost.cost_per_captured_m3 <= 50000.0 {
6
} else {
0
}
let resilience_points = (train.resilience_score.to_double() * 0.18).to_int()
passport_clamp_int(
capture_points +
readiness_points +
cost_points +
resilience_points -
maintenance_penalty_for_passport(maintenance.priority),
0,
100,
)
}
///|
fn passport_band(
score : Int,
capture_ratio : Double,
bypass_volume_m3 : Double,
priority : MaintenancePriority,
) -> PassportBand {
if score >= 85 && capture_ratio >= 0.95 && bypass_volume_m3 <= 0.25 {
Excellent
} else if score >= 70 && priority != Restore {
Solid
} else if score >= 45 && priority != Restore {
Watchlist
} else {
Rework
}
}
///|
fn passport_assumptions(
garden : GardenPlan,
train : TreatmentTrainPlan,
cost : TrainCostSummary,
) -> Array[String] {
let assumptions : Array[String] = []
assumptions.push(
"design rain depth " + garden.design_rain_mm.to_string() + " mm",
)
assumptions.push(
"catchment area " + garden.catchment_area_m2.to_string() + " m2",
)
assumptions.push(
"treatment train facility count " + train.facility_count.to_string(),
)
assumptions.push("cost currency " + cost.currency)
if train.notes.length() > 0 {
for note in train.notes {
assumptions.push(note)
}
}
if cost.notes.length() > 0 {
for note in cost.notes {
assumptions.push(note)
}
}
assumptions
}
///|
fn passport_actions(
train : TreatmentTrainPlan,
cost : TrainCostSummary,
maintenance : MaintenancePlan,
) -> Array[String] {
let actions : Array[String] = []
for action in maintenance.actions {
actions.push(action.label())
}
if train.bypass_volume_m3 > 0.0 {
actions.push("confirm overflow route before construction")
}
if cost.notes.length() > 0 {
actions.push("review cost notes before procurement")
}
if actions.length() == 0 {
actions.push("keep routine inspection log after major storms")
}
actions
}
///|
fn passport_summary(
site_name : String,
band : PassportBand,
capture_ratio : Double,
bypass_volume_m3 : Double,
) -> String {
site_name +
" is rated " +
band.label() +
" with capture ratio " +
capture_ratio.to_string() +
" and bypass " +
bypass_volume_m3.to_string() +
" m3 under the selected design event."
}
///|
fn passport_interval_from_priority(priority : MaintenancePriority) -> Int {
match priority {
Routine => 90
Watch => 60
ServiceSoon => 30
Restore => 14
}
}
///|
fn maintenance_penalty_for_passport(priority : MaintenancePriority) -> Int {
match priority {
Routine => 0
Watch => 5
ServiceSoon => 14
Restore => 28
}
}
///|
fn passport_min_int(left : Int, right : Int) -> Int {
if left < right {
left
} else {
right
}
}
///|
fn passport_clamp_double(value : Double, low : Double, high : Double) -> Double {
if value < low {
low
} else if value > high {
high
} else {
value
}
}
///|
fn passport_clamp_int(value : Int, low : Int, high : Int) -> Int {
if value < low {
low
} else if value > high {
high
} else {
value
}
}
///|
fn passport_string_array_to_json(values : Array[String]) -> String {
let out = StringBuilder::new()
out.write_string("[")
for index, value in values {
if index > 0 {
out.write_string(",")
}
out.write_string("\"" + escape_json(value) + "\"")
}
out.write_string("]")
out.to_string()
}