%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.12%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%3E%3D0.24.0%22%2C%0A%23%20%20%20%20%20%22polars%3E%3D1.33.1%22%2C%0A%23%20%5D%0A%23%0A%23%20%5Btool.marimo-studio%5D%0A%23%20default%20%3D%20%22monitor%22%0A%23%20preserve_session%20%3D%20false%0A%23%20runtime%20%3D%20%22server%22%0A%23%20runtimes%20%3D%20%5B%22server%22%2C%20%22wasm%22%2C%20%22zero-python%22%5D%0A%23%20show_cell_logs%20%3D%20false%0A%23%0A%23%20%5Btool.marimo-studio.cells%5D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.0%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22%2C%20app_title%3D%22Building%20Occupancy%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20introduction(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%20Building%20Occupancy%0A%0A%20%20%20%20Environmental%20readings%20from%20Room%2001%20cover%20temperature%2C%20humidity%2C%20light%2C%0A%20%20%20%20carbon%20dioxide%2C%20and%20observed%20occupancy.%20Sensor%20deviations%20are%20measured%0A%20%20%20%20against%20rolling%20baselines%2C%20and%20a%20transparent%20occupancy%20score%20is%20evaluated%0A%20%20%20%20across%20thresholds.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20io%0A%20%20%20%20import%20urllib.request%0A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20polars%20as%20pl%0A%0A%20%20%20%20return%20io%2C%20mo%2C%20pl%2C%20urllib%0A%0A%0A%40app.cell%0Adef%20analysis_parameters()%3A%0A%20%20%20%20occupancy_parameters%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22monitor%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22anomaly_min_samples%22%3A%2024%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22anomaly_quantile%22%3A%200.95%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22anomaly_window%22%3A%20120%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22baseline_min_samples%22%3A%2012%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22baseline_window%22%3A%2060%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22model%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22co2_weight%22%3A%200.3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22default_threshold%22%3A%200.5%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22light_weight%22%3A%200.7%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22normalization_quantile%22%3A%200.99%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22threshold_maximum%22%3A%200.9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22threshold_minimum%22%3A%200.1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22threshold_step%22%3A%200.05%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%20(occupancy_parameters%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20data_context(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%23%20Room%20observations%0A%0A%20%20%20%20We%20load%20the%20training%20split%20from%20Luis%20Candanedo's%0A%20%20%20%20%5BUCI%20Occupancy%20Detection%20dataset%5D(https%3A%2F%2Fdoi.org%2F10.24432%2FC5X01N)%2C%20order%0A%20%20%20%20the%20observations%20by%20timestamp%2C%20and%20retain%20the%20reported%20physical%20units.%20The%0A%20%20%20%20dataset%20is%20licensed%20under%20%5BCC%20BY%204.0%5D(https%3A%2F%2Fcreativecommons.org%2Flicenses%2Fby%2F4.0%2F).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20load_readings(io%2C%20pl%2C%20urllib)%3A%0A%20%20%20%20source_url%20%3D%20(%0A%20%20%20%20%20%20%20%20%22https%3A%2F%2Fraw.githubusercontent.com%2Fholoviz%2Fpanel%2F%22%0A%20%20%20%20%20%20%20%20%2262dda3f986251295ab4892a45e02c7f1533cf4c0%2Fexamples%2Fassets%2Foccupancy.csv%22%0A%20%20%20%20)%0A%20%20%20%20with%20urllib.request.urlopen(source_url)%20as%20response%3A%0A%20%20%20%20%20%20%20%20source%20%3D%20io.BytesIO(response.read())%0A%20%20%20%20readings%20%3D%20pl.read_csv(source%2C%20try_parse_dates%3DTrue).sort(%22date%22)%0A%20%20%20%20readings.head(8)%0A%20%20%20%20return%20(readings%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20scope_context(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%23%20Observation%20scope%0A%0A%20%20%20%20Choose%20the%20slice%20of%20room%20activity%20used%20by%20every%20analysis%20below.%20The%20sensor%0A%20%20%20%20history%2C%20room%20profiles%2C%20model%20evidence%2C%20and%20published%20views%20recompute%20from%0A%20%20%20%20the%20same%20selected%20observations.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20analysis_scope_control(mo)%3A%0A%20%20%20%20analysis_scope%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20options%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Complete%20record%22%3A%20%22all%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Weekdays%20%C2%B7%2007%3A00%E2%80%9319%3A00%22%3A%20%22operating-hours%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Off-hours%20%2B%20weekends%22%3A%20%22off-hours%22%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20value%3D%22Complete%20record%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Scope%22%2C%0A%20%20%20%20)%0A%20%20%20%20analysis_scope%0A%20%20%20%20return%20(analysis_scope%2C)%0A%0A%0A%40app.cell%0Adef%20select_observations(analysis_scope%2C%20pl%2C%20readings)%3A%0A%20%20%20%20_operating_hours%20%3D%20(%0A%20%20%20%20%20%20%20%20(pl.col(%22date%22).dt.weekday()%20%3C%3D%205)%0A%20%20%20%20%20%20%20%20%26%20(pl.col(%22date%22).dt.hour()%20%3E%3D%207)%0A%20%20%20%20%20%20%20%20%26%20(pl.col(%22date%22).dt.hour()%20%3C%2019)%0A%20%20%20%20)%0A%20%20%20%20if%20analysis_scope.value%20%3D%3D%20%22operating-hours%22%3A%0A%20%20%20%20%20%20%20%20scoped_readings%20%3D%20readings.filter(_operating_hours)%0A%20%20%20%20%20%20%20%20scope_label%20%3D%20%22Weekdays%20%C2%B7%2007%3A00%E2%80%9319%3A00%22%0A%20%20%20%20elif%20analysis_scope.value%20%3D%3D%20%22off-hours%22%3A%0A%20%20%20%20%20%20%20%20scoped_readings%20%3D%20readings.filter(~_operating_hours)%0A%20%20%20%20%20%20%20%20scope_label%20%3D%20%22Off-hours%20%2B%20weekends%22%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20scoped_readings%20%3D%20readings%0A%20%20%20%20%20%20%20%20scope_label%20%3D%20%22Complete%20record%22%0A%20%20%20%20scoped_readings.head(8)%0A%20%20%20%20return%20scope_label%2C%20scoped_readings%0A%0A%0A%40app.cell%0Adef%20summarize_scope(pl%2C%20scope_label%2C%20scoped_readings)%3A%0A%20%20%20%20_occupied_readings%20%3D%20scoped_readings.select(pl.col(%22Occupancy%22).sum()).item()%0A%20%20%20%20_reading_interval_minutes%20%3D%20scoped_readings.select(%0A%20%20%20%20%20%20%20%20pl.col(%22date%22).diff().dt.total_seconds().median()%20%2F%2060%0A%20%20%20%20).item()%0A%20%20%20%20scope_summary%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22room%22%3A%20%22Room%2001%22%2C%0A%20%20%20%20%20%20%20%20%22scope_label%22%3A%20scope_label%2C%0A%20%20%20%20%20%20%20%20%22period_start%22%3A%20scoped_readings%5B%22date%22%5D.min().strftime(%22%25Y-%25m-%25d%20%25H%3A%25M%22)%2C%0A%20%20%20%20%20%20%20%20%22period_end%22%3A%20scoped_readings%5B%22date%22%5D.max().strftime(%22%25Y-%25m-%25d%20%25H%3A%25M%22)%2C%0A%20%20%20%20%20%20%20%20%22observations%22%3A%20scoped_readings.height%2C%0A%20%20%20%20%20%20%20%20%22occupied%22%3A%20_occupied_readings%2C%0A%20%20%20%20%20%20%20%20%22occupancy_rate%22%3A%20scoped_readings.select(pl.col(%22Occupancy%22).mean()).item()%2C%0A%20%20%20%20%20%20%20%20%22reading_interval_minutes%22%3A%20float(_reading_interval_minutes)%2C%0A%20%20%20%20%20%20%20%20%22estimated_occupied_hours%22%3A%20float(%0A%20%20%20%20%20%20%20%20%20%20%20%20_occupied_readings%20*%20_reading_interval_minutes%20%2F%2060%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%20(scope_summary%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20monitor_context(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%23%20Environmental%20signal%0A%0A%20%20%20%20Select%20a%20physical%20measure%20to%20compare%20its%20history%20in%20the%20current%20observation%0A%20%20%20%20scope%20with%20a%20rolling%20baseline%20and%20the%20resulting%20anomaly%20candidates.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20metric_control(mo)%3A%0A%20%20%20%20metric%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20options%3D%5B%22CO2%22%2C%20%22Light%22%2C%20%22Temperature%22%2C%20%22Humidity%22%5D%2C%0A%20%20%20%20%20%20%20%20value%3D%22CO2%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Signal%22%2C%0A%20%20%20%20)%0A%20%20%20%20metric%0A%20%20%20%20return%20(metric%2C)%0A%0A%0A%40app.cell%0Adef%20sensor_analysis(%0A%20%20%20%20metric%2C%0A%20%20%20%20occupancy_parameters%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20scope_summary%2C%0A%20%20%20%20scoped_readings%2C%0A)%3A%0A%20%20%20%20_monitor%20%3D%20occupancy_parameters%5B%22monitor%22%5D%0A%20%20%20%20selected_sensor_series%20%3D%20(%0A%20%20%20%20%20%20%20%20scoped_readings.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22date%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Occupancy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(metric.value).alias(%22metric%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(metric.value).cast(pl.Float32).alias(%22value%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22value%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rolling_mean(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20window_size%3D_monitor%5B%22baseline_window%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples%3D_monitor%5B%22baseline_min_samples%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22baseline%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns((pl.col(%22value%22)%20-%20pl.col(%22baseline%22)).abs().alias(%22deviation%22))%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22deviation%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rolling_quantile(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_monitor%5B%22anomaly_quantile%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20window_size%3D_monitor%5B%22anomaly_window%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples%3D_monitor%5B%22anomaly_min_samples%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22limit%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22deviation%22)%20%3E%20pl.col(%22limit%22)).fill_null(False).alias(%22anomaly%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.select(%22date%22%2C%20%22Occupancy%22%2C%20%22metric%22%2C%20%22value%22%2C%20%22baseline%22%2C%20%22anomaly%22)%0A%20%20%20%20)%0A%20%20%20%20current_window%20%3D%20selected_sensor_series.tail(180)%0A%20%20%20%20occupancy_summary%20%3D%20%7B%0A%20%20%20%20%20%20%20%20**scope_summary%2C%0A%20%20%20%20%20%20%20%20%22metric%22%3A%20metric.value%2C%0A%20%20%20%20%20%20%20%20%22anomalies%22%3A%20selected_sensor_series.filter(pl.col(%22anomaly%22)).height%2C%0A%20%20%20%20%20%20%20%20%22monitor%22%3A%20_monitor%2C%0A%20%20%20%20%7D%0A%20%20%20%20current_window.tail(8)%0A%20%20%20%20return%20(occupancy_summary%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20room_profile_context(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%23%20Room%20use%20profiles%0A%0A%20%20%20%20Aggregate%20the%20readings%20by%20hour%20and%20day%2C%20then%20compare%20environmental%0A%20%20%20%20conditions%20recorded%20while%20the%20room%20was%20occupied%20and%20vacant.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20room_profiles(pl%2C%20scoped_readings)%3A%0A%20%20%20%20hourly_room_profile%20%3D%20(%0A%20%20%20%20%20%20%20%20scoped_readings.group_by_dynamic(%22date%22%2C%20every%3D%221h%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Occupancy%22).mean().alias(%22occupancy_rate%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22CO2%22).mean().alias(%22co2%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Temperature%22).mean().alias(%22temperature%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Humidity%22).mean().alias(%22humidity%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Light%22).mean().alias(%22light%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22date%22).dt.strftime(%22%25Y-%25m-%25dT%25H%3A%25M%3A%25S%22).alias(%22timestamp%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22timestamp%22%2C%20%22occupancy_rate%22%2C%20%22co2%22%2C%20%22temperature%22%2C%20%22humidity%22%2C%20%22light%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20daily_room_profile%20%3D%20(%0A%20%20%20%20%20%20%20%20scoped_readings.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22date%22).dt.strftime(%22%25Y-%25m-%25d%22).alias(%22day%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.group_by(%22day%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.len().alias(%22observations%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Occupancy%22).sum().alias(%22occupied%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Occupancy%22).mean().alias(%22occupancy_rate%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22CO2%22).mean().alias(%22mean_co2%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22CO2%22).max().alias(%22peak_co2%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Temperature%22).mean().alias(%22mean_temperature%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22day%22)%0A%20%20%20%20)%0A%20%20%20%20daily_occupancy_peak%20%3D%20daily_room_profile.sort(%0A%20%20%20%20%20%20%20%20%5B%22occupancy_rate%22%2C%20%22day%22%5D%2C%20descending%3D%5BTrue%2C%20False%5D%0A%20%20%20%20).row(0%2C%20named%3DTrue)%0A%0A%20%20%20%20_sensor_profile_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_column%2C%20_label%2C%20_unit%20in%20(%0A%20%20%20%20%20%20%20%20(%22CO2%22%2C%20%22Carbon%20dioxide%22%2C%20%22ppm%22)%2C%0A%20%20%20%20%20%20%20%20(%22Light%22%2C%20%22Light%22%2C%20%22lux%22)%2C%0A%20%20%20%20%20%20%20%20(%22Temperature%22%2C%20%22Temperature%22%2C%20%22%C2%B0C%22)%2C%0A%20%20%20%20%20%20%20%20(%22Humidity%22%2C%20%22Humidity%22%2C%20%22%25%22)%2C%0A%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20_vacant%20%3D%20scoped_readings.filter(pl.col(%22Occupancy%22)%20%3D%3D%200)%5B_column%5D.mean()%0A%20%20%20%20%20%20%20%20_occupied%20%3D%20scoped_readings.filter(pl.col(%22Occupancy%22)%20%3D%3D%201)%5B_column%5D.mean()%0A%20%20%20%20%20%20%20%20_minimum%20%3D%20scoped_readings%5B_column%5D.min()%0A%20%20%20%20%20%20%20%20_maximum%20%3D%20scoped_readings%5B_column%5D.max()%0A%20%20%20%20%20%20%20%20_sensor_profile_rows.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22key%22%3A%20_column%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22label%22%3A%20_label%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22unit%22%3A%20_unit%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22vacant%22%3A%20float(_vacant)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22occupied%22%3A%20float(_occupied)%20if%20_occupied%20is%20not%20None%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22minimum%22%3A%20float(_minimum)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22maximum%22%3A%20float(_maximum)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22relative_separation%22%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20abs(float(_occupied)%20-%20float(_vacant))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%20max(float(_maximum)%20-%20float(_minimum)%2C%201.0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_occupied%20is%20not%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20sensor_profiles%20%3D%20pl.DataFrame(_sensor_profile_rows)%0A%20%20%20%20_available_sensor_separations%20%3D%20sensor_profiles.filter(%0A%20%20%20%20%20%20%20%20pl.col(%22relative_separation%22).is_not_null()%0A%20%20%20%20)%0A%20%20%20%20sensor_separation_peak%20%3D%20(%0A%20%20%20%20%20%20%20%20_available_sensor_separations.sort(%22relative_separation%22%2C%20descending%3DTrue).row(%0A%20%20%20%20%20%20%20%20%20%20%20%200%2C%20named%3DTrue%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20_available_sensor_separations.height%0A%20%20%20%20%20%20%20%20else%20None%0A%20%20%20%20)%0A%0A%20%20%20%20daily_room_profile%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20daily_occupancy_peak%2C%0A%20%20%20%20%20%20%20%20daily_room_profile%2C%0A%20%20%20%20%20%20%20%20hourly_room_profile%2C%0A%20%20%20%20%20%20%20%20sensor_profiles%2C%0A%20%20%20%20%20%20%20%20sensor_separation_peak%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20model_context(mo)%3A%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20%23%23%20Occupancy%20score%0A%0A%20%20%20%20A%20transparent%20score%20combines%20normalized%20light%20and%20CO2%20readings.%20The%0A%20%20%20%20threshold%20control%20exposes%20the%20in-sample%20precision%20and%20recall%20tradeoff%20while%0A%20%20%20%20each%20component%20of%20the%20calculation%20remains%20explicit.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20threshold_control(mo%2C%20occupancy_parameters)%3A%0A%20%20%20%20_model%20%3D%20occupancy_parameters%5B%22model%22%5D%0A%20%20%20%20threshold%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D_model%5B%22threshold_minimum%22%5D%2C%0A%20%20%20%20%20%20%20%20stop%3D_model%5B%22threshold_maximum%22%5D%2C%0A%20%20%20%20%20%20%20%20step%3D_model%5B%22threshold_step%22%5D%2C%0A%20%20%20%20%20%20%20%20value%3D_model%5B%22default_threshold%22%5D%2C%0A%20%20%20%20%20%20%20%20label%3D%22Occupancy%20threshold%22%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20threshold%0A%20%20%20%20return%20(threshold%2C)%0A%0A%0A%40app.cell%0Adef%20model_sweep(occupancy_parameters%2C%20pl%2C%20scoped_readings)%3A%0A%20%20%20%20_model%20%3D%20occupancy_parameters%5B%22model%22%5D%0A%20%20%20%20light_max%20%3D%20scoped_readings.select(%0A%20%20%20%20%20%20%20%20pl.col(%22Light%22).quantile(_model%5B%22normalization_quantile%22%5D)%0A%20%20%20%20).item()%0A%20%20%20%20co2_min%20%3D%20scoped_readings%5B%22CO2%22%5D.min()%0A%20%20%20%20co2_max%20%3D%20scoped_readings.select(%0A%20%20%20%20%20%20%20%20pl.col(%22CO2%22).quantile(_model%5B%22normalization_quantile%22%5D)%0A%20%20%20%20).item()%0A%20%20%20%20scored%20%3D%20scoped_readings.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22Light%22).clip(0%2C%20light_max).truediv(light_max).alias(%22light_score%22)%2C%0A%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22CO2%22).clip(co2_min%2C%20co2_max)%20-%20co2_min).truediv(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max(co2_max%20-%20co2_min%2C%201.0)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20).alias(%22co2_score%22)%2C%0A%20%20%20%20).with_columns(%0A%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_model%5B%22light_weight%22%5D%20*%20pl.col(%22light_score%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20_model%5B%22co2_weight%22%5D%20*%20pl.col(%22co2_score%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.cast(pl.Float32)%0A%20%20%20%20%20%20%20%20.alias(%22score%22)%0A%20%20%20%20)%0A%0A%20%20%20%20def%20evaluate(_threshold)%3A%0A%20%20%20%20%20%20%20%20_rows%20%3D%20scored.with_columns((pl.col(%22score%22)%20%3E%3D%20_threshold).alias(%22predicted%22))%0A%20%20%20%20%20%20%20%20_tp%20%3D%20_rows.filter(pl.col(%22predicted%22)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%201)).height%0A%20%20%20%20%20%20%20%20_tn%20%3D%20_rows.filter(~pl.col(%22predicted%22)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200)).height%0A%20%20%20%20%20%20%20%20_fp%20%3D%20_rows.filter(pl.col(%22predicted%22)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200)).height%0A%20%20%20%20%20%20%20%20_fn%20%3D%20_rows.filter(~pl.col(%22predicted%22)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%201)).height%0A%20%20%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22threshold%22%3A%20_threshold%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22accuracy%22%3A%20(_tp%20%2B%20_tn)%20%2F%20scored.height%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22precision%22%3A%20_tp%20%2F%20max(_tp%20%2B%20_fp%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22recall%22%3A%20_tp%20%2F%20max(_tp%20%2B%20_fn%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22true_positive%22%3A%20_tp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22true_negative%22%3A%20_tn%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22false_positive%22%3A%20_fp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22false_negative%22%3A%20_fn%2C%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20def%20error_evidence(_threshold)%3A%0A%20%20%20%20%20%20%20%20_prediction_rows%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20scored.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22score%22)%20%3E%3D%20_threshold).cast(pl.Int8).alias(%22predicted%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.when((pl.col(%22predicted%22)%20%3D%3D%201)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%201))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22true%20positive%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.when((pl.col(%22predicted%22)%20%3D%3D%200)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22true%20negative%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.when((pl.col(%22predicted%22)%20%3D%3D%201)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22false%20positive%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.otherwise(pl.lit(%22false%20negative%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.cast(pl.Categorical)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22outcome%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22date%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Temperature%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Humidity%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Light%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22CO2%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Occupancy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22score%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22predicted%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22outcome%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_prediction_rows.filter(pl.col(%22Occupancy%22)%20!%3D%20pl.col(%22predicted%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22score%22)%20-%20_threshold).abs().alias(%22distance%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22date%22).dt.strftime(%22%25Y-%25m-%25dT%25H%3A%25M%3A%25S%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22distance%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_threshold_steps%20%3D%20int(%0A%20%20%20%20%20%20%20%20round(%0A%20%20%20%20%20%20%20%20%20%20%20%20(_model%5B%22threshold_maximum%22%5D%20-%20_model%5B%22threshold_minimum%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%20_model%5B%22threshold_step%22%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20thresholds%20%3D%20%5B%0A%20%20%20%20%20%20%20%20round(%0A%20%20%20%20%20%20%20%20%20%20%20%20_model%5B%22threshold_minimum%22%5D%20%2B%20_model%5B%22threshold_step%22%5D%20*%20index%2C%0A%20%20%20%20%20%20%20%20%20%20%20%202%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20for%20index%20in%20range(_threshold_steps%20%2B%201)%0A%20%20%20%20%5D%0A%20%20%20%20threshold_metrics%20%3D%20pl.DataFrame(%0A%20%20%20%20%20%20%20%20%5Bevaluate(_threshold)%20for%20_threshold%20in%20thresholds%5D%0A%20%20%20%20)%0A%20%20%20%20model_evidence%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20**evaluate(_threshold)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22errors%22%3A%20error_evidence(_threshold).head(14).to_dicts()%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20for%20_threshold%20in%20thresholds%0A%20%20%20%20%5D%0A%20%20%20%20model_normalization%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22light_min%22%3A%200.0%2C%0A%20%20%20%20%20%20%20%20%22light_max%22%3A%20float(light_max)%2C%0A%20%20%20%20%20%20%20%20%22co2_min%22%3A%20float(co2_min)%2C%0A%20%20%20%20%20%20%20%20%22co2_max%22%3A%20float(co2_max)%2C%0A%20%20%20%20%7D%0A%20%20%20%20threshold_metrics%0A%20%20%20%20return%20model_evidence%2C%20model_normalization%2C%20scored%0A%0A%0A%40app.cell%0Adef%20selected_model(model_normalization%2C%20pl%2C%20scored%2C%20threshold)%3A%0A%20%20%20%20model_summary%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22threshold%22%3A%20threshold.value%2C%0A%20%20%20%20%20%20%20%20%22normalization%22%3A%20model_normalization%2C%0A%20%20%20%20%7D%0A%20%20%20%20prediction_rows%20%3D%20(%0A%20%20%20%20%20%20%20%20scored.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22score%22)%20%3E%3D%20threshold.value).cast(pl.Int8).alias(%22predicted%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.when((pl.col(%22predicted%22)%20%3D%3D%201)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%201))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22true%20positive%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when((pl.col(%22predicted%22)%20%3D%3D%200)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22true%20negative%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when((pl.col(%22predicted%22)%20%3D%3D%201)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22false%20positive%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.otherwise(pl.lit(%22false%20negative%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.cast(pl.Categorical)%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22outcome%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22date%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Temperature%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Humidity%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Light%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22CO2%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Occupancy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22score%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22predicted%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22outcome%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20ranked_model_errors%20%3D%20(%0A%20%20%20%20%20%20%20%20prediction_rows.filter(pl.col(%22Occupancy%22)%20!%3D%20pl.col(%22predicted%22))%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22score%22)%20-%20model_summary%5B%22threshold%22%5D).abs().alias(%22distance%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22distance%22%2C%20descending%3DTrue)%0A%20%20%20%20)%0A%20%20%20%20_true_positive%20%3D%20prediction_rows.filter(%0A%20%20%20%20%20%20%20%20(pl.col(%22predicted%22)%20%3D%3D%201)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%201)%0A%20%20%20%20).height%0A%20%20%20%20_true_negative%20%3D%20prediction_rows.filter(%0A%20%20%20%20%20%20%20%20(pl.col(%22predicted%22)%20%3D%3D%200)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200)%0A%20%20%20%20).height%0A%20%20%20%20_false_positive%20%3D%20prediction_rows.filter(%0A%20%20%20%20%20%20%20%20(pl.col(%22predicted%22)%20%3D%3D%201)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%200)%0A%20%20%20%20).height%0A%20%20%20%20_false_negative%20%3D%20prediction_rows.filter(%0A%20%20%20%20%20%20%20%20(pl.col(%22predicted%22)%20%3D%3D%200)%20%26%20(pl.col(%22Occupancy%22)%20%3D%3D%201)%0A%20%20%20%20).height%0A%20%20%20%20model_summary.update(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22accuracy%22%3A%20(_true_positive%20%2B%20_true_negative)%20%2F%20prediction_rows.height%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22precision%22%3A%20_true_positive%20%2F%20max(_true_positive%20%2B%20_false_positive%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22recall%22%3A%20_true_positive%20%2F%20max(_true_positive%20%2B%20_false_negative%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22true_positive%22%3A%20_true_positive%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22true_negative%22%3A%20_true_negative%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22false_positive%22%3A%20_false_positive%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22false_negative%22%3A%20_false_negative%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20return%20(model_summary%2C)%0A%0A%0A%40app.cell%0Adef%20occupancy_analysis_snapshot(%0A%20%20%20%20analysis_scope%2C%0A%20%20%20%20daily_occupancy_peak%2C%0A%20%20%20%20daily_room_profile%2C%0A%20%20%20%20hourly_room_profile%2C%0A%20%20%20%20model_evidence%2C%0A%20%20%20%20model_normalization%2C%0A%20%20%20%20occupancy_parameters%2C%0A%20%20%20%20scope_summary%2C%0A%20%20%20%20sensor_profiles%2C%0A%20%20%20%20sensor_separation_peak%2C%0A)%3A%0A%20%20%20%20occupancy_analysis%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22selection%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22scope%22%3A%20analysis_scope.value%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22summary%22%3A%20scope_summary%2C%0A%20%20%20%20%20%20%20%20%22hourly_room_profile%22%3A%20hourly_room_profile.to_dicts()%2C%0A%20%20%20%20%20%20%20%20%22daily_room_profile%22%3A%20daily_room_profile.to_dicts()%2C%0A%20%20%20%20%20%20%20%20%22sensor_profiles%22%3A%20sensor_profiles.to_dicts()%2C%0A%20%20%20%20%20%20%20%20%22profile_summary%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22highest_occupancy_day%22%3A%20daily_occupancy_peak%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22widest_sensor_separation%22%3A%20sensor_separation_peak%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22model%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20**occupancy_parameters%5B%22model%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22normalization%22%3A%20model_normalization%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22evidence%22%3A%20model_evidence%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%7D%0A%20%20%20%20occupancy_analysis%5B%22summary%22%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20conclusion(mo%2C%20model_summary%2C%20occupancy_summary)%3A%0A%20%20%20%20_recall_reading%20%3D%20(%0A%20%20%20%20%20%20%20%20f%22**%7Bmodel_summary%5B'recall'%5D%3A.1%25%7D%20in-sample%20recall**%22%0A%20%20%20%20%20%20%20%20if%20occupancy_summary%5B%22occupied%22%5D%0A%20%20%20%20%20%20%20%20else%20%22**recall%20unavailable%20because%20this%20scope%20contains%20no%20occupied%20observations**%22%0A%20%20%20%20)%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20Operational%20reading%0A%0A%20%20%20%20%20%20%20%20The%20source%20records%20occupancy%20for%20**%7Boccupancy_summary%5B%22occupancy_rate%22%5D%3A.1%25%7D**%0A%20%20%20%20%20%20%20%20of%20observations.%20At%20a%20threshold%20of%20**%7Bmodel_summary%5B%22threshold%22%5D%3A.2f%7D**%2C%0A%20%20%20%20%20%20%20%20the%20transparent%20score%20reaches%20**%7Bmodel_summary%5B%22accuracy%22%5D%3A.1%25%7D%0A%20%20%20%20%20%20%20%20in-sample%20accuracy**%2C%20with%20%7B_recall_reading%7D%20on%20the%20selected%20readings.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
3e730e808bc87c3e5ada31461c997e19