impl Db { pub fn add_ha_reading_if_due(&self, reading: &HaReading, min_interval_seconds: i64) -> Result { let cutoff = reading.timestamp.clone() - Duration::seconds(min_interval_seconds.max(1)); let conn = self.lock()?; let changed = conn.execute( queries::INSERT_HA_READING_IF_DUE, params![ reading.entity_id, reading.zone_id, reading.kind, reading.timestamp.to_rfc3339(), reading.temperature, cutoff.to_rfc3339(), ], )?; Ok(changed > 0) } pub fn list_ha_history(&self, entity_id: Option<&str>, since: DateTime, bucket_seconds: i64, limit: u32) -> Result> { let conn = self.lock()?; let bucket_seconds = bucket_seconds.max(1); let limit = limit.clamp(1, 20_000) as i64; let mut rows_out = Vec::new(); if let Some(entity_id) = entity_id { let mut stmt = conn.prepare(queries::LIST_HA_HISTORY_BY_ENTITY_BUCKETED)?; let rows = stmt.query_map(params![entity_id, since.to_rfc3339(), bucket_seconds, limit], Self::map_ha_reading)?; for row in rows { rows_out.push(row?); } } else { let mut stmt = conn.prepare(queries::LIST_HA_HISTORY_ALL_BUCKETED)?; let rows = stmt.query_map(params![since.to_rfc3339(), bucket_seconds, limit], Self::map_ha_reading)?; for row in rows { rows_out.push(row?); } } Ok(rows_out) } fn map_ha_reading(row: &rusqlite::Row<'_>) -> rusqlite::Result { let timestamp: String = row.get(4)?; Ok(HaReading { id: row.get(0)?, entity_id: row.get(1)?, zone_id: row.get(2)?, kind: row.get(3)?, timestamp: DateTime::parse_from_rfc3339(×tamp) .map(|value| value.with_timezone(&Utc)) .unwrap_or_else(|_| Utc::now()), temperature: row.get(5)?, }) } }