Functional Weave
Code in Rust

health.gestational-age@1.0.0

impl/rust.rs

2,395 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::{add_days, days_between, parse_iso_date};  ← from dates.add-days ^1.0.0 · built alongside by fune

/// Naegele's rule: 280 days (40+0 weeks) from the LMP for a 28-day cycle.
const TERM_DAYS: i64 = 280;
/// 44+0 weeks: beyond this the dates are almost certainly wrong.
const MAX_DAYS: i64 = 308;

/// Gestational age and estimated due date from the last menstrual period. A
/// cycle longer or shorter than 28 days moves ovulation, so both are shifted by
/// the difference, as the NHS due date calculator does.
///
/// # Panics
/// Panics on a cycle length outside 20..=44, a malformed date, or a
/// gestational age below zero or above 44+0 weeks.
pub fn gestational_age(lmp_date: &str, on_date: &str, cycle_length_days: i64) -> GestationalAge {
    if !(20..=44).contains(&cycle_length_days) {
        panic!("cycleLengthDays must be a whole number from 20 to 44, received {}", cycle_length_days);
    }
    parse_iso_date(lmp_date);
    parse_iso_date(on_date);
    let shift = cycle_length_days - 28;
    let total_days = days_between(lmp_date, on_date) - shift;
    if total_days < 0 {
        panic!("onDate {} is before the adjusted start of pregnancy", on_date);
    }
    if total_days > MAX_DAYS {
        panic!("gestational age of {} days is more than 44+0 weeks: check the dates", total_days);
    }
    GestationalAge {
        weeks: total_days / 7,
        days: total_days % 7,
        total_days,
        due_date: add_days(lmp_date, TERM_DAYS + shift),
    }
}

pub fn gestational_age_to_value(age: &GestationalAge) -> Value {
    Value::obj(vec![
        ("weeks", Value::Int(age.weeks)),
        ("days", Value::Int(age.days)),
        ("totalDays", Value::Int(age.total_days)),
        ("dueDate", Value::str(&age.due_date)),
    ])
}

fn date_arg(value: &Value) -> &str {
    match value {
        Value::Str(s) => s.as_str(),
        _ => panic!("{:?} is not an ISO date (YYYY-MM-DD)", value),
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion below quietly change it.
    let cycle = match args[2] {
        Value::Int(i) => i,
        _ => panic!("cycleLengthDays must be a whole number from 20 to 44, received {:?}", args[2]),
    };
    gestational_age_to_value(&gestational_age(date_arg(&args[0]), date_arg(&args[1]), cycle))
}