#![no_std]
use ri_esp_core::{EnvironmentReading, ReadingStatus};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PromptId {
HighHeatHumidity,
HotRoom,
HumidRoom,
MissingSensor,
NormalRoom,
SafeAction,
StaleData,
LocalFirstMeans,
}
impl PromptId {
pub const fn prompt_text(self) -> &'static str {
match self {
PromptId::HighHeatHumidity => "high heat and humidity. action is ",
PromptId::HotRoom => "hot room. action is ",
PromptId::HumidRoom => "humid room. action is ",
PromptId::MissingSensor => "missing sensor. action is ",
PromptId::NormalRoom => "normal room. action is ",
PromptId::SafeAction => "safe action is ",
PromptId::StaleData => "stale data. action is ",
PromptId::LocalFirstMeans => "local first means ",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PolicyReason {
ProofPromptOverride,
SensorMissing,
StaleReading,
HotHumid,
HotRoom,
HumidRoom,
UnsupportedColdOrDry,
Normal,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PolicyContext<'a> {
pub reading: EnvironmentReading,
pub uptime_ms: u64,
pub last_read_ms: u64,
pub proof_prompt_override: Option<&'a str>,
}
impl<'a> PolicyContext<'a> {
pub fn new(reading: EnvironmentReading, uptime_ms: u64) -> Self {
Self {
reading,
uptime_ms,
last_read_ms: 0,
proof_prompt_override: None,
}
}
pub fn with_last_read_ms(mut self, ms: u64) -> Self {
self.last_read_ms = ms;
self
}
pub fn with_proof_prompt_override(mut self, prompt: &'a str) -> Self {
self.proof_prompt_override = Some(prompt);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PolicyDecision {
pub prompt_id: PromptId,
pub reason: PolicyReason,
pub ai_route: bool,
pub confidence: f32,
prompt_text: &'static str,
}
impl PolicyDecision {
pub fn prompt(&self) -> &'static str {
self.prompt_text
}
}
pub struct SensorPolicy {
pub stale_ms: u64,
pub hot_f: f32,
pub cold_f: f32,
pub humid_pct: f32,
pub dry_pct: f32,
}
impl Default for SensorPolicy {
fn default() -> Self {
Self {
stale_ms: 120_000,
hot_f: 82.0,
cold_f: 60.0,
humid_pct: 65.0,
dry_pct: 25.0,
}
}
}
fn match_prompt_id(s: &str) -> PromptId {
match s {
"high heat and humidity. action is " => PromptId::HighHeatHumidity,
"hot room. action is " => PromptId::HotRoom,
"humid room. action is " => PromptId::HumidRoom,
"missing sensor. action is " => PromptId::MissingSensor,
"normal room. action is " => PromptId::NormalRoom,
"safe action is " => PromptId::SafeAction,
"stale data. action is " => PromptId::StaleData,
"local first means " => PromptId::LocalFirstMeans,
_ => PromptId::NormalRoom,
}
}
const fn ai_route_for(id: PromptId) -> bool {
match id {
PromptId::HighHeatHumidity => true,
PromptId::HotRoom => true,
PromptId::HumidRoom => true,
PromptId::MissingSensor => true,
PromptId::StaleData => true,
PromptId::NormalRoom => false,
PromptId::SafeAction => false,
PromptId::LocalFirstMeans => false,
}
}
fn decision(id: PromptId, reason: PolicyReason) -> PolicyDecision {
let conf = match id {
PromptId::HighHeatHumidity | PromptId::HotRoom | PromptId::HumidRoom => 0.9,
PromptId::MissingSensor => 0.0,
PromptId::StaleData => 0.3,
PromptId::NormalRoom => 1.0,
PromptId::SafeAction => 0.5,
PromptId::LocalFirstMeans => 0.8,
};
PolicyDecision {
prompt_id: id,
reason,
ai_route: ai_route_for(id),
confidence: conf,
prompt_text: id.prompt_text(),
}
}
impl SensorPolicy {
pub fn evaluate(&self, ctx: PolicyContext) -> PolicyDecision {
if let Some(override_prompt) = ctx.proof_prompt_override {
let id = match_prompt_id(override_prompt);
return PolicyDecision {
prompt_id: id,
reason: PolicyReason::ProofPromptOverride,
ai_route: ai_route_for(id),
confidence: 1.0,
prompt_text: id.prompt_text(),
};
}
let r = &ctx.reading;
if r.status == ReadingStatus::SensorReadFailed || r.status == ReadingStatus::NotConnected {
return decision(PromptId::MissingSensor, PolicyReason::SensorMissing);
}
if r.status == ReadingStatus::Stale {
return decision(PromptId::StaleData, PolicyReason::StaleReading);
}
if ctx.uptime_ms.saturating_sub(ctx.last_read_ms) > self.stale_ms {
return decision(PromptId::StaleData, PolicyReason::StaleReading);
}
let temp_c = r.temperature_c.unwrap_or(25.0);
let humidity = r.humidity_pct.unwrap_or(50.0);
let temp_f = temp_c * 9.0 / 5.0 + 32.0;
if temp_f >= self.hot_f && humidity >= self.humid_pct {
return decision(PromptId::HighHeatHumidity, PolicyReason::HotHumid);
}
if temp_f >= self.hot_f {
return decision(PromptId::HotRoom, PolicyReason::HotRoom);
}
if humidity >= self.humid_pct {
return decision(PromptId::HumidRoom, PolicyReason::HumidRoom);
}
if temp_f <= self.cold_f || humidity <= self.dry_pct {
return decision(PromptId::SafeAction, PolicyReason::UnsupportedColdOrDry);
}
decision(PromptId::NormalRoom, PolicyReason::Normal)
}
}