nwp500-python 9.8.0__tar.gz → 9.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/CHANGELOG.rst +147 -0
- {nwp500_python-9.8.0/src/nwp500_python.egg-info → nwp500_python-9.10.0}/PKG-INFO +1 -1
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/simulator.rst +5 -1
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/calibrate-simulator.rst +119 -12
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/simulate-heater.rst +85 -5
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/sim.rst +12 -9
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/simulator-inputs.rst +18 -1
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/__main__.py +2 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/simulate.py +46 -77
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/api.py +1 -5
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/calibration.py +457 -33
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/codec.py +88 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/cold_supply.py +13 -52
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/decay.py +32 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/params.py +21 -1
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/physics/compressor.py +12 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/physics/tank.py +68 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/tank_physics.py +2 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim_adapters.py +412 -84
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim_calibrate.py +615 -164
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim_home_assistant.py +105 -168
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim_live.py +149 -16
- {nwp500_python-9.8.0 → nwp500_python-9.10.0/src/nwp500_python.egg-info}/PKG-INFO +1 -1
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/SOURCES.txt +4 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/scm_file_list.json +4 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/scm_version.json +2 -2
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_cli_simulate.py +3 -1
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_book_history.py +185 -0
- nwp500_python-9.10.0/tests/test_sim_bottom_loss.py +665 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_calibrate.py +20 -12
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_cold_supply.py +41 -64
- nwp500_python-9.10.0/tests/test_sim_documents.py +127 -0
- nwp500_python-9.10.0/tests/test_sim_fit_book.py +695 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_home_assistant.py +179 -121
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_live.py +47 -0
- nwp500_python-9.10.0/tests/test_sim_overlay.py +150 -0
- nwp500_python-9.8.0/src/nwp500/sim/meter.py +0 -130
- nwp500_python-9.8.0/tests/test_sim_meter.py +0 -163
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.coveragerc +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.github/copilot-instructions.md +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.github/workflows/ci.yml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.github/workflows/release.yml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.gitignore +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.pre-commit-config.yaml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/.readthedocs.yml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/AUTHORS.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/CONTRIBUTING.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/LICENSE.txt +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/Makefile +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/README.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/RELEASE.md +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/Makefile +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/_static/.gitignore +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/conf.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/architecture.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/draws.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/heating-elements.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/index.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/recovery-speed.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/standby-loss.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/tank-energy.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/tou-recovery-cap.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/unknown-values.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/explanation/what-starts-a-recovery.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/authenticate.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/auto-recovery.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/bluetooth.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/diagnose-mqtt.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/home-assistant.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/index.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/maintenance.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/manage-units.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/monitor-status.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/optimize-tou.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/queue-commands.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/schedule-operation.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/how-to/track-energy.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/index.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/openapi.yaml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/project/authors.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/project/changelog.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/project/contributing.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/project/history.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/project/license.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/configuration.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/enumerations.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/index.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/installation.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/data_conversions.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/device_features.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/device_status.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/error_codes.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/quick_reference.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/protocol/rest_api.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/api_client.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/auth_client.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/cli.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/events.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/exceptions.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/models.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/reference/python_api/mqtt_client.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/requirements.txt +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/docs/tutorials/getting-started.rst +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/.ruff.toml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/README.md +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/power_control.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/mask.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/simulator/lower-setpoint.json +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/simulator/plan.json +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/simulator/scenario.json +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/pyproject.toml +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/README.md +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/bump_version.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/extract_changelog.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/format.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/lint.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/setup-dev.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/scripts/validate_version.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/setup.cfg +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/setup.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/_base.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/api_client.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/auth.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/ble/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/ble/__main__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/ble/client.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/ble/mqtt_adapter.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/ble/protocol.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/ble_backend.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/calibration.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/handlers.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/presentation.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/recording.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/config.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/converters.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/encoding.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/enums.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/events.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/factory.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/account.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/device.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/diagnostics.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/energy.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/feature.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/firmware.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/maintenance.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/mqtt_models.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/schedule.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/status.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/models/tou.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/_control_commands.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/_device_subscriptions.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/client.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/command_queue.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/connection.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/control.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/periodic.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/subscriptions.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/types.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt/utils.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/openei.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/py.typed +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/reservations.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/attribution.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/book.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/circulation.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/controller.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/defaults.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/delivery.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/draw_forecast.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/engine.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/extensions.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/heat_pump_recovery.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/heating_trajectory.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/inputs.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/location_air.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/observed.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/physics/__init__.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/physics/draw.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/physics/element.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/physics/refrigerant.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/profile.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/ready.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/record.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/result.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/schedules.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/tou_triggers.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/sim/units.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/status_history.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/temperature.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/topic_builder.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/tou_schedule.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500/utils.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/requires.txt +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/_sim_site.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/conftest.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/fixtures/sim/engine_golden.json +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/fixtures/sim/heater_params.json +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/fixtures/sim/heater_params_fitted.json +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_api_endpoints_201.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_api_helpers.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_app_queries.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_auth.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_auth_session_lifecycle.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_auth_session_refresh.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_ble_mqtt_adapter.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_ble_protocol.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_bug_fixes.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_canonical_schedule.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_cli_basic.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_cli_calibrate.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_cli_commands.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_cli_tristate_presentation.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_command_decorators.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_command_queue.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_device_rest_models.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_events.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_exceptions.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_model_converters.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_models.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_clean_session_resume.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_control_fail.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_events.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_hypothesis.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_last_will.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_reconnection.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_reconnection_storm.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_mqtt_reliability.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_multi_device.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_navilink_201_enums.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_openei.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_protocol_correctness.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_public_api.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_reservation_validation.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_reservations.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_adapters.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_api.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_book.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_calibration.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_change_history.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_circulation.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_codec.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_controller.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_defaults.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_draw_forecast.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_draw_loss.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_element.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_element_fit.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_forecast.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_golden.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_hot_water.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_import_contract.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_location_air.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_no_clock_no_state.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_observed.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_profile.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_readiness.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_readiness_fit.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_ready.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_ready_api.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_replay.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_schedules.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_sim_tank_physics.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_status_history.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_threading_model.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_token_storage.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_tou_api.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_tou_schedule.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_unit_switching.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_unit_system_process_wide.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_utility_modules.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tests/test_utils.py +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/tox.ini +0 -0
- {nwp500_python-9.8.0 → nwp500_python-9.10.0}/uv.lock +0 -0
|
@@ -2,6 +2,153 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 9.10.0 (2026-10-22)
|
|
6
|
+
===========================
|
|
7
|
+
|
|
8
|
+
Added
|
|
9
|
+
-----
|
|
10
|
+
- **The heat book from a heater's values' changes** (#191).
|
|
11
|
+
``sim_adapters.book_from_changes`` books the tank's heat from
|
|
12
|
+
``ChangeRows`` and the instants the heater was heard from (a
|
|
13
|
+
``HeaterHistory``'s ``rows`` and ``observed``, as
|
|
14
|
+
``calibrate_changes`` takes them), with the same steps, anchor search
|
|
15
|
+
and silent-meter refusal as ``book_from_history``. Like the rows, its
|
|
16
|
+
flows are gal/min and its temperatures °F, whatever the preferred
|
|
17
|
+
units. A span the heater
|
|
18
|
+
wasn't heard from (a silence longer than ``max_gap`` of the heard
|
|
19
|
+
instants, or an outage) breaks the book: it is read only from after
|
|
20
|
+
the last one, and is None while the heater isn't heard at ``at``.
|
|
21
|
+
``book_steps_from_changes`` gives the steps, and ``silences`` the
|
|
22
|
+
unheard spans.
|
|
23
|
+
- ``sim_adapters.cold_supply_f``: the mains' plateau with the probe in
|
|
24
|
+
°F, reading no unit preference. The live source's inlet reads through it,
|
|
25
|
+
so a preference changed while it runs changes nothing.
|
|
26
|
+
- ``sim_adapters.cold_supply(..., window=)``: how far back a long draw
|
|
27
|
+
counts (72 hours by default).
|
|
28
|
+
- **The heat book's terms and the circulation loop's loss, fitted
|
|
29
|
+
together** (#196). ``sim_calibrate.fit_book`` fits the book's
|
|
30
|
+
compressor heat rate and slope with the air, its standby UA and a
|
|
31
|
+
circulation loop's loss per gallon from a heater's values' changes,
|
|
32
|
+
the household's draws, and the pump's runs with the loop's flow, each
|
|
33
|
+
given apart from the other. The loss is fitted with the book's terms
|
|
34
|
+
on the pairs of quiet full tanks with a run between. It needs 10
|
|
35
|
+
pairs (10 with a run for the loss), and keeps the base unless the
|
|
36
|
+
refit books the held-out latest fifth no worse, and unless a term
|
|
37
|
+
reaches its bound (``BookTermsFit``). ``sim.calibration.fit_terms_l1``
|
|
38
|
+
fits the terms exactly on ``RecordedBook.pair_terms``, within
|
|
39
|
+
``BOOK_TERM_BOUNDS``. ``sim_adapters.run_readings`` gives the book a
|
|
40
|
+
loop's flow readings within its runs, each run's first held from its
|
|
41
|
+
start, as the fit takes them, and ``sim_adapters.run_flow`` gives runs
|
|
42
|
+
at a flow.
|
|
43
|
+
- **A scenario document over the live scenario** (#201).
|
|
44
|
+
``sim_live.overlay`` lays a scenario document over the heater's
|
|
45
|
+
composed scenario, as ``nwp-cli simulate --live`` does, and the CLI
|
|
46
|
+
now calls it: objects merge key by key (``sim.codec.merge``) and any
|
|
47
|
+
other value replaces. The start, horizon and zone take defaults, and
|
|
48
|
+
the parameters come from the caller, then the document, then the
|
|
49
|
+
heater, then the defaults (``Built``).
|
|
50
|
+
- **The simulator's answers as documents** (#205).
|
|
51
|
+
``sim.codec.to_document`` writes ``Readiness``, ``Recovery``,
|
|
52
|
+
``RecoveryResult``, ``Forecast``, ``ActiveCycle``,
|
|
53
|
+
``ElementModeResult``, ``Deliverable``, ``Trace`` and
|
|
54
|
+
``HeaterProfile`` as JSON documents with ``schema_version`` and a
|
|
55
|
+
``kind`` (``DOCUMENT_KINDS``), as ``result_to_document`` does for a
|
|
56
|
+
walk's result.
|
|
57
|
+
- **The live parts from values read** (#207).
|
|
58
|
+
``sim_live.heater_reading(status, feature)`` and
|
|
59
|
+
``live_schedules(status, reservations, tou)``: what
|
|
60
|
+
``LiveHeaterSource`` and ``LiveScheduleSource`` read, from a status,
|
|
61
|
+
a feature info, the reservations and the TOU periods read any other
|
|
62
|
+
way. Both sources now build their answers through them.
|
|
63
|
+
|
|
64
|
+
Fixed
|
|
65
|
+
-----
|
|
66
|
+
- The live source's inlet looked back 72 hours of wall time, an hour
|
|
67
|
+
more or less than 72 hours across a clock change. It now looks back
|
|
68
|
+
72 elapsed hours.
|
|
69
|
+
- ``book_from_history`` counted a missing meter reading (None or NaN)
|
|
70
|
+
as the meter reporting, so a gap filled with them passed the
|
|
71
|
+
``METER_MAX_GAP`` check and was booked as no flow. Missing readings
|
|
72
|
+
now count as silence.
|
|
73
|
+
|
|
74
|
+
Changed
|
|
75
|
+
-------
|
|
76
|
+
- ``parse_state`` and the simulate how-to say that over Home
|
|
77
|
+
Assistant's REST API an entity's ``last_reported`` comes from a copy
|
|
78
|
+
that a report of the same value doesn't renew, so a sensor holding
|
|
79
|
+
one value for over an hour can read as stale.
|
|
80
|
+
- ``sim.calibration.RecordedBook`` prices a draw against each step's
|
|
81
|
+
inlet, as the heat book does (``book.step_heat_btu``), where it priced
|
|
82
|
+
it against the book's reference. Without an inlet the steps take
|
|
83
|
+
``inlet_default_f``, the reference by default, and nothing changes.
|
|
84
|
+
- ``max_gap``, ``GAP_SAMPLES`` and ``MIN_GAP`` are defined in
|
|
85
|
+
``sim_adapters`` and documented there. ``sim_calibrate`` still exports
|
|
86
|
+
them.
|
|
87
|
+
- **The household's draws come from a demand entity** (#203). The
|
|
88
|
+
simulator no longer works out when hot water was drawn from a water
|
|
89
|
+
meter. ``sim_home_assistant.Entities.demand`` (``demand`` in a
|
|
90
|
+
sources file) is a binary sensor on while the household draws hot
|
|
91
|
+
water, with the circulation's water not among its draws; the live
|
|
92
|
+
inlet's long draws and ``record_inputs``' ``drawing`` come from it
|
|
93
|
+
(``demand_spans``). ``sim.cold_supply.plateau_t_cold``,
|
|
94
|
+
``sim_adapters.cold_supply`` and ``cold_supply_f`` take the draws as
|
|
95
|
+
(start, end) spans (``draws=``) in place of the meter's flow and draw
|
|
96
|
+
instants; ``ColdSupplyParams`` loses ``tick_s``, ``tick_gap_s`` and
|
|
97
|
+
``draw_gpm``.
|
|
98
|
+
|
|
99
|
+
Removed
|
|
100
|
+
-------
|
|
101
|
+
- ``Entities.meter_volume`` and ``meter_flow`` (and their sources-file
|
|
102
|
+
keys), ``sim.meter``, ``sim_home_assistant.draw_spans``,
|
|
103
|
+
``volume_spans`` and ``reports_after``: the simulator's own reading
|
|
104
|
+
of draws from a water meter.
|
|
105
|
+
|
|
106
|
+
Version 9.9.0 (2026-10-22)
|
|
107
|
+
==========================
|
|
108
|
+
|
|
109
|
+
Added
|
|
110
|
+
-----
|
|
111
|
+
- **A bottom loss in the standby model** (#187). Over one installation's 407
|
|
112
|
+
quiet windows across ten months, a window that opens stratified loses
|
|
113
|
+
heat at the bottom: its gap widens, its lower probe falls about 40 %
|
|
114
|
+
faster than a mixed tank's, and its upper probe hardly moves. One UA
|
|
115
|
+
on the whole tank could not represent it, and the fitted UA was a
|
|
116
|
+
blend that cooled a mixed tank too fast, as after a cold season's full
|
|
117
|
+
recoveries. ``LossParams`` gains ``bottom_loss_btu_hr`` and
|
|
118
|
+
``bottom_gate_f``: heat leaving the lower zone, of which the lower
|
|
119
|
+
probe sees ``1 - exp(-gap / gate)`` (``sim.physics.tank.
|
|
120
|
+
bottom_loss_reach``). The standby walk (``decay.project_decay``) and
|
|
121
|
+
the recovery integrator charge it to the lower zone. A bottom loss of
|
|
122
|
+
0.0, the default and what stored parameter sets carry, reproduces the
|
|
123
|
+
model before it.
|
|
124
|
+
- ``sim.calibration.fit_loss`` fits the UA law and the bottom loss
|
|
125
|
+
together, through the standby walk, on the quiet windows opening with
|
|
126
|
+
a gap of 10 °F or less (``loss_windows``); ``fit_standby`` uses it.
|
|
127
|
+
On the installation above, the lower probe's median error over ten
|
|
128
|
+
hours fell from 1.25 to 0.77 °F from March to October and from 1.67
|
|
129
|
+
to 1.05 °F over the winter (1.15 °F trained without the winter).
|
|
130
|
+
``StandbyWindow`` carries each window's probes and air, and
|
|
131
|
+
``standby_ends`` and ``standby_error`` walk a loss over windows.
|
|
132
|
+
- The report has the bottom loss and its gate, and ``keep_better``
|
|
133
|
+
judges them with the standby loss, on the probes' error in each
|
|
134
|
+
held-out window's fall, °F/h.
|
|
135
|
+
|
|
136
|
+
Changed
|
|
137
|
+
-------
|
|
138
|
+
- The standby loss is fitted only on quiet windows opening with a gap of
|
|
139
|
+
10 °F or less: a wider gap is a draw's front closing, which the gap's
|
|
140
|
+
time constant describes.
|
|
141
|
+
- ``keep_better`` scores the standby group on the probes' error in each
|
|
142
|
+
held-out window's fall, walked as the simulation walks it, in place of
|
|
143
|
+
the error in log UA, so a loss with a bottom term and one without are
|
|
144
|
+
scored alike.
|
|
145
|
+
- The zone gap time constant is fitted with the bottom loss opening the
|
|
146
|
+
gaps it relaxes (``fit_gap_tau``'s ``loss``, ``gap_paths``), in turn
|
|
147
|
+
with the loss until it settles: a gap the bottom loss widens read as a
|
|
148
|
+
pure relaxation pushed the constant long. ``keep_better`` judges the
|
|
149
|
+
constant before the standby loss and fits and scores the loss on the
|
|
150
|
+
constant kept.
|
|
151
|
+
|
|
5
152
|
Version 9.8.0 (2026-10-21)
|
|
6
153
|
==========================
|
|
7
154
|
|
|
@@ -78,7 +78,11 @@ zone (about half the tank's mass). Once the gap between the zones
|
|
|
78
78
|
closes, the tank heats as one body. In standby the gap relaxes on a
|
|
79
79
|
fitted time constant. Standby loss follows a jacket law,
|
|
80
80
|
``UA(dT) = ua_ref * (max(dT, 5) / 50) ^ n``, with the ambient as the
|
|
81
|
-
other side.
|
|
81
|
+
other side, on the whole tank. A bottom loss ``Q_b`` leaves the lower
|
|
82
|
+
zone alone: the lower probe sees the share ``1 - exp(-gap / gate)`` of
|
|
83
|
+
it, so a stratified tank's lower zone falls faster and its gap opens,
|
|
84
|
+
while a mixed tank hides it below the probe. With ``Q_b = 0`` the loss
|
|
85
|
+
is the jacket law alone.
|
|
82
86
|
|
|
83
87
|
**The elements.** They follow the device's own rules
|
|
84
88
|
(:doc:`heating-elements`): per-mode entry thresholds, a dead time,
|
|
@@ -81,7 +81,8 @@ Idle, it reads the cabinet, about 6 °F warm. So give one of these:
|
|
|
81
81
|
- **Home Assistant:** ``--sources FILE``, the same sources file as
|
|
82
82
|
``simulate run`` takes (:doc:`simulate-heater`). The command reads the
|
|
83
83
|
air beside the heater and its humidity over the record. It also reads
|
|
84
|
-
the
|
|
84
|
+
the household's draws (the ``demand`` entity) and the circulation's
|
|
85
|
+
runs, which make the quiet
|
|
85
86
|
test exact: the heater's own "hot water in use" flag also fires on
|
|
86
87
|
circulation. The air is used only over the span its history covers,
|
|
87
88
|
and not while its sensor was ``unavailable``; the humidity takes the
|
|
@@ -91,7 +92,7 @@ Idle, it reads the cabinet, about 6 °F warm. So give one of these:
|
|
|
91
92
|
loss is fitted from those alone. For the whole record, raise the
|
|
92
93
|
recorder's ``purge_keep_days`` to cover it before you start
|
|
93
94
|
recording, or give the air another way (``--air``). Where the
|
|
94
|
-
|
|
95
|
+
draws' or the circulation's history doesn't cover the whole record,
|
|
95
96
|
the heater's own flag is used instead. ``--air``, ``--ambient`` and
|
|
96
97
|
``--humidity`` override the sources.
|
|
97
98
|
|
|
@@ -139,16 +140,23 @@ fitted or kept the base's value, and why:
|
|
|
139
140
|
|
|
140
141
|
* - Term
|
|
141
142
|
- Fitted from
|
|
142
|
-
* - Standby loss and its
|
|
143
|
+
* - Standby loss, its exponent, the bottom loss and its gate
|
|
143
144
|
- Quiet windows of 3 h or more, starting 45 minutes after any
|
|
144
|
-
heating or hot water in use
|
|
145
|
-
|
|
146
|
-
the
|
|
147
|
-
|
|
145
|
+
heating or hot water in use, that open with a gap of 10 °F or
|
|
146
|
+
less between the probes. A window counts only if its fit tracks
|
|
147
|
+
the probes within 2 °F. Each window's upper and lower probe are
|
|
148
|
+
walked from its start as the simulation walks them, and the
|
|
149
|
+
terms are fitted to where the probes ended. The exponent is
|
|
150
|
+
fitted only when the windows span two 20 °F bands of tank-to-air
|
|
151
|
+
difference; it is held at 0 (a constant loss) otherwise. The
|
|
152
|
+
bottom loss needs 10 windows that open with a gap of 1 °F or
|
|
153
|
+
more; with fewer it is 0, and the loss is the jacket law
|
|
154
|
+
alone.
|
|
148
155
|
* - Zone gap time constant
|
|
149
156
|
- How fast the upper and lower probes' gap closes over quiet
|
|
150
157
|
windows; it needs 20 windows that open with a gap of 2 °F or
|
|
151
|
-
more.
|
|
158
|
+
more. The bottom loss opens the gaps it relaxes, so the two are
|
|
159
|
+
fitted in turn until the constant settles.
|
|
152
160
|
* - Compressor constant, evaporator approach
|
|
153
161
|
- Heat-pump recoveries that reached their target, against the loss
|
|
154
162
|
just fitted. The error is the mean absolute error of the
|
|
@@ -214,7 +222,7 @@ Assistant integration, can calibrate the same way:
|
|
|
214
222
|
2. Read the air beside the heater and its humidity over the same span
|
|
215
223
|
with ``sim_home_assistant.record_inputs(client, entities, start,
|
|
216
224
|
end)``, and pass the result as ``record=``. It also brings the
|
|
217
|
-
|
|
225
|
+
household's draws and the circulation's runs. Any client with the same
|
|
218
226
|
``read_history`` as ``HomeAssistantClient`` works. The recorder's
|
|
219
227
|
retention limits the air, as above.
|
|
220
228
|
3. Call ``sim_calibrate.calibrate`` in a worker thread. It takes the
|
|
@@ -240,8 +248,12 @@ fit as ``params`` with ``keep_better=True``:
|
|
|
240
248
|
|
|
241
249
|
Each group of terms holds out its latest evidence from its refit:
|
|
242
250
|
|
|
243
|
-
- the standby loss and its
|
|
244
|
-
|
|
251
|
+
- the standby loss, its exponent, the bottom loss and its gate: its
|
|
252
|
+
quiet windows, scored on the probes' error in each window's fall,
|
|
253
|
+
°F/h;
|
|
254
|
+
- the zone gap time constant: its windows that open a gap, judged
|
|
255
|
+
before the standby loss, which is then fitted and scored on the
|
|
256
|
+
constant kept;
|
|
245
257
|
- the compressor constant and the evaporator approach: its reached
|
|
246
258
|
recoveries;
|
|
247
259
|
- the power scale: the metered compressor cycles it can use;
|
|
@@ -263,7 +275,7 @@ without ``keep_better``.
|
|
|
263
275
|
|
|
264
276
|
``sim.calibration.calibrate`` underneath takes the frame
|
|
265
277
|
``sim_adapters.history`` builds; ``sim_adapters.add_inputs`` adds the
|
|
266
|
-
air, the humidity,
|
|
278
|
+
air, the humidity, the household's draws and the circulation's runs to it.
|
|
267
279
|
From there you can also give the draws that price the heat each
|
|
268
280
|
cycle's draws took. The fits for the readiness terms, the heat book,
|
|
269
281
|
the draw response and the elements also run there: see "Improve
|
|
@@ -356,6 +368,101 @@ On a desktop computer, 60 days with a heartbeat every 10 seconds
|
|
|
356
368
|
Assistant's own queries, and 11 to fit, in about 180 MB.
|
|
357
369
|
|
|
358
370
|
|
|
371
|
+
Fit the heat book
|
|
372
|
+
=================
|
|
373
|
+
|
|
374
|
+
The heat book (``book_from_changes``) counts the heat the tank holds
|
|
375
|
+
from the compressor's heat rate and its slope with the air, the standby
|
|
376
|
+
UA, and, with a circulation pump, the loop's loss per gallon
|
|
377
|
+
(``CirculationParams.loss_btu_per_gal``). ``fit_book`` fits them
|
|
378
|
+
together from the heater's history, the household's draws, and the
|
|
379
|
+
pump's runs with the loop's flow. The draws and the loop's flow are
|
|
380
|
+
yours to give, apart from each other: the fit takes every gallon of the
|
|
381
|
+
draws as hot water drawn, and each run at the loop's flow.
|
|
382
|
+
|
|
383
|
+
.. code-block:: python
|
|
384
|
+
|
|
385
|
+
from dataclasses import replace
|
|
386
|
+
|
|
387
|
+
from nwp500 import sim_adapters as sa
|
|
388
|
+
|
|
389
|
+
async with ha.HomeAssistantClient(url, token) as client:
|
|
390
|
+
history = await ha.read_heater_history(client, entities, start, end)
|
|
391
|
+
record = await ha.record_inputs(client, ha.Entities(air=..., humidity=...),
|
|
392
|
+
start, end)
|
|
393
|
+
params = sim_calibrate.calibrate_changes(
|
|
394
|
+
history.rows, history.observed, record=record, notes=history.notes
|
|
395
|
+
).calibration.params
|
|
396
|
+
fit = sim_calibrate.fit_book(
|
|
397
|
+
history.rows, history.observed, params,
|
|
398
|
+
flow=draws, runs=runs, circulation_flow=loop_flow,
|
|
399
|
+
air=record.air or (),
|
|
400
|
+
)
|
|
401
|
+
params = replace(params, book=fit.book)
|
|
402
|
+
|
|
403
|
+
``draws`` and ``loop_flow`` are (instant, gal/min) readings and ``runs``
|
|
404
|
+
the pump's (start, end) spans; ``record_inputs``' air is °F, as the fit
|
|
405
|
+
takes it. ``fit.loss_btu_per_gal`` is the loop's loss for your
|
|
406
|
+
``CirculationParams`` (None without one).
|
|
407
|
+
|
|
408
|
+
The fit reads the pairs of quiet full tanks up to
|
|
409
|
+
``book.max_anchor_age_h`` apart, and fits each term for the least mean
|
|
410
|
+
absolute error between the book and the probes at the second of each
|
|
411
|
+
pair:
|
|
412
|
+
|
|
413
|
+
- **The book's terms:** the compressor's heat rate and slope, and the
|
|
414
|
+
standby UA, on every pair. A term the pairs can't tell apart from
|
|
415
|
+
the others (the slope, with the air never off one temperature) stays
|
|
416
|
+
at ``params.book``. The rest of the book (its draw line, the anchors'
|
|
417
|
+
age, the valve's line) is ``params``'.
|
|
418
|
+
- **The loop's loss per gallon:** on 10 pairs with a run between,
|
|
419
|
+
with the book's terms. With fewer, the loss is
|
|
420
|
+
``params.circulation``'s, and without one, the pairs with a run are
|
|
421
|
+
left out. A pair with a run that has no flow reading is left out.
|
|
422
|
+
|
|
423
|
+
The fit needs 10 pairs. It holds out the latest fifth and replaces the
|
|
424
|
+
base only when it books them no worse. A term on its bound
|
|
425
|
+
(``sim.calibration.BOOK_TERM_BOUNDS``) is no fit: the book misses
|
|
426
|
+
something between those anchors that the term is standing in for. The
|
|
427
|
+
loop's loss on its bound, or one the pairs can't tell from the book's
|
|
428
|
+
terms (runs that follow the recoveries), leaves the loss at the base's;
|
|
429
|
+
any other term on its bound keeps the base. ``fit.kept`` says which it
|
|
430
|
+
kept and ``fit.why`` why. A span the heater wasn't heard from, a probe
|
|
431
|
+
read nothing, or the draws were silent over an hour, breaks the book.
|
|
432
|
+
|
|
433
|
+
A pair with a run needs a quiet full tank on each side of it, so they
|
|
434
|
+
come slowly. With a pump running about once a day, one installation had
|
|
435
|
+
5 in 14 days and 19 in 42: fit on four weeks or more, and raise Home
|
|
436
|
+
Assistant's ``purge_keep_days`` to keep them.
|
|
437
|
+
|
|
438
|
+
Book with the same inputs. Runs are priced with the loop's loss, in a
|
|
439
|
+
``CirculationParams`` of your installation's with ``meter_share`` 0,
|
|
440
|
+
since the draws hold none of the loop's water; without a loss, they
|
|
441
|
+
can't be booked. ``sa.run_readings`` gives the book each run's flow as
|
|
442
|
+
the fit takes it: its own readings, the first from its start, and none
|
|
443
|
+
read outside the runs.
|
|
444
|
+
|
|
445
|
+
.. code-block:: python
|
|
446
|
+
|
|
447
|
+
from nwp500.sim.params import CirculationParams
|
|
448
|
+
|
|
449
|
+
book = None
|
|
450
|
+
loss = fit.loss_btu_per_gal
|
|
451
|
+
if loss is not None or not runs:
|
|
452
|
+
loop = None
|
|
453
|
+
if loss is not None:
|
|
454
|
+
loop = CirculationParams(
|
|
455
|
+
displaced_gal=..., flow_gpm=..., loss_btu_per_gal=loss,
|
|
456
|
+
meter_share=0.0,
|
|
457
|
+
)
|
|
458
|
+
book = sa.book_from_changes(
|
|
459
|
+
history.rows, history.observed, replace(params, circulation=loop),
|
|
460
|
+
at=end, flow=draws, circulation=runs,
|
|
461
|
+
circulation_flow=sa.run_readings(runs, loop_flow),
|
|
462
|
+
air=record.air or (),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
359
466
|
Limitations
|
|
360
467
|
===========
|
|
361
468
|
|
|
@@ -138,8 +138,7 @@ pass it with ``--sources`` (or set ``NWP500_SOURCES``):
|
|
|
138
138
|
air = "sensor.beside_the_heater_temperature"
|
|
139
139
|
humidity = "sensor.beside_the_heater_humidity"
|
|
140
140
|
inlet = "sensor.cold_supply_temperature"
|
|
141
|
-
|
|
142
|
-
meter_flow = "sensor.water_flow_rate"
|
|
141
|
+
demand = "binary_sensor.hot_water_demand" # on while hot water is drawn
|
|
143
142
|
circulation_speed = "sensor.circulation_pump_speed"
|
|
144
143
|
weather = "weather.forecast_home"
|
|
145
144
|
|
|
@@ -167,9 +166,17 @@ give:
|
|
|
167
166
|
- **the humidity:** a reading at most an hour old;
|
|
168
167
|
- **the inlet:** the plateau of the long draws (11 minutes or more,
|
|
169
168
|
circulation off) in the last 72 hours, from the inlet probe and the
|
|
170
|
-
|
|
169
|
+
``demand`` entity: a binary sensor that is on while the household
|
|
170
|
+
draws hot water, with the circulation's water not among its draws. A
|
|
171
171
|
circulation pump with no speed reading leaves no estimate.
|
|
172
172
|
|
|
173
|
+
A reading's age is from when Home Assistant last heard the sensor
|
|
174
|
+
report (``last_reported``). Over the REST API, Home Assistant serves
|
|
175
|
+
that from a copy of the state it keeps until the state changes, and a
|
|
176
|
+
report of the same value doesn't renew the copy. So a sensor that has
|
|
177
|
+
held one value for over an hour can read as over an hour old, and its part
|
|
178
|
+
keeps its default, even while it still reports.
|
|
179
|
+
|
|
173
180
|
Units come from each entity's ``unit_of_measurement``: the pump's speed
|
|
174
181
|
in rpm or Hz. A part that can't be read, or is stale, keeps its
|
|
175
182
|
default, and a warning on stderr says why. ``--ambient``, ``--inlet``
|
|
@@ -217,6 +224,35 @@ source when TOU is on and its periods cannot be read. A missing
|
|
|
217
224
|
feature info leaves ``composed.params`` None (the tank size is
|
|
218
225
|
unknown). Change a part with ``composed.scenario.model_copy(update=...)``.
|
|
219
226
|
|
|
227
|
+
With the status, the feature info, the reservations and the TOU
|
|
228
|
+
periods already read some other way, build the same parts from them:
|
|
229
|
+
``sim_live.heater_reading(status, feature)`` is what
|
|
230
|
+
``LiveHeaterSource`` reads, and ``sim_live.live_schedules(status,
|
|
231
|
+
reservations, tou)`` what ``LiveScheduleSource`` reads. Pass either to
|
|
232
|
+
``compose`` as ``sim_live.Given(...)``. ``tou`` is the ``TOUInfo`` the
|
|
233
|
+
TOU read gives, or the ``TOUPeriod`` list itself; the TOU switch is the
|
|
234
|
+
status's. ``live_schedules`` raises ``LiveReadError`` when the status
|
|
235
|
+
has TOU on and ``tou`` is None.
|
|
236
|
+
|
|
237
|
+
To lay a scenario document over the heater as it is now, as
|
|
238
|
+
``nwp-cli simulate --scenario ... --live`` does, use ``overlay``:
|
|
239
|
+
|
|
240
|
+
.. code-block:: python
|
|
241
|
+
|
|
242
|
+
built = sim_live.overlay(composed, doc, now=datetime.now(UTC))
|
|
243
|
+
result = sim.simulate(built.scenario, built.params)
|
|
244
|
+
|
|
245
|
+
Objects merge key by key, and any other value, a list included,
|
|
246
|
+
replaces the one under it, so ``{"schema_version": 1, "scenario":
|
|
247
|
+
{"settings": {"setpoint_f": 125}}}`` changes the setpoint and keeps
|
|
248
|
+
the rest of the heater's settings. ``composed`` or ``doc`` may be None.
|
|
249
|
+
Where neither gives one, the start is ``now``, the horizon 24 hours,
|
|
250
|
+
and the zone ``tz=``. The parameters are ``params=``, then the
|
|
251
|
+
document's, then the heater's, then the 65 gal defaults (with a note in
|
|
252
|
+
``built.notes``). ``replace=`` takes parts that stand in whole for the
|
|
253
|
+
scenario's, and ``over=`` parts laid on after them. A document that
|
|
254
|
+
doesn't make a scenario raises ``ValueError``.
|
|
255
|
+
|
|
220
256
|
To have the models themselves, read them directly. Each MQTT read is a
|
|
221
257
|
request answered on a subscription:
|
|
222
258
|
|
|
@@ -824,6 +860,30 @@ heater's statuses, book the tank's heat from the record and pass it as
|
|
|
824
860
|
)
|
|
825
861
|
readiness = sim.time_to_ready(scenario, params, book=book)
|
|
826
862
|
|
|
863
|
+
With the heater's values recorded as changes instead (Home Assistant's
|
|
864
|
+
entity history, which ``read_heater_history`` reads), book from those,
|
|
865
|
+
with the instants the heater was heard from:
|
|
866
|
+
|
|
867
|
+
.. code-block:: python
|
|
868
|
+
|
|
869
|
+
from nwp500 import sim_home_assistant as ha
|
|
870
|
+
|
|
871
|
+
history = await ha.read_heater_history(client, entities, start, now)
|
|
872
|
+
book = sa.book_from_changes(
|
|
873
|
+
history.rows,
|
|
874
|
+
history.observed,
|
|
875
|
+
params,
|
|
876
|
+
at=now,
|
|
877
|
+
flow=draws_gpm, # gal/min, whatever the preferred units
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
Like the history's rows, its ``flow`` and ``circulation_flow`` are
|
|
881
|
+
gal/min and its ``inlet`` and ``air`` °F, whatever the preferred units.
|
|
882
|
+
A span the heater wasn't heard from (an outage, or a silence much
|
|
883
|
+
longer than its usual reporting) breaks the book: it is walked only
|
|
884
|
+
from an anchor after the last one, and is None while the heater isn't
|
|
885
|
+
heard now.
|
|
886
|
+
|
|
827
887
|
Without a meter, start one only when you know the tank is full and
|
|
828
888
|
quiet (``sa.full_tank_book``).
|
|
829
889
|
|
|
@@ -835,12 +895,18 @@ the air around the pipe:
|
|
|
835
895
|
|
|
836
896
|
from nwp500.sim.cold_supply import on_the_mains
|
|
837
897
|
|
|
838
|
-
cold = sa.cold_supply(inlet_probe, at=now,
|
|
898
|
+
cold = sa.cold_supply(inlet_probe, at=now, draws=draws,
|
|
839
899
|
circulation=runs)
|
|
840
900
|
book, measured = on_the_mains(book, cold)
|
|
841
901
|
readiness = sim.time_to_ready(scenario, params, book=book,
|
|
842
902
|
inlet_measured=measured)
|
|
843
903
|
|
|
904
|
+
``draws`` are the (start, end) spans the household drew hot water,
|
|
905
|
+
without the circulation's water. ``sa.cold_supply`` takes the probe in
|
|
906
|
+
the preferred unit. ``sa.cold_supply_f`` takes it in °F and reads no
|
|
907
|
+
unit preference, for a caller whose preference may change while it
|
|
908
|
+
runs.
|
|
909
|
+
|
|
844
910
|
The mains is the median plateau of the long draws (11 minutes or more,
|
|
845
911
|
with circulation off) over the last 72 hours, read from minute 9 of each
|
|
846
912
|
draw, once the probe has passed the stagnant pipe. There is none
|
|
@@ -1162,7 +1228,21 @@ the ones you want to look at again, or attach one to a bug report:
|
|
|
1162
1228
|
scenario, params = codec.scenario_from_document(f.read())
|
|
1163
1229
|
|
|
1164
1230
|
``codec.result_to_document(result)`` writes a result the same
|
|
1165
|
-
way.
|
|
1231
|
+
way. The other answers (``time_to_ready``, ``next_recovery``,
|
|
1232
|
+
``recovery``, ``forecast``, ``active_cycle``, ``element_mode``,
|
|
1233
|
+
``readiness``, ``standby`` and ``heater_profile``) are documents through
|
|
1234
|
+
``codec.to_document``:
|
|
1235
|
+
|
|
1236
|
+
.. code-block:: python
|
|
1237
|
+
|
|
1238
|
+
doc = codec.to_document(sim.time_to_ready(scenario, params))
|
|
1239
|
+
doc["kind"] # "readiness"
|
|
1240
|
+
doc["deliverable_gal"] # the answer's fields, at the top level
|
|
1241
|
+
|
|
1242
|
+
Each document has ``schema_version`` and ``kind``, the answer's type
|
|
1243
|
+
in snake case (``codec.DOCUMENT_KINDS``). Instants are ISO 8601 strings,
|
|
1244
|
+
nested answers are objects, and a number that isn't finite is null, so
|
|
1245
|
+
the document is strict JSON.
|
|
1166
1246
|
|
|
1167
1247
|
|
|
1168
1248
|
See also
|
|
@@ -154,6 +154,7 @@ Hot water and the heat book
|
|
|
154
154
|
clean_draws, CleanDraw, crossing,
|
|
155
155
|
demonstrated, frontier, capacity, held_out, fit_evidence,
|
|
156
156
|
EvidenceFit, cold_lower_cap, RecordedBook, fit_book_terms, BookFit,
|
|
157
|
+
fit_terms_l1, BOOK_TERMS, BOOK_TERM_BOUNDS,
|
|
157
158
|
book_error_bands, book_error_btu, empty_point,
|
|
158
159
|
zone_empty_point, withheld_btu, book_walk, long_draws,
|
|
159
160
|
front_frame, fit_front, FrontFit, settle_tails, fit_front_settle,
|
|
@@ -169,6 +170,8 @@ Hot water and the heat book
|
|
|
169
170
|
metered_withdrawn, metered_lead,
|
|
170
171
|
path_to_end, with_power, snapshots, recoveries, quiet_windows,
|
|
171
172
|
busy_intervals, fit_window, fit_ua_law, fit_gap_tau, with_standby,
|
|
173
|
+
fit_loss, LossFit, loss_windows, standby_ends, standby_error,
|
|
174
|
+
walk_tau, gap_paths, gap_error,
|
|
172
175
|
with_compressor, UA_SEARCH_BOUNDS, UA_EXPONENT_BOUNDS, DRAW_LEAD
|
|
173
176
|
|
|
174
177
|
.. automodule:: nwp500.sim.profile
|
|
@@ -182,9 +185,6 @@ Hot water and the heat book
|
|
|
182
185
|
.. automodule:: nwp500.sim.cold_supply
|
|
183
186
|
:members: plateau_t_cold, ColdSupply, ColdSupplyParams, on_the_mains
|
|
184
187
|
|
|
185
|
-
.. automodule:: nwp500.sim.meter
|
|
186
|
-
:members: flow_gpm
|
|
187
|
-
|
|
188
188
|
.. automodule:: nwp500.sim.record
|
|
189
189
|
:members: Record, RecordedWrite, Recorded, RecordedStarts, corroborator
|
|
190
190
|
|
|
@@ -250,7 +250,7 @@ The pieces the entry points are built from.
|
|
|
250
250
|
vapor_enthalpy, dew_point_f
|
|
251
251
|
|
|
252
252
|
.. automodule:: nwp500.sim.physics.tank
|
|
253
|
-
:members: ua_at
|
|
253
|
+
:members: ua_at, bottom_loss_reach, bottom_loss_drop_f
|
|
254
254
|
|
|
255
255
|
.. automodule:: nwp500.sim.physics.element
|
|
256
256
|
:members: mode_recovery, ModeRecovery, mode_plan, ModePlan,
|
|
@@ -289,7 +289,9 @@ Live sources
|
|
|
289
289
|
:members: compose, Composed, Source, Given, HeaterReading,
|
|
290
290
|
LiveHeaterSource, LiveScheduleSource, LiveReadError, read_status,
|
|
291
291
|
read_feature, read_reservations, HeaterSource, ScheduleSource,
|
|
292
|
-
EnvironmentSource, LoadSource, DEFAULT_TIMEOUT
|
|
292
|
+
EnvironmentSource, LoadSource, DEFAULT_TIMEOUT, overlay, Built,
|
|
293
|
+
DEFAULT_HORIZON, DEFAULT_PARAMS_NOTE, zone_name, heater_reading,
|
|
294
|
+
live_schedules
|
|
293
295
|
|
|
294
296
|
|
|
295
297
|
Recorded history
|
|
@@ -299,9 +301,10 @@ Recorded history
|
|
|
299
301
|
:members:
|
|
300
302
|
|
|
301
303
|
.. automodule:: nwp500.sim_calibrate
|
|
302
|
-
:members: calibrate, calibrate_changes, Calibrated, report, Term,
|
|
304
|
+
:members: calibrate, calibrate_changes, Calibrated, report, Term,
|
|
305
|
+
fit_book, BookTermsFit, MIN_BOOK_PAIRS, MIN_RUN_PAIRS,
|
|
303
306
|
score, Judged, HeldOut, GROUPS, air_unread_note,
|
|
304
|
-
CalibrationInputError, DEFAULT_HUMIDITY_PCT,
|
|
307
|
+
CalibrationInputError, DEFAULT_HUMIDITY_PCT,
|
|
305
308
|
DEFAULT_BASE_NOTE, NO_AIR_NOTE
|
|
306
309
|
|
|
307
310
|
|
|
@@ -310,9 +313,9 @@ Home Assistant sources
|
|
|
310
313
|
|
|
311
314
|
.. automodule:: nwp500.sim_home_assistant
|
|
312
315
|
:members: HomeAssistantEnvironment, HomeAssistantClient, record_inputs,
|
|
313
|
-
RecordInputs, Sources, Entities, sources_from, State, parse_state,
|
|
316
|
+
RecordInputs, INLET_LOOKBACK, Sources, Entities, sources_from, State, parse_state,
|
|
314
317
|
parse_history, parse_outages, parse_states, History, history_for,
|
|
315
318
|
parse_forecast, read_heater_history, HeaterEntities, HeaterHistory,
|
|
316
319
|
HISTORY_WINDOW, to_w, to_wh,
|
|
317
|
-
pump_runs,
|
|
320
|
+
pump_runs, demand_spans, busy, covers_start, percent,
|
|
318
321
|
fresh, to_f, to_gpm, to_gal, to_rpm, HomeAssistantError
|
|
@@ -519,6 +519,13 @@ tank over the cold water of a recent draw. Start one only from what you know:
|
|
|
519
519
|
- You record the heater's statuses and meter the hot water: the
|
|
520
520
|
book is walked from the last time the record shows the tank
|
|
521
521
|
full and quiet after a recovery (below).
|
|
522
|
+
* - ``sim_adapters.book_from_changes(rows, observed, params, at=...,
|
|
523
|
+
flow=...)``
|
|
524
|
+
- The same, from the heater's values' changes (``ChangeRows``) and
|
|
525
|
+
the instants it was heard from, as Home Assistant records them,
|
|
526
|
+
with the flows in gal/min and the temperatures in °F.
|
|
527
|
+
Only an anchor after the last span the heater wasn't heard from
|
|
528
|
+
counts.
|
|
522
529
|
* - A ``Booked`` from your own record
|
|
523
530
|
- You keep a book of the tank's heat some other way.
|
|
524
531
|
|
|
@@ -540,7 +547,17 @@ median of the probe's readings over the last 3 hours
|
|
|
540
547
|
(``book.inlet_window_s``), the default without one. The meter must
|
|
541
548
|
report at rest too: a silence over an hour in the window is refused,
|
|
542
549
|
since it would read as a quiet full tank. A missing reading (None or
|
|
543
|
-
NaN) is skipped, and the value before it holds
|
|
550
|
+
NaN) is skipped, and the value before it holds; it doesn't count as the
|
|
551
|
+
meter reporting.
|
|
552
|
+
|
|
553
|
+
``book_from_changes`` grids the changes the same way, each value held
|
|
554
|
+
until its next change; without a compressor flag, the heat source says
|
|
555
|
+
when the compressor runs. A value that holds writes no change, so only
|
|
556
|
+
``observed`` says the heater was heard. A silence between those
|
|
557
|
+
instants longer than ``max_gap`` of them (4 times their usual spacing,
|
|
558
|
+
and at least 5 minutes), or one of ``rows.outages``, is a span nobody
|
|
559
|
+
saw. The book is read only from after the last such span, and is None
|
|
560
|
+
when the heater isn't heard at ``at``.
|
|
544
561
|
|
|
545
562
|
To build the steps yourself, pass them to ``book.book_steps``.
|
|
546
563
|
``BookStep.circulation_in_meter_gpm`` is
|
|
@@ -1276,8 +1276,8 @@ async def simulate_record(
|
|
|
1276
1276
|
"--sources",
|
|
1277
1277
|
type=click.Path(exists=True, dir_okay=False),
|
|
1278
1278
|
envvar="NWP500_SOURCES",
|
|
1279
|
-
help="A TOML file naming where the air, its humidity, the
|
|
1280
|
-
"the circulation are recorded (Home Assistant)",
|
|
1279
|
+
help="A TOML file naming where the air, its humidity, the hot water "
|
|
1280
|
+
"demand and the circulation are recorded (Home Assistant)",
|
|
1281
1281
|
)
|
|
1282
1282
|
@click.pass_context
|
|
1283
1283
|
def simulate_calibrate(
|