use crate::{AbstainReason, Hyp, Operator, Outcome};
const SOFA_SCORE_0_MIN: f64 = 400.0; const SOFA_SCORE_1_MIN: f64 = 300.0; const SOFA_SCORE_2_MIN: f64 = 200.0; const SOFA_SCORE_3_MIN: f64 = 100.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SofaRespEvidence {
pub pao2: f64,
pub fio2: f64,
pub on_mech_vent: bool,
}
impl SofaRespEvidence {
pub fn new(pao2: f64, fio2: f64, on_mech_vent: bool) -> Self {
SofaRespEvidence {
pao2,
fio2,
on_mech_vent,
}
}
pub fn pao2_fio2_ratio(&self) -> Option<f64> {
if self.fio2 > 0.0 {
Some(self.pao2 / self.fio2)
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SofaRespHypothesis {
Unknown,
Score0,
Score1,
Score2,
Score3,
Score4,
}
impl SofaRespHypothesis {
pub fn score(&self) -> Option<u8> {
match self {
SofaRespHypothesis::Unknown => None,
SofaRespHypothesis::Score0 => Some(0),
SofaRespHypothesis::Score1 => Some(1),
SofaRespHypothesis::Score2 => Some(2),
SofaRespHypothesis::Score3 => Some(3),
SofaRespHypothesis::Score4 => Some(4),
}
}
}
pub struct SofaRespOperator;
impl Operator for SofaRespOperator {
fn apply(&self, _h: &Hyp, _e: &crate::operator::Evidence) -> Outcome<Hyp, AbstainReason> {
Outcome::Abstain(AbstainReason::InsufficientEvidence(
"SOFA respiratory operator requires structured evidence (v0.2+)",
))
}
}
pub fn score_from_ratio(ratio: f64, on_mech_vent: bool) -> Option<u8> {
let score = if ratio >= SOFA_SCORE_0_MIN {
0
} else if ratio >= SOFA_SCORE_1_MIN {
1
} else if ratio >= SOFA_SCORE_2_MIN {
2
} else if ratio >= SOFA_SCORE_3_MIN {
3
} else {
4
};
if score >= 3 && !on_mech_vent {
return None;
}
Some(score)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pao2_fio2_ratio_valid() {
let evidence = SofaRespEvidence::new(350.0, 1.0, false);
assert_eq!(evidence.pao2_fio2_ratio(), Some(350.0));
}
#[test]
fn test_pao2_fio2_ratio_zero_fio2() {
let evidence = SofaRespEvidence::new(350.0, 0.0, false);
assert_eq!(evidence.pao2_fio2_ratio(), None);
}
#[test]
fn test_pao2_fio2_ratio_negative_fio2() {
let evidence = SofaRespEvidence::new(350.0, -0.5, false);
assert_eq!(evidence.pao2_fio2_ratio(), None);
}
#[test]
fn test_score_from_ratio_score0() {
let ratio = 400.0;
assert_eq!(score_from_ratio(ratio, false), Some(0));
}
#[test]
fn test_score_from_ratio_score1() {
let ratio = 350.0;
assert_eq!(score_from_ratio(ratio, false), Some(1));
}
#[test]
fn test_score_from_ratio_score2() {
let ratio = 250.0;
assert_eq!(score_from_ratio(ratio, false), Some(2));
}
#[test]
fn test_score_from_ratio_score3_with_vent() {
let ratio = 150.0;
assert_eq!(score_from_ratio(ratio, true), Some(3));
}
#[test]
fn test_score_from_ratio_score3_without_vent() {
let ratio = 150.0;
assert_eq!(score_from_ratio(ratio, false), None);
}
#[test]
fn test_score_from_ratio_score4_with_vent() {
let ratio = 80.0;
assert_eq!(score_from_ratio(ratio, true), Some(4));
}
#[test]
fn test_score_from_ratio_score4_without_vent() {
let ratio = 80.0;
assert_eq!(score_from_ratio(ratio, false), None);
}
#[test]
fn test_sofa_resp_hypothesis_score() {
assert_eq!(SofaRespHypothesis::Unknown.score(), None);
assert_eq!(SofaRespHypothesis::Score0.score(), Some(0));
assert_eq!(SofaRespHypothesis::Score1.score(), Some(1));
assert_eq!(SofaRespHypothesis::Score2.score(), Some(2));
assert_eq!(SofaRespHypothesis::Score3.score(), Some(3));
assert_eq!(SofaRespHypothesis::Score4.score(), Some(4));
}
}