thermo_trail

Deterministic cold-chain temperature excursion analysis and replay engine

cold-chain
time-series
sensor
simulation
risk-analysis
moon add sujy123456/thermo_trail@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
12 days ago
Downloads
2
README

#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.