///|
pub(all) enum SafetySeverity {
SafetyWarning
SafetyCritical
SafetyEmergency
} derive(Eq, Debug)
///|
pub fn SafetySeverity::is_fatal(self : SafetySeverity) -> Bool {
match self {
SafetyWarning => false
SafetyCritical | SafetyEmergency => true
}
}
///|
pub fn SafetySeverity::to_string(self : SafetySeverity) -> String {
match self {
SafetyWarning => "WARNING"
SafetyCritical => "CRITICAL"
SafetyEmergency => "EMERGENCY"
}
}
///|
pub(all) enum SafetyViolationKind {
MissingTurnout
TurnoutPositionMismatch
TurnoutUnlocked
FlankProtectionMismatch
HostileRouteConflict
MissingTrack
FaultedRouteSection
ProceedSignalUnsafe
FaultSignalNotRed
ReleaseBeforeTraversal
OccupancyOrderViolation
IllegalRouteTransition
} derive(Eq, Debug)
///|
pub fn SafetyViolationKind::to_string(self : SafetyViolationKind) -> String {
match self {
MissingTurnout => "MISSING_TURNOUT"
TurnoutPositionMismatch => "TURNOUT_POSITION_MISMATCH"
TurnoutUnlocked => "TURNOUT_UNLOCKED"
FlankProtectionMismatch => "FLANK_PROTECTION_MISMATCH"
HostileRouteConflict => "HOSTILE_ROUTE_CONFLICT"
MissingTrack => "MISSING_TRACK"
FaultedRouteSection => "FAULTED_ROUTE_SECTION"
ProceedSignalUnsafe => "PROCEED_SIGNAL_UNSAFE"
FaultSignalNotRed => "FAULT_SIGNAL_NOT_RED"
ReleaseBeforeTraversal => "RELEASE_BEFORE_TRAVERSAL"
OccupancyOrderViolation => "OCCUPANCY_ORDER_VIOLATION"
IllegalRouteTransition => "ILLEGAL_ROUTE_TRANSITION"
}
}
///|
pub(all) struct SafetyViolation {
kind : SafetyViolationKind
severity : SafetySeverity
affected_route_id : RouteId?
description : String
} derive(Debug)
///|
pub fn SafetyViolation::new(
kind : SafetyViolationKind,
severity : SafetySeverity,
affected_route_id : RouteId?,
description : String,
) -> SafetyViolation {
{ kind, severity, affected_route_id, description }
}
///|
pub fn SafetyViolation::to_string(self : SafetyViolation) -> String {
self.severity.to_string() +
" " +
self.kind.to_string() +
": " +
self.description
}
///|
pub(all) struct SafetyAuditReport {
violations : Array[SafetyViolation]
mut fatal_count : Int
mut warning_count : Int
mut can_clear_signal : Bool
} derive(Debug)
///|
pub fn SafetyAuditReport::new() -> SafetyAuditReport {
{ violations: [], fatal_count: 0, warning_count: 0, can_clear_signal: true }
}
///|
fn SafetyAuditReport::add_violation(
self : SafetyAuditReport,
violation : SafetyViolation,
) -> Unit {
self.violations.push(violation)
if violation.severity.is_fatal() {
self.fatal_count = self.fatal_count + 1
self.can_clear_signal = false
} else {
self.warning_count = self.warning_count + 1
}
}
///|
pub fn SafetyAuditReport::contains_kind(
self : SafetyAuditReport,
kind : SafetyViolationKind,
) -> Bool {
for violation in self.violations {
if violation.kind == kind {
return true
}
}
false
}
///|
pub fn SafetyAuditReport::violations(
self : SafetyAuditReport,
) -> Array[SafetyViolation] {
self.violations
}
///|
pub(all) struct ScanSafetySummary {
mut checked_routes : Int
mut fatal_count : Int
mut warning_count : Int
mut can_clear_signal : Bool
} derive(Debug)
///|
fn ScanSafetySummary::new() -> ScanSafetySummary {
{
checked_routes: 0,
fatal_count: 0,
warning_count: 0,
can_clear_signal: true,
}
}
///|
fn ScanSafetySummary::record_fatal(self : ScanSafetySummary) -> Unit {
self.fatal_count = self.fatal_count + 1
self.can_clear_signal = false
}
///|
pub fn InterlockingTable::audit_safety(
self : InterlockingTable,
station : @topology.Station,
) -> SafetyAuditReport {
let report = SafetyAuditReport::new()
for _, active_route in self.active_routes {
if !self.is_route_reserved(active_route.rule.id) {
continue
}
let rule = active_route.rule
let lifecycle = self.validate_active_route_lifecycle(station, rule.id)
for lifecycle_error in lifecycle.errors {
report.add_violation(
SafetyViolation::new(
IllegalRouteTransition,
SafetyEmergency,
Some(rule.id),
lifecycle_error,
),
)
}
if active_route.occupancy_order_violation {
report.add_violation(
SafetyViolation::new(
OccupancyOrderViolation,
SafetyEmergency,
Some(rule.id),
"track occupancy was observed out of route order",
),
)
}
for sw in rule.switches {
match station.graph.get_turnout(sw.turnout_id) {
None =>
report.add_violation(
SafetyViolation::new(
MissingTurnout,
SafetyEmergency,
Some(rule.id),
"main route turnout is missing",
),
)
Some(turnout) => {
if turnout.position != sw.required_position {
report.add_violation(
SafetyViolation::new(
TurnoutPositionMismatch,
SafetyEmergency,
Some(rule.id),
"main route turnout position does not match its rule",
),
)
}
if !turnout.is_locked {
report.add_violation(
SafetyViolation::new(
TurnoutUnlocked,
SafetyEmergency,
Some(rule.id),
"main route turnout is not locked",
),
)
}
}
}
}
for flank in rule.flank_switches {
match station.graph.get_turnout(flank.turnout_id) {
None =>
report.add_violation(
SafetyViolation::new(
MissingTurnout,
SafetyEmergency,
Some(rule.id),
"flank protection turnout is missing",
),
)
Some(turnout) => {
if turnout.position != flank.required_position {
report.add_violation(
SafetyViolation::new(
FlankProtectionMismatch,
SafetyEmergency,
Some(rule.id),
flank.description,
),
)
}
if !turnout.is_locked {
report.add_violation(
SafetyViolation::new(
TurnoutUnlocked,
SafetyEmergency,
Some(rule.id),
"flank protection turnout is not locked",
),
)
}
}
}
}
for hostile_id in rule.hostile_route_ids {
if self.is_route_reserved(hostile_id) {
report.add_violation(
SafetyViolation::new(
HostileRouteConflict,
SafetyEmergency,
Some(rule.id),
"hostile route is active concurrently",
),
)
}
}
let entry_signal = station.graph.get_signal(rule.entry_signal)
match entry_signal {
None =>
report.add_violation(
SafetyViolation::new(
ProceedSignalUnsafe,
SafetyEmergency,
Some(rule.id),
"entry signal is missing",
),
)
Some(signal) => {
if active_route.state == RouteFault && signal.current_aspect != Red {
report.add_violation(
SafetyViolation::new(
FaultSignalNotRed,
SafetyEmergency,
Some(rule.id),
"faulted route entry signal is not red",
),
)
}
for track_id in rule.track_sections {
if active_route.was_unlocked(track_id) &&
!active_route.was_occupied(track_id) {
report.add_violation(
SafetyViolation::new(
ReleaseBeforeTraversal,
SafetyEmergency,
Some(rule.id),
"route section was released without a recorded traversal",
),
)
}
match station.graph.get_track(track_id) {
None =>
report.add_violation(
SafetyViolation::new(
MissingTrack,
SafetyEmergency,
Some(rule.id),
"route track section is missing",
),
)
Some(track) => {
if track.state == Fault {
report.add_violation(
SafetyViolation::new(
FaultedRouteSection,
SafetyEmergency,
Some(rule.id),
"route track section is faulted",
),
)
}
if signal.current_aspect.is_proceed() &&
(track.state == Occupied || track.state == Fault) {
report.add_violation(
SafetyViolation::new(
ProceedSignalUnsafe,
SafetyEmergency,
Some(rule.id),
"entry signal proceeds over an occupied or faulted section",
),
)
}
}
}
}
}
}
}
report
}
///|
pub fn InterlockingTable::scan_safety_summary(
self : InterlockingTable,
station : @topology.Station,
) -> ScanSafetySummary {
let summary = ScanSafetySummary::new()
for _, active_route in self.active_routes {
if !self.is_route_reserved(active_route.rule.id) {
continue
}
summary.checked_routes = summary.checked_routes + 1
let rule = active_route.rule
if !active_route.lifecycle_snapshot_is_safe() {
summary.record_fatal()
}
for sw in rule.switches {
match station.graph.get_turnout(sw.turnout_id) {
None => summary.record_fatal()
Some(turnout) =>
if turnout.position != sw.required_position || !turnout.is_locked {
summary.record_fatal()
}
}
}
for flank in rule.flank_switches {
match station.graph.get_turnout(flank.turnout_id) {
None => summary.record_fatal()
Some(turnout) =>
if turnout.position != flank.required_position || !turnout.is_locked {
summary.record_fatal()
}
}
}
for hostile_id in rule.hostile_route_ids {
if self.is_route_reserved(hostile_id) {
summary.record_fatal()
}
}
match station.graph.get_signal(rule.entry_signal) {
None => summary.record_fatal()
Some(signal) => {
if active_route.state == RouteFault && signal.current_aspect != Red {
summary.record_fatal()
}
for track_id in rule.track_sections {
if active_route.was_unlocked(track_id) &&
!active_route.was_occupied(track_id) {
summary.record_fatal()
}
match station.graph.get_track(track_id) {
None => summary.record_fatal()
Some(track) => {
if track.state == Fault {
summary.record_fatal()
}
if signal.current_aspect.is_proceed() &&
(track.state == Occupied || track.state == Fault) {
summary.record_fatal()
}
}
}
}
}
}
}
summary
}
///|
pub(all) struct ReleaseCounters {
mut sections_unlocked : Int
mut switches_unlocked : Int
mut is_route_completed : Bool
mut entered_route : Bool
} derive(Debug)
///|
fn ReleaseCounters::empty() -> ReleaseCounters {
{
sections_unlocked: 0,
switches_unlocked: 0,
is_route_completed: false,
entered_route: false,
}
}