thermo_trail

    Deterministic cold-chain temperature excursion analysis and replay engine

    cold-chain
    time-series
    sensor
    simulation
    risk-analysis
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    7

    #ThermoTrail(温链哨兵)

    ThermoTrail 是一个使用 MoonBit 编写的冷链温度数据分析与运输回放引擎。它读取 CSV 传感器记录,完成数据清洗、异常区间识别、热暴露统计、传感器健康检查和确定性故障模拟, 并输出 JSON、Markdown 与 CSV 报告。

    项目边界:ThermoTrail 解决冷链和时序传感器数据问题。MoonBit 只是实现语言;本项目不是 MoonBit 编译器、LSP、格式化器、包管理器、IDE 插件或其他 MoonBit 工具链组件。

    #已实现能力

    • 支持引号、转义、CRLF、BOM 和列别名的 CSV 解析及字段诊断;
    • ISO-8601 时区归一化、排序、重复值合并、校准、物理范围和采样缺口检查;
    • 带迟滞、宽限时间和最短持续时间的高温/低温偏差状态机;
    • 度时、时间加权均值、标准差、百分位、滚动窗口和 MKT;
    • 传感器卡死、漂移、噪声、离线、低电量和多传感器冲突检测;
    • 稳定运输、开门、冷机故障、冻结、掉线、卡死、断电和混合故障模拟;
    • JSON、Markdown、事件 CSV、标准化读数 CSV 和终端摘要;
    • 4,000 行以上生产 MoonBit 代码、115 个自动化测试和 GitHub Actions CI。

    ThermoTrail 提供风险筛查结果,不替代医疗、食品或监管机构的合规认证。

    #快速开始

    安装 MoonBit 后,在仓库根目录运行:

    moon check --deny-warn moon test moon build --target native --release moon run cmd/main -- version moon run cmd/main -- simulate door-open summary moon run cmd/main -- simulate cooling-failure json moon run cmd/main -- demo markdown moon run examples/embedded

    查看全部模拟场景:

    moon run cmd/main -- list-scenarios

    输出格式可选 summary、json、markdown、events 和 readings。

    #作为库使用

    let batch = @thermo.parse_readings_csv(csv_text) let request = @thermo.analysis_request( "shipment-001", "logger-export.csv", 1785916800L, batch.readings, ) let report = @thermo.analyze({ ..request, diagnostics: batch.diagnostics }) println(@thermo.report_to_markdown(report))

    也可以一次完成 CSV 分析:

    let report = @thermo.analyze_csv( "shipment-001", "logger-export.csv", 1785916800L, csv_text, )

    #CSV 格式

    timestamp,sensor_id,temperature_c,humidity_percent,battery_percent,status 2026-08-05T10:00:00Z,S-001,4.2,61.0,92,ok

    必填列为 timestamp、sensor_id 和 temperature_c。示例文件位于 examples/data,完整格式见 docs/DATA_FORMAT.md。

    #工程结构

    • domain.mbt:核心领域模型与配置;
    • time.mbt、csv.mbt、normalize.mbt:输入和标准化;
    • sensor.mbt:传感器健康分析;
    • excursion.mbt、metrics.mbt:状态机与热暴露指标;
    • simulate.mbt:确定性故障模拟;
    • analysis.mbt、report.mbt:端到端分析和报告;
    • cmd/main:命令行程序;
    • examples:可运行示例与公开测试数据;
    • docs:架构、数据格式、设计决策、申报书与验收清单。

    #开发与验证

    moon fmt moon check --deny-warn moon test moon build --target native --release moon info

    CI 在 Linux 上重复执行格式检查、静态检查、测试、原生发布构建和公共接口生成。

    #发布到 mooncakes.io

    仓库已包含 moon.mod 发布元数据。拥有 sujy123456 对应 mooncakes.io 账号后运行:

    moon login moon check --deny-warn moon test moon package --list moon publish

    发布属于账号级外部操作,需要由账号持有人完成登录确认。

    #项目文档

    • 功能边界与架构:docs/ARCHITECTURE.md
    • 数据格式:docs/DATA_FORMAT.md
    • 比赛验收清单:docs/ACCEPTANCE.md
    • 一页项目申报书:docs/APPLICATION.md
    • 设计决策:docs/adr/0001-domain-boundary.md
    • 更新日志:CHANGELOG.md
    • 第三方声明:THIRD_PARTY_NOTICES.md
    • 贡献指南:CONTRIBUTING.md
    • 安全说明:SECURITY.md

    #原创性与许可证

    项目为原创实现,不移植其他开源项目代码。公历换算采用公开领域的数学公式,MKT 使用公开 的热力学计算公式;详细说明见 THIRD_PARTY_NOTICES.md。

    Apache License 2.0。

    AnalysisConfig

    pub(all) struct AnalysisConfig {
    policy : TemperaturePolicy
    profiles : Array[SensorProfile]
    default_profile : SensorProfile
    merge_duplicate_average : Bool
    interpolate_short_gaps : Bool
    interpolation_limit_seconds : Int64
    conflict_threshold_c : Double
    include_flagged_in_metrics : Bool
    } derive(Eq,
    Debug
    )

    Overall analysis behavior.

    AnalysisConfig::profile_for

    fn AnalysisConfig::profile_for(self : AnalysisConfig, sensor_id : String) -> SensorProfile

    Resolve a sensor-specific profile or clone the wildcard defaults.

    AnalysisReport

    pub(all) struct AnalysisReport {
    schema : String
    shipment_id : String
    generated_at : Int64
    source_name : String
    sensor_ids : Array[String]
    readings : Array[Reading]
    gaps : Array[SamplingGap]
    events : Array[ExcursionEvent]
    statistics : Array[SensorStatistics]
    health : Array[SensorHealth]
    risk : RiskAssessment
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Complete immutable result of one analysis run.

    AnalysisRequest

    pub(all) struct AnalysisRequest {
    shipment_id : String
    source_name : String
    generated_at : Int64
    readings : Array[Reading]
    diagnostics : Array[Diagnostic]
    config : AnalysisConfig
    } derive(Eq,
    Debug
    )

    Metadata supplied by the caller for a reproducible analysis run.

    CsvDocument

    pub(all) struct CsvDocument {
    rows : Array[CsvRow]
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Low-level CSV result. Syntax diagnostics are retained instead of raised.

    CsvRow

    pub(all) struct CsvRow {
    line : Int
    fields : Array[String]
    } derive(Eq,
    Debug
    )

    One lexical CSV row before domain conversion.

    Diagnostic

    pub(all) struct Diagnostic {
    level : DiagnosticLevel
    code : String
    message : String
    line : Int?
    sensor_id : String?
    } derive(Eq,
    Debug
    )

    A diagnostic always points to a stable code and may include an input line.

    Diagnostic::at_line

    fn Diagnostic::at_line(self : Diagnostic, line : Int) -> Diagnostic

    Attach an input line to a diagnostic.

    Diagnostic::for_sensor

    fn Diagnostic::for_sensor(self : Diagnostic, sensor_id : String) -> Diagnostic

    Attach a sensor identifier to a diagnostic.

    DiagnosticLevel

    pub(all) enum DiagnosticLevel {
    Info
    Warning
    Error
    } derive(Eq,
    Debug
    )

    Domain-level diagnostic severity.

    EventStatus

    pub(all) enum EventStatus {
    Candidate
    Confirmed
    Suppressed
    Closed
    } derive(Eq,
    Debug
    )

    Life-cycle status retained for auditability.

    ExcursionEvent

    pub(all) struct ExcursionEvent {
    event_id : String
    sensor_id : String
    kind : ExcursionKind
    status : EventStatus
    started_at : Int64
    ended_at : Int64
    duration_seconds : Int64
    sample_count : Int
    minimum_c : Double?
    maximum_c : Double?
    mean_c : Double?
    degree_seconds : Double
    peak_deviation_c : Double
    reason : String
    } derive(Eq,
    Debug
    )

    One confirmed or suppressed excursion segment.

    ExcursionKind

    pub(all) enum ExcursionKind {
    LowTemperature
    HighTemperature
    DataGap
    SensorFailure
    } derive(Eq,
    Debug
    )

    Classification of an excursion.

    NormalizationResult

    pub(all) struct NormalizationResult {
    readings : Array[Reading]
    gaps : Array[SamplingGap]
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Full output of the standardization stage.

    ParsedTimestamp

    pub(all) struct ParsedTimestamp {
    unix_seconds : Int64
    offset_seconds : Int
    } derive(Eq,
    Debug
    )

    Parsed UTC timestamp and the original offset in seconds.

    QualityFlag

    pub(all) enum QualityFlag {
    DuplicateTimestamp
    MissingField
    OutOfPhysicalRange
    GapBefore
    Calibrated
    SensorStuck
    SensorNoisy
    SensorDrifting
    SensorOffline
    SensorConflict
    UserExcluded
    } derive(Eq,
    Debug
    )

    Quality flags are additive: one sample may carry several independent issues.

    Reading

    pub(all) struct Reading {
    timestamp : Int64
    sensor_id : String
    temperature_c : Double
    humidity_percent : Double?
    battery_percent : Double?
    status : String
    origin : SampleOrigin
    flags : Array[QualityFlag]
    source_line : Int?
    } derive(Eq,
    Debug
    )

    A normalized sensor reading. Timestamp is UTC Unix seconds.

    Reading::add_flag

    fn Reading::add_flag(self : Reading, flag : QualityFlag) -> Reading

    Add a quality flag once while preserving the existing order.

    Reading::has_flag

    fn Reading::has_flag(self : Reading, flag : QualityFlag) -> Bool

    Whether the reading contains a particular quality flag.

    Reading::is_usable

    fn Reading::is_usable(self : Reading) -> Bool

    True when a reading should participate in primary metrics.

    ReadingBatch

    pub(all) struct ReadingBatch {
    readings : Array[Reading]
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Result of parsing or normalizing readings.

    ReportComparison

    pub(all) struct ReportComparison {
    baseline_shipment_id : String
    candidate_shipment_id : String
    score_change : Int
    confidence_change : Int
    confirmed_event_change : Int
    gap_change : Int
    sensor_health_change : Int?
    summary : String
    } derive(Eq,
    Debug
    )

    Differences between two analysis runs.

    RiskAssessment

    pub(all) struct RiskAssessment {
    score : Int
    band : RiskBand
    temperature_component : Int
    duration_component : Int
    data_quality_component : Int
    sensor_health_component : Int
    confidence_percent : Int
    reasons : Array[String]
    } derive(Eq,
    Debug
    )

    Explainable risk score with component contributions.

    RiskBand

    pub(all) enum RiskBand {
    Minimal
    Low
    Moderate
    High
    Critical
    Indeterminate
    } derive(Eq,
    Debug
    )

    Overall risk bands are deliberately non-regulatory.

    SampleOrigin

    pub(all) enum SampleOrigin {
    Recorded
    Interpolated
    Simulated
    } derive(Eq,
    Debug
    )

    Origin of a temperature sample.

    SamplingGap

    pub(all) struct SamplingGap {
    sensor_id : String
    previous_timestamp : Int64
    next_timestamp : Int64
    duration_seconds : Int64
    estimated_missing_samples : Int
    } derive(Eq,
    Debug
    )

    Describes one missing interval in an otherwise ordered sequence.

    SensorHealth

    pub(all) struct SensorHealth {
    sensor_id : String
    score : Int
    stuck_runs : Int
    noisy_steps : Int
    drift_windows : Int
    gaps : Int
    conflicts : Int
    low_battery_samples : Int
    flagged_samples : Int
    observations : Array[String]
    } derive(Eq,
    Debug
    )

    Health assessment for a sensor stream.

    SensorProfile

    pub(all) struct SensorProfile {
    sensor_id : String
    calibration_offset_c : Double
    physical_min_c : Double
    physical_max_c : Double
    expected_interval_seconds : Int64
    stuck_tolerance_c : Double
    stuck_minimum_samples : Int
    noise_step_c : Double
    drift_window_samples : Int
    drift_threshold_c : Double
    } derive(Eq,
    Debug
    )

    Calibration and physical plausibility settings for one sensor.

    SensorQualityResult

    pub(all) struct SensorQualityResult {
    readings : Array[Reading]
    health : Array[SensorHealth]
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Output of sensor quality marking before event analysis.

    SensorStatistics

    pub(all) struct SensorStatistics {
    sensor_id : String
    sample_count : Int
    usable_count : Int
    first_timestamp : Int64?
    last_timestamp : Int64?
    minimum_c : Double?
    maximum_c : Double?
    mean_c : Double?
    time_weighted_mean_c : Double?
    mean_kinetic_temperature_c : Double?
    standard_deviation_c : Double?
    median_c : Double?
    p05_c : Double?
    p95_c : Double?
    in_range_seconds : Int64
    low_seconds : Int64
    high_seconds : Int64
    unknown_seconds : Int64
    low_degree_seconds : Double
    high_degree_seconds : Double
    } derive(Eq,
    Debug
    )

    Summary statistics for one sensor.

    SimulationConfig

    pub(all) struct SimulationConfig {
    scenario : SimulationScenario
    sensor_ids : Array[String]
    started_at : Int64
    duration_seconds : Int64
    interval_seconds : Int64
    baseline_c : Double
    ambient_c : Double
    noise_amplitude_c : Double
    humidity_percent : Double
    battery_percent : Double
    event_start_seconds : Int64
    event_duration_seconds : Int64
    seed : Int64
    } derive(Eq,
    Debug
    )

    Simulation parameters. All timestamps are UTC Unix seconds.

    SimulationResult

    pub(all) struct SimulationResult {
    config : SimulationConfig
    readings : Array[Reading]
    description : String
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Result and prose explanation for a generated scenario.

    SimulationScenario

    pub(all) enum SimulationScenario {
    StableTransit
    DoorOpen
    CoolingFailure
    FreezerExposure
    SensorDropout
    SensorStuckFault
    PowerCycle
    MixedFaults
    } derive(Eq,
    Debug
    )

    Supported deterministic cold-chain fault scenarios.

    TemperaturePolicy

    pub(all) struct TemperaturePolicy {
    lower_c : Double
    upper_c : Double
    hysteresis_c : Double
    grace_seconds : Int64
    minimum_event_seconds : Int64
    maximum_gap_seconds : Int64
    } derive(Eq,
    Debug
    )

    Limits used by the excursion state machine.

    TimestampError

    pub(all) enum TimestampError {
    EmptyTimestamp
    InvalidLength
    InvalidSeparator
    InvalidDigit(Int)
    InvalidYear(Int)
    InvalidMonth(Int)
    InvalidDay(Int)
    InvalidHour(Int)
    InvalidMinute(Int)
    InvalidSecond(Int)
    InvalidOffset
    TrailingContent
    } derive(Eq,
    Debug
    )

    Errors produced by the restricted ISO-8601 parser.

    WindowStatistic

    pub(all) struct WindowStatistic {
    sensor_id : String
    started_at : Int64
    ended_at : Int64
    sample_count : Int
    minimum_c : Double?
    maximum_c : Double?
    mean_c : Double?
    low_degree_seconds : Double
    high_degree_seconds : Double
    } derive(Eq,
    Debug
    )

    One fixed-width time-window aggregate.

    REPORT_SCHEMA

    let REPORT_SCHEMA : String

    Semantic version of the stable report model.

    abs_double

    fn abs_double(value : Double) -> Double

    Absolute value without relying on target-specific math bindings.

    analysis_request

    fn analysis_request(shipment_id : String, source_name : String, generated_at : Int64, readings : Array[Reading]) -> AnalysisRequest

    Create a request with standard 2–8 °C behavior.

    analyze

    fn analyze(request : AnalysisRequest) -> AnalysisReport

    Execute normalization, sensor health, excursions, metrics and risk.

    analyze_csv

    fn analyze_csv(shipment_id : String, source_name : String, generated_at : Int64, input : String, config? : AnalysisConfig) -> AnalysisReport

    Parse and analyze CSV in one library call.

    analyze_sensor_quality

    fn analyze_sensor_quality(input : Array[Reading], gaps : Array[SamplingGap], config : AnalysisConfig) -> SensorQualityResult

    Mark within-sensor quality anomalies and calculate health summaries.

    analyze_simulation

    fn analyze_simulation(scenario_name : String, generated_at? : Int64) -> AnalysisReport

    Analyze a named simulation in one call.

    assess_risk

    fn assess_risk(events : Array[ExcursionEvent], gaps : Array[SamplingGap], diagnostics : Array[Diagnostic], health : Array[SensorHealth]) -> RiskAssessment

    Build an explainable risk assessment from events, gaps and data diagnostics.

    average_sensor_health

    fn average_sensor_health(health : Array[SensorHealth]) -> Int?

    Average sensor health score.

    build_event_timeline

    fn build_event_timeline(readings : Array[Reading], gaps : Array[SamplingGap], policy : TemperaturePolicy) -> Array[ExcursionEvent]

    Combine temperature and data-gap events in chronological order.

    calibrate_and_validate

    fn calibrate_and_validate(readings : Array[Reading], config : AnalysisConfig) -> ReadingBatch

    Apply calibration and physical plausibility checks.

    civil_from_days

    fn civil_from_days(days : Int64) -> (Int, Int, Int)

    Convert days since Unix epoch back to Gregorian year, month and day.

    clamp_double

    fn clamp_double(value : Double, minimum : Double, maximum : Double) -> Double

    Clamp a double to an inclusive range.

    clamp_int

    fn clamp_int(value : Int, minimum : Int, maximum : Int) -> Int

    Clamp an integer to an inclusive range.

    compare_reports

    fn compare_reports(baseline : AnalysisReport, candidate : AnalysisReport) -> ReportComparison

    Compare two reports without re-running their analyses.

    compute_all_statistics

    fn compute_all_statistics(readings : Array[Reading], policy : TemperaturePolicy) -> Array[SensorStatistics]

    Compute summaries for every sensor.

    compute_sensor_statistics

    fn compute_sensor_statistics(sensor_id : String, input : Array[Reading], policy : TemperaturePolicy) -> SensorStatistics

    Compute all summary statistics for one sensor.

    compute_windows

    fn compute_windows(sensor_id : String, input : Array[Reading], policy : TemperaturePolicy, width_seconds : Int64) -> Array[WindowStatistic]

    Fixed-width time windows anchored to the first sample.

    confirmed_events

    fn confirmed_events(events : Array[ExcursionEvent]) -> Array[ExcursionEvent]

    Only confirmed events contribute to the primary risk score.

    copy_array

    fn[T] copy_array(source : Array[T]) -> Array[T]

    Copy an array so APIs do not share mutable array storage accidentally.

    count_diagnostics

    fn count_diagnostics(diagnostics : Array[Diagnostic], level : DiagnosticLevel) -> Int

    Count diagnostics of one level.

    count_events

    fn count_events(events : Array[ExcursionEvent], kind : ExcursionKind, status : EventStatus) -> Int

    Count events matching kind and status.

    count_origin

    fn count_origin(readings : Array[Reading], origin : SampleOrigin) -> Int

    Count readings by origin.

    csv_escape

    fn csv_escape(value : String) -> String

    Escape one CSV cell according to RFC 4180.

    days_from_civil

    fn days_from_civil(year : Int, month : Int, day : Int) -> Int64

    Convert a civil date to days since 1970-01-01. Algorithm adapted from the public-domain civil calendar arithmetic by Howard Hinnant; only the mathematical formula is used.

    days_in_month

    fn days_in_month(year : Int, month : Int) -> Int

    Number of days in a Gregorian month, or zero for an invalid month.

    default_analysis_config

    fn default_analysis_config() -> AnalysisConfig

    Create a configuration for an arbitrary set of sensor identifiers.

    default_sensor_profile

    fn default_sensor_profile(sensor_id : String) -> SensorProfile

    Default profile for common electronic temperature loggers.

    default_simulation_config

    fn default_simulation_config(scenario : SimulationScenario) -> SimulationConfig

    Defaults produce four hours of five-minute readings for two sensors.

    default_temperature_policy

    fn default_temperature_policy() -> TemperaturePolicy

    A conservative policy suitable for a 2–8 °C demonstration data set.

    detect_excursions

    fn detect_excursions(readings : Array[Reading], policy : TemperaturePolicy) -> Array[ExcursionEvent]

    Detect temperature events independently for every sensor.

    detect_sampling_gaps

    fn detect_sampling_gaps(readings : Array[Reading], config : AnalysisConfig) -> NormalizationResult

    Find sampling gaps and tag the first sample after each gap.

    detect_sensor_excursions

    fn detect_sensor_excursions(readings : Array[Reading], policy : TemperaturePolicy) -> Array[ExcursionEvent]

    Detect excursion events for an already ordered single-sensor stream.

    diagnostic_level_name

    fn diagnostic_level_name(level : DiagnosticLevel) -> String

    Stable lowercase name for a diagnostic level.

    diagnostics_have_errors

    fn diagnostics_have_errors(diagnostics : Array[Diagnostic]) -> Bool

    Count diagnostics at or above error level.

    empty_excursion

    fn empty_excursion(event_id : String, sensor_id : String, kind : ExcursionKind, started_at : Int64) -> ExcursionEvent

    Empty event helper used internally by state machines and tests.

    empty_reading_batch

    fn empty_reading_batch() -> ReadingBatch

    Create an empty batch.

    error_diagnostic

    fn error_diagnostic(code : String, message : String) -> Diagnostic

    Create an error diagnostic.

    events_to_csv

    fn events_to_csv(events : Array[ExcursionEvent]) -> String

    Machine-friendly event CSV.

    excursion_kind_name

    fn excursion_kind_name(kind : ExcursionKind) -> String

    Stable lowercase name for an event kind.

    format_duration

    fn format_duration(seconds : Int64) -> String

    Convert seconds into a compact audit-friendly duration.

    format_iso8601

    fn format_iso8601(unix_seconds : Int64) -> String

    Format UTC Unix seconds as canonical YYYY-MM-DDTHH:MM:SSZ.

    gap_events

    fn gap_events(gaps : Array[SamplingGap]) -> Array[ExcursionEvent]

    Convert sampling gaps to events so reports share one event timeline.

    info_diagnostic

    fn info_diagnostic(code : String, message : String) -> Diagnostic

    Create an informational diagnostic.

    interpolate_short_gaps

    fn interpolate_short_gaps(readings : Array[Reading], config : AnalysisConfig) -> ReadingBatch

    Insert expected samples in short gaps. Long gaps remain explicit.

    is_leap_year

    fn is_leap_year(year : Int) -> Bool

    Gregorian leap-year rule.

    json_escape

    fn json_escape(value : String) -> String

    JSON string escaping for stable dependency-free reports.

    mark_sensor_conflicts

    fn mark_sensor_conflicts(input : Array[Reading], threshold_c : Double) -> ReadingBatch

    Compare all sensors sharing a timestamp and mark disagreements.

    maximum_peak_deviation

    fn maximum_peak_deviation(events : Array[ExcursionEvent]) -> Double

    Maximum peak deviation among confirmed temperature events.

    mean

    fn mean(values : Array[Double]) -> Double?

    Arithmetic mean of an array.

    mean_kinetic_temperature

    fn mean_kinetic_temperature(readings : Array[Reading], activation_energy_j_per_mol? : Double) -> Double?

    Mean kinetic temperature using a default activation energy of 83.144 kJ/mol. This is an analytical indicator, not a regulatory disposition decision.

    median

    fn median(values : Array[Double]) -> Double?

    Median convenience wrapper.

    merge_duplicate_readings

    fn merge_duplicate_readings(ordered : Array[Reading], average : Bool) -> ReadingBatch

    Collapse duplicate sensor and timestamp keys.

    normalize_readings

    fn normalize_readings(readings : Array[Reading], config : AnalysisConfig) -> NormalizationResult

    Run the deterministic standardization pipeline.

    observation_span

    fn observation_span(readings : Array[Reading], sensor_id : String) -> Int64

    Determine total observation span per sensor.

    parse_csv_document

    fn parse_csv_document(input : String) -> CsvDocument

    Parse the RFC 4180 features needed by common temperature logger exports: commas, quoted fields, doubled quotes and CRLF/LF line endings.

    parse_decimal

    fn parse_decimal(text : String) -> Double?

    Parse a strict decimal without exponent notation.

    parse_iso8601

    fn parse_iso8601(text : String) -> Result[ParsedTimestamp, TimestampError]

    Parse YYYY-MM-DDTHH:MM:SSZ or the same timestamp with ±HH:MM offset.

    parse_readings_csv

    fn parse_readings_csv(input : String) -> ReadingBatch

    Parse logger CSV data into domain readings.

    parse_simulation_scenario

    fn parse_simulation_scenario(name : String) -> SimulationScenario?

    Parse a CLI-friendly scenario name.

    percentile

    fn percentile(values : Array[Double], percent : Double) -> Double?

    Linear-interpolated percentile in the inclusive 0–100 range.

    quality_flag_name

    fn quality_flag_name(flag : QualityFlag) -> String

    Stable lowercase name for a quality flag.

    reading

    fn reading(timestamp : Int64, sensor_id : String, temperature_c : Double) -> Reading

    Construct the smallest valid reading.

    readings_for_sensor

    fn readings_for_sensor(readings : Array[Reading], sensor_id : String) -> Array[Reading]

    Return all readings for one sensor.

    readings_to_csv

    fn readings_to_csv(readings : Array[Reading]) -> String

    Serialize normalized readings to a stable CSV representation.

    report_summary

    fn report_summary(report : AnalysisReport) -> String

    Compact terminal summary.

    report_to_json

    fn report_to_json(report : AnalysisReport) -> String

    Stable complete JSON report.

    report_to_markdown

    fn report_to_markdown(report : AnalysisReport) -> String

    Human-reviewable Markdown report.

    risk_band_from_score

    fn risk_band_from_score(score : Int, confidence_percent : Int) -> RiskBand

    Convert a numeric score to a non-regulatory risk band.

    risk_band_name

    fn risk_band_name(band : RiskBand) -> String

    Stable lowercase name for a risk band.

    simulate

    fn simulate(config : SimulationConfig) -> SimulationResult

    Generate a complete deterministic simulation.

    simulate_named

    fn simulate_named(name : String) -> SimulationResult

    Convenience simulation used by examples and smoke tests.

    simulation_scenario_description

    fn simulation_scenario_description(scenario : SimulationScenario) -> String

    Human-readable scenario description.

    sort_readings

    fn sort_readings(readings : Array[Reading]) -> Array[Reading]

    Return an ordered copy without mutating caller storage.

    standard_deviation

    fn standard_deviation(values : Array[Double]) -> Double?

    Population standard deviation.

    timestamp_error_message

    fn timestamp_error_message(error : TimestampError) -> String

    Human readable description for timestamp parse errors.

    total_event_duration

    fn total_event_duration(events : Array[ExcursionEvent], kind : ExcursionKind) -> Int64

    Total duration of confirmed events of one kind.

    unique_sensor_ids

    fn unique_sensor_ids(readings : Array[Reading]) -> Array[String]

    Return stable, sorted unique sensor identifiers.

    unix_seconds_from_parts

    fn unix_seconds_from_parts(year : Int, month : Int, day : Int, hour : Int, minute : Int, second : Int, offset_seconds : Int) -> Int64

    Convert validated date-time fields and an explicit offset to Unix seconds.

    validate_analysis_config

    fn validate_analysis_config(config : AnalysisConfig) -> Array[Diagnostic]

    Validate the complete analysis configuration.

    validate_sensor_profile

    fn validate_sensor_profile(profile : SensorProfile) -> Array[Diagnostic]

    Validate one sensor profile.

    validate_simulation_config

    fn validate_simulation_config(config : SimulationConfig) -> Array[Diagnostic]

    Validate a simulation request.

    validate_temperature_policy

    fn validate_temperature_policy(policy : TemperaturePolicy) -> Array[Diagnostic]

    Validate policy relationships and ranges.

    warning_diagnostic

    fn warning_diagnostic(code : String, message : String) -> Diagnostic

    Create a warning diagnostic.

    weakest_sensor

    fn weakest_sensor(health : Array[SensorHealth]) -> SensorHealth?

    Find the weakest sensor health summary.