nwp500-python 9.11.0__tar.gz → 9.12.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.
Files changed (345) hide show
  1. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/CHANGELOG.rst +165 -0
  2. {nwp500_python-9.11.0/src/nwp500_python.egg-info → nwp500_python-9.12.0}/PKG-INFO +1 -1
  3. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/calibrate-simulator.rst +45 -23
  4. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/simulate-heater.rst +154 -18
  5. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/sim.rst +6 -3
  6. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/simulator-inputs.rst +44 -17
  7. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/calibration.py +2 -0
  8. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/simulate.py +1 -0
  9. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/api.py +50 -13
  10. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/book.py +38 -5
  11. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/calibration.py +140 -33
  12. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/circulation.py +96 -7
  13. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/defaults.py +26 -9
  14. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/delivery.py +30 -12
  15. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/inputs.py +3 -0
  16. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/params.py +17 -3
  17. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/ready.py +18 -7
  18. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_adapters.py +443 -51
  19. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_calibrate.py +250 -51
  20. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_home_assistant.py +964 -203
  21. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_live.py +30 -2
  22. {nwp500_python-9.11.0 → nwp500_python-9.12.0/src/nwp500_python.egg-info}/PKG-INFO +1 -1
  23. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/scm_version.json +2 -2
  24. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_adapters.py +8 -3
  25. nwp500_python-9.12.0/tests/test_sim_book_history.py +954 -0
  26. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_bottom_loss.py +11 -0
  27. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_calibrate.py +136 -0
  28. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_calibration.py +9 -0
  29. nwp500_python-9.12.0/tests/test_sim_change_history.py +1373 -0
  30. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_circulation.py +122 -6
  31. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_defaults.py +16 -4
  32. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_fit_book.py +139 -72
  33. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_home_assistant.py +436 -10
  34. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_hot_water.py +10 -2
  35. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_live.py +23 -0
  36. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_overlay.py +51 -0
  37. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_ready_api.py +165 -0
  38. nwp500_python-9.11.0/tests/test_sim_book_history.py +0 -435
  39. nwp500_python-9.11.0/tests/test_sim_change_history.py +0 -572
  40. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.coveragerc +0 -0
  41. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/RESOLVING_PR_COMMENTS.md +0 -0
  42. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/copilot-instructions.md +0 -0
  43. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/workflows/ci.yml +0 -0
  44. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/workflows/release.yml +0 -0
  45. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.gitignore +0 -0
  46. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.pre-commit-config.yaml +0 -0
  47. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.readthedocs.yml +0 -0
  48. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/AUTHORS.rst +0 -0
  49. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/CONTRIBUTING.rst +0 -0
  50. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/LICENSE.txt +0 -0
  51. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/Makefile +0 -0
  52. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/README.rst +0 -0
  53. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/RELEASE.md +0 -0
  54. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/Makefile +0 -0
  55. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/_static/.gitignore +0 -0
  56. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/conf.py +0 -0
  57. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/architecture.rst +0 -0
  58. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/draws.rst +0 -0
  59. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/heating-elements.rst +0 -0
  60. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/index.rst +0 -0
  61. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/recovery-speed.rst +0 -0
  62. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/simulator.rst +0 -0
  63. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/standby-loss.rst +0 -0
  64. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/tank-energy.rst +0 -0
  65. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/tou-recovery-cap.rst +0 -0
  66. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/unknown-values.rst +0 -0
  67. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/what-starts-a-recovery.rst +0 -0
  68. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/authenticate.rst +0 -0
  69. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/auto-recovery.rst +0 -0
  70. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/bluetooth.rst +0 -0
  71. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/diagnose-mqtt.rst +0 -0
  72. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/home-assistant.rst +0 -0
  73. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/index.rst +0 -0
  74. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/maintenance.rst +0 -0
  75. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/manage-units.rst +0 -0
  76. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/monitor-status.rst +0 -0
  77. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/optimize-tou.rst +0 -0
  78. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/queue-commands.rst +0 -0
  79. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/schedule-operation.rst +0 -0
  80. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/track-energy.rst +0 -0
  81. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/index.rst +0 -0
  82. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/openapi.yaml +0 -0
  83. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/authors.rst +0 -0
  84. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/changelog.rst +0 -0
  85. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/contributing.rst +0 -0
  86. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/history.rst +0 -0
  87. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/license.rst +0 -0
  88. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/configuration.rst +0 -0
  89. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/enumerations.rst +0 -0
  90. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/index.rst +0 -0
  91. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/installation.rst +0 -0
  92. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/data_conversions.rst +0 -0
  93. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/device_features.rst +0 -0
  94. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/device_status.rst +0 -0
  95. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/error_codes.rst +0 -0
  96. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/mqtt_protocol.rst +0 -0
  97. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/quick_reference.rst +0 -0
  98. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/rest_api.rst +0 -0
  99. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/api_client.rst +0 -0
  100. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/auth_client.rst +0 -0
  101. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/cli.rst +0 -0
  102. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/events.rst +0 -0
  103. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/exceptions.rst +0 -0
  104. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/models.rst +0 -0
  105. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/mqtt_client.rst +0 -0
  106. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/requirements.txt +0 -0
  107. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/tutorials/getting-started.rst +0 -0
  108. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/.ruff.toml +0 -0
  109. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/README.md +0 -0
  110. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/air_filter_reset.py +0 -0
  111. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/anti_legionella.py +0 -0
  112. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/auto_recovery.py +0 -0
  113. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/combined_callbacks.py +0 -0
  114. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/demand_response.py +0 -0
  115. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/device_capabilities.py +0 -0
  116. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/device_status_debug.py +0 -0
  117. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/energy_analytics.py +0 -0
  118. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/error_code_demo.py +0 -0
  119. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/firmware_payload_capture.py +0 -0
  120. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/mqtt_diagnostics.py +0 -0
  121. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/power_control.py +0 -0
  122. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/recirculation_control.py +0 -0
  123. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/reconnection_demo.py +0 -0
  124. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/reservation_schedule.py +0 -0
  125. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/simple_auto_recovery.py +0 -0
  126. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/token_restoration.py +0 -0
  127. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/tou_openei.py +0 -0
  128. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/tou_schedule.py +0 -0
  129. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/water_reservation.py +0 -0
  130. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/01_authentication.py +0 -0
  131. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/02_list_devices.py +0 -0
  132. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/03_get_status.py +0 -0
  133. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/04_set_temperature.py +0 -0
  134. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/advanced_auth_patterns.py +0 -0
  135. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/command_queue.py +0 -0
  136. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/device_status_callback.py +0 -0
  137. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/error_handling.py +0 -0
  138. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/event_driven_control.py +0 -0
  139. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/improved_auth.py +0 -0
  140. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/legacy_auth_constructor.py +0 -0
  141. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
  142. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/periodic_requests.py +0 -0
  143. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/set_mode.py +0 -0
  144. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/vacation_mode.py +0 -0
  145. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/mask.py +0 -0
  146. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/simulator/lower-setpoint.json +0 -0
  147. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/simulator/plan.json +0 -0
  148. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/simulator/scenario.json +0 -0
  149. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/periodic_device_info.py +0 -0
  150. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/simple_periodic_info.py +0 -0
  151. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_api_client.py +0 -0
  152. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_mqtt_connection.py +0 -0
  153. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_mqtt_messaging.py +0 -0
  154. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_periodic_minimal.py +0 -0
  155. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/pyproject.toml +0 -0
  156. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/README.md +0 -0
  157. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/bump_version.py +0 -0
  158. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/diagnose_mqtt_connection.py +0 -0
  159. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/extract_changelog.py +0 -0
  160. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/format.py +0 -0
  161. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/lint.py +0 -0
  162. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/setup-dev.py +0 -0
  163. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/validate_version.py +0 -0
  164. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/setup.cfg +0 -0
  165. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/setup.py +0 -0
  166. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/__init__.py +0 -0
  167. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/_base.py +0 -0
  168. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/api_client.py +0 -0
  169. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/auth.py +0 -0
  170. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/__init__.py +0 -0
  171. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/__main__.py +0 -0
  172. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/client.py +0 -0
  173. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/mqtt_adapter.py +0 -0
  174. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/protocol.py +0 -0
  175. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/__init__.py +0 -0
  176. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/__main__.py +0 -0
  177. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/ble_backend.py +0 -0
  178. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/handlers.py +0 -0
  179. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/monitoring.py +0 -0
  180. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/output_formatters.py +0 -0
  181. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/presentation.py +0 -0
  182. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/recording.py +0 -0
  183. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/rich_output.py +0 -0
  184. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/token_storage.py +0 -0
  185. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/command_decorators.py +0 -0
  186. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/config.py +0 -0
  187. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/converters.py +0 -0
  188. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/device_capabilities.py +0 -0
  189. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/device_info_cache.py +0 -0
  190. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/encoding.py +0 -0
  191. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/enums.py +0 -0
  192. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/events.py +0 -0
  193. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/exceptions.py +0 -0
  194. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/factory.py +0 -0
  195. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/field_factory.py +0 -0
  196. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/__init__.py +0 -0
  197. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/_converters.py +0 -0
  198. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/account.py +0 -0
  199. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/device.py +0 -0
  200. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/diagnostics.py +0 -0
  201. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/energy.py +0 -0
  202. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/feature.py +0 -0
  203. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/firmware.py +0 -0
  204. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/maintenance.py +0 -0
  205. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/mqtt_models.py +0 -0
  206. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/schedule.py +0 -0
  207. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/status.py +0 -0
  208. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/tou.py +0 -0
  209. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/__init__.py +0 -0
  210. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/_control_commands.py +0 -0
  211. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/_device_subscriptions.py +0 -0
  212. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/client.py +0 -0
  213. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/command_queue.py +0 -0
  214. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/connection.py +0 -0
  215. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/control.py +0 -0
  216. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/diagnostics.py +0 -0
  217. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/periodic.py +0 -0
  218. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/reconnection.py +0 -0
  219. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/state_tracker.py +0 -0
  220. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/subscriptions.py +0 -0
  221. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/types.py +0 -0
  222. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/utils.py +0 -0
  223. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt_events.py +0 -0
  224. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/openei.py +0 -0
  225. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/py.typed +0 -0
  226. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/reservations.py +0 -0
  227. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/__init__.py +0 -0
  228. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/attribution.py +0 -0
  229. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/codec.py +0 -0
  230. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/cold_supply.py +0 -0
  231. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/controller.py +0 -0
  232. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/decay.py +0 -0
  233. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/draw_forecast.py +0 -0
  234. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/engine.py +0 -0
  235. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/extensions.py +0 -0
  236. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/heat_pump_recovery.py +0 -0
  237. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/heating_trajectory.py +0 -0
  238. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/location_air.py +0 -0
  239. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/observed.py +0 -0
  240. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/__init__.py +0 -0
  241. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/compressor.py +0 -0
  242. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/draw.py +0 -0
  243. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/element.py +0 -0
  244. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/refrigerant.py +0 -0
  245. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/tank.py +0 -0
  246. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/profile.py +0 -0
  247. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/record.py +0 -0
  248. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/result.py +0 -0
  249. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/schedules.py +0 -0
  250. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/tank_physics.py +0 -0
  251. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/tou_triggers.py +0 -0
  252. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/units.py +0 -0
  253. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/status_history.py +0 -0
  254. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/temperature.py +0 -0
  255. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/topic_builder.py +0 -0
  256. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/tou_schedule.py +0 -0
  257. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/unit_system.py +0 -0
  258. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/utils.py +0 -0
  259. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/SOURCES.txt +0 -0
  260. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  261. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  262. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  263. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/requires.txt +0 -0
  264. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/scm_file_list.json +0 -0
  265. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/top_level.txt +0 -0
  266. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/_sim_site.py +0 -0
  267. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/conftest.py +0 -0
  268. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/fixtures/sim/engine_golden.json +0 -0
  269. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/fixtures/sim/heater_params.json +0 -0
  270. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/fixtures/sim/heater_params_fitted.json +0 -0
  271. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_api_endpoints_201.py +0 -0
  272. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_api_helpers.py +0 -0
  273. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_app_queries.py +0 -0
  274. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_auth.py +0 -0
  275. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_auth_session_lifecycle.py +0 -0
  276. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_auth_session_refresh.py +0 -0
  277. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_ble_mqtt_adapter.py +0 -0
  278. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_ble_protocol.py +0 -0
  279. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_bug_fixes.py +0 -0
  280. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_canonical_schedule.py +0 -0
  281. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_basic.py +0 -0
  282. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_calibrate.py +0 -0
  283. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_commands.py +0 -0
  284. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_simulate.py +0 -0
  285. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_tristate_presentation.py +0 -0
  286. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_command_decorators.py +0 -0
  287. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_command_queue.py +0 -0
  288. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_device_capabilities.py +0 -0
  289. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_device_info_cache.py +0 -0
  290. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_device_rest_models.py +0 -0
  291. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_events.py +0 -0
  292. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_exceptions.py +0 -0
  293. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_model_converters.py +0 -0
  294. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_models.py +0 -0
  295. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_clean_session_resume.py +0 -0
  296. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_client_init.py +0 -0
  297. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_control_fail.py +0 -0
  298. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_events.py +0 -0
  299. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_hypothesis.py +0 -0
  300. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_last_will.py +0 -0
  301. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_reconnection.py +0 -0
  302. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_reconnection_storm.py +0 -0
  303. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_reliability.py +0 -0
  304. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_multi_device.py +0 -0
  305. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_navilink_201_enums.py +0 -0
  306. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_openei.py +0 -0
  307. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_protocol_correctness.py +0 -0
  308. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_public_api.py +0 -0
  309. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_reservation_validation.py +0 -0
  310. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_reservations.py +0 -0
  311. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_api.py +0 -0
  312. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_book.py +0 -0
  313. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_codec.py +0 -0
  314. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_cold_supply.py +0 -0
  315. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_controller.py +0 -0
  316. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_documents.py +0 -0
  317. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_draw_forecast.py +0 -0
  318. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_draw_loss.py +0 -0
  319. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_element.py +0 -0
  320. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_element_fit.py +0 -0
  321. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_forecast.py +0 -0
  322. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_golden.py +0 -0
  323. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_import_contract.py +0 -0
  324. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_location_air.py +0 -0
  325. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_no_clock_no_state.py +0 -0
  326. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_observed.py +0 -0
  327. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_profile.py +0 -0
  328. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_readiness.py +0 -0
  329. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_readiness_fit.py +0 -0
  330. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_ready.py +0 -0
  331. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_replay.py +0 -0
  332. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_schedules.py +0 -0
  333. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_tank_physics.py +0 -0
  334. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_status_history.py +0 -0
  335. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_temperature_converters.py +0 -0
  336. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_threading_model.py +0 -0
  337. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_token_storage.py +0 -0
  338. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_tou_api.py +0 -0
  339. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_tou_schedule.py +0 -0
  340. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_unit_switching.py +0 -0
  341. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_unit_system_process_wide.py +0 -0
  342. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_utility_modules.py +0 -0
  343. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_utils.py +0 -0
  344. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tox.ini +0 -0
  345. {nwp500_python-9.11.0 → nwp500_python-9.12.0}/uv.lock +0 -0
@@ -2,6 +2,171 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 9.12.0 (2026-10-23)
6
+ ===========================
7
+
8
+ Added
9
+ -----
10
+
11
+ - **The heater's own entities in the sources file** (#232).
12
+ ``[home_assistant.heater]`` names them (``heard``, ``upper`` and
13
+ ``lower`` required), so the CLI fits and anchors the projected air to
14
+ the heater's reading while its compressor runs where no air entity
15
+ does.
16
+ - **The idle cabinet air, said** (#232). ``Environment.ambient_cabinet``
17
+ marks air taken from the heater's own reading while the compressor
18
+ was idle, about 6 °F warm, and answers give the reason
19
+ ``ambient_from_idle_cabinet``.
20
+ - **The heater's own air in the book** (#236). With no air reading,
21
+ ``book_from_history``, ``book_from_changes`` and
22
+ ``HomeAssistantBook`` take the heater's own air reading once its
23
+ compressor has run 5 minutes (``sim_adapters.settled_air``,
24
+ ``HEATER_AIR_SETTLE``), held until the next run, rather than 65 °F
25
+ throughout. ``HomeAssistantBook`` notes it.
26
+ - **The standby loss from the heater's own air** (#236). With no air
27
+ given, calibration fits the standby loss on the heater's own reading
28
+ once its compressor has run 5 minutes, interpolated from one run to
29
+ the next, over the quiet windows with a run within
30
+ ``HEATER_AIR_BRACKET`` (6 h) of both ends: the law's level only, on
31
+ the base's exponent. ``Calibrated.air`` is ``"heater"``, and a note
32
+ says how many windows qualified and their share of the quiet hours.
33
+ It was left unfitted. ``fit_ua_law``, ``fit_loss``, ``fit_standby``
34
+ and ``calibration.calibrate`` take a fixed exponent.
35
+ - **The book prices a circulation run by a circuit volume.**
36
+ ``CirculationParams.book_circuit_gal``: with it, the heat book
37
+ charges a run its first gallons up to that volume, runs closer than
38
+ ``run_gap_min`` one run, each at the tank's upper probe less the air;
39
+ without it, ``loss_btu_per_gal`` per gallon as before. ``fit_book``
40
+ fits the volume in place of the loss per gallon (``book_circuit_gal``
41
+ on its result), and ``fit_book_terms`` with ``circuit=True``. At one
42
+ installation, over 284 re-anchors, the book's error on pairs with a
43
+ run fell 14 % against a refitted loss per gallon (1741 against 2023
44
+ BTU), and on held-out months from 3184 to 2454 BTU, the volume
45
+ steady at 1.1 to 1.5 gal where the loss per gallon moved from 59 to
46
+ 120. It is an effective volume, apart from ``displaced_gal``.
47
+ ``SITE_CIRCULATION`` carries one installation's, and the book's
48
+ heat-pump rate and slope are refitted with it (4338 BTU/h, 66.0
49
+ BTU/h/°F; the UA stays 2.88).
50
+
51
+ - **The heater's heard spans, given** (#230). ``book_from_changes``
52
+ takes ``heard=``, the spans the heater was heard over, for a record
53
+ that keeps no instant for each response. A gap between spans breaks
54
+ the book as a silence between instants does, and the heater is heard
55
+ at ``at`` only inside a span.
56
+ - **The air fit carried across reads** (#221).
57
+ ``sim_home_assistant.fit_location_air`` is the fit
58
+ ``HomeAssistantEnvironment`` makes where it is given no model, with
59
+ its notes, as a call of its own. ``HomeAssistantEnvironment`` takes
60
+ it back as ``fitted_air``, projects through it, and notes its r and
61
+ lag on every ``read`` without fitting again. A caller that reads
62
+ every few minutes fits on its own cadence.
63
+ - **The heat book kept current from Home Assistant** (#227).
64
+ ``sim_home_assistant.HomeAssistantBook`` holds the heater's and the
65
+ household flow's history, reading only what is new on each ``read``,
66
+ and books from it with no request (``book``, for an executor). The
67
+ flow is the household's draws as delivered through the mixing valve.
68
+ The circulation's speed, an inlet probe and the demand entity, each
69
+ optional, give the runs, the inlet and the conversion at the mains
70
+ (``BookAnswer.inlet_measured``). ``mains_plateau`` and ``known_runs``
71
+ are the inlet's plateau and the pump's runs from a ``History``, as
72
+ ``HomeAssistantEnvironment`` reads them. ``book_from_changes`` and
73
+ ``book_steps_from_changes`` take ``air_outages``: while the air is
74
+ unknown the book takes the default air, not the last reading.
75
+ - **The CPU work off the event loop** (#225). ``fit_location_air`` and
76
+ ``HomeAssistantEnvironment`` take ``run=`` (``Run``), which runs a
77
+ piece of work and awaits its result, such as Home Assistant's
78
+ ``hass.async_add_executor_job``. The fit, the samples of the heater's
79
+ air and the inlet's plateau go through it. Without it they run
80
+ inline (``inline``), as before.
81
+ - **A history kept current** (#223). ``sim_home_assistant.HeaterHistory``,
82
+ ``sim_home_assistant.History`` and ``sim_adapters.ChangeRows`` take
83
+ ``extended(newer)``, which merges a later read in by instant (the
84
+ later read's row winning at a shared instant) and joins the outages,
85
+ and ``since(start)``, which trims to ``start`` and keeps each value in
86
+ force there. A caller that books every few minutes reads only what is
87
+ new and books from what it holds. ``sim_adapters.merge_changes``,
88
+ ``changes_since``, ``join_spans`` and ``spans_since`` do it for one
89
+ series.
90
+
91
+ Changed
92
+ -------
93
+
94
+ - ``SITE_CIRCULATION.meter_share`` is 0. The book's flow is the
95
+ household's draws alone, which no circulation run passes, so the
96
+ example terms take nothing off it. A caller whose meter carries the
97
+ loop's water still sets its own share.
98
+
99
+ - **Readiness with no evidence and no book** (#234). A tank whose probes
100
+ are within the book's ``uniform_band_f`` reads its full-tank floor
101
+ from its probes (``Deliverable.basis`` ``probes``), at any
102
+ temperature. Any other tank reads 0 with the reason
103
+ ``no_evidence_for_state``, which says the gallons are unknown, not
104
+ none, in place of ``colder_than_all_evidence``.
105
+ - The 50 and 80 gal defaults scale the book's empty point
106
+ (``BookParams.empty_btu``) and the full tank's hidden cold water
107
+ (``ReadinessParams.full_hidden_gal``) by tank size, as they scale its
108
+ volume (#234). Both were the 65 gal tank's at every size.
109
+ - Circulation runs given with params that have no circulation terms are
110
+ priced at one installation's (``SITE_CIRCULATION``), and the answer
111
+ says so in ``defaults_used`` (#232). They raised in every walk, and a
112
+ book left them unpriced. ``sim_adapters.with_circulation_terms`` does
113
+ this for the books.
114
+ - A status in Standby is Power Off, as the NaviLink app counts it (#232).
115
+ It raised.
116
+ - ``book_from_changes`` books from the flow's first reading when its
117
+ history starts later than the window, rather than raising (#232).
118
+ - ``known_runs`` returns the pump's runs from its first reading and the
119
+ span before it as unknown, rather than nothing for a history shorter
120
+ than the span (#232). The inlet's plateau skips a draw in that span,
121
+ and ``HomeAssistantBook`` takes no anchor before it.
122
+ - ``read_heater_history`` leaves out a heating flag entity with no
123
+ history at all, with a note, rather than leaving the whole record
124
+ unobserved (#232).
125
+
126
+ - The simulate how-to's ``book_from_changes`` example passes the
127
+ circulation's runs and flow, and says to pass the household's draws,
128
+ separated from any circulation in the meter, with ``meter_share`` 0.
129
+ - With a model, given or fitted, and no fresh reading of the air beside
130
+ the heater, ``HomeAssistantEnvironment`` anchors the projection to
131
+ ``heater``'s newest air reading from the last hour while its
132
+ compressor ran, on every ``read``. It anchored to one only on the
133
+ read that fitted the model (#221).
134
+
135
+ Fixed
136
+ -----
137
+
138
+ - ``simulate`` and ``time_to_ready`` convert a book's heat to gallons at
139
+ the scenario's inlet (``Environment.inlet_f``) where one is given, as
140
+ the walk's draws take it (#234). The gallons ignored a measured inlet,
141
+ and with a book a colder inlet gave warmer delivered water.
142
+ - ``fit_location_air`` fits to the heater's own air when the air entity
143
+ can't be read or gives no fit (#232).
144
+ - Calibration's recoveries outside a partial air record read the
145
+ heater's own air, rather than being dropped (#232).
146
+ - The simulate how-to's and ``sim_live``'s ``compose`` example falls
147
+ back to the 65 gal defaults when the feature info doesn't arrive
148
+ (#232). It passed None to ``simulate``.
149
+ - ``HomeAssistantBook`` says when the flow is unknown now (#232).
150
+
151
+ - **A heater read missing a heating flag is unobserved** (#227).
152
+ ``read_heater_history`` reads a day at a time; a window with nothing
153
+ of the compressor, an element or the heat source (each opens with
154
+ the value in force) now leaves the heater unobserved over it, with a
155
+ note, rather than holding the flag from before.
156
+ - ``sim_adapters.book_from_changes`` read its flow as a sampled meter:
157
+ a reading held 60 seconds (``FLOW_HOLD``) and the inlet's an hour,
158
+ and flow rows more than an hour apart refused the book as a silent
159
+ meter. A history of changes writes a row only when a value changes,
160
+ so a steady draw counted for a minute and an hour at 0 gal/min was
161
+ refused. Its flows, inlet and air now hold until the next change. It
162
+ takes ``flow_outages``, a span with no anchor before it counting, as
163
+ for a heater unheard, and ``inlet_outages``. No flow reading in force
164
+ at the window's start is a ``ValueError`` (#219).
165
+ - ``sim_home_assistant.pump_runs`` ended a run 5 minutes after its last
166
+ speed reading. A pump at a steady speed writes no row, so a long run
167
+ read as 5 minutes. A speed now holds until it changes; ``stale``
168
+ (``PUMP_STALE``) keeps the old rule for a sampled speed (#219).
169
+
5
170
  Version 9.11.0 (2026-10-22)
6
171
  ===========================
7
172
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 9.11.0
3
+ Version: 9.12.0
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Home-page: https://github.com/eman/nwp500-python
6
6
  Author: Emmanuel Levijarvi
@@ -96,9 +96,23 @@ Idle, it reads the cabinet, about 6 °F warm. So give one of these:
96
96
  the heater's own flag is used instead. ``--air``, ``--ambient`` and
97
97
  ``--humidity`` override the sources.
98
98
 
99
- Without the air, the standby loss keeps the base's value and the report
100
- says so. When you give the air, the recoveries use it too; without it,
101
- they use the heater's own reading while the compressor runs.
99
+ Without the air, the command fits the standby loss on the heater's own
100
+ reading, taken once the compressor has run 5 minutes and drawn straight
101
+ from one run's reading to the next across the idle span between them.
102
+ Only quiet windows with a run within 6 hours of both ends are fitted:
103
+ they fall mostly overnight, and a reading held from the day's last run
104
+ would read the night warm. Those windows span too narrow a range of
105
+ tank-to-air difference to fit the loss law's shape, so only its level is
106
+ fitted, on the base's exponent. A note says how many windows qualified
107
+ and what share of the quiet hours they hold. Expect the loss to read
108
+ somewhat high: at one installation, against a sensor beside the heater,
109
+ it read about 12 % high, as the night air dips below the line between
110
+ an evening run and a morning one, and the running reading is itself a
111
+ little warm. A sensor beside the heater (``--air``) avoids both.
112
+ With no such reading at all, the standby loss keeps the base's value
113
+ and the report says so. When you give the air, the recoveries use it
114
+ too; without it, they use the heater's own reading while the compressor
115
+ runs.
102
116
 
103
117
  ``--humidity`` takes the same kind of file, in %RH, for the
104
118
  evaporator; without it the command gives the fits 50 %.
@@ -205,7 +219,8 @@ read in order without holding every status:
205
219
  print(term.name, term.value, "fitted" if term.fitted else term.why)
206
220
 
207
221
  Give ``ambient`` instead of ``air`` for a constant, or neither: the
208
- standby loss then keeps the base's value. Without ``humidity`` the fits
222
+ standby loss is then fitted on the heater's own reading between runs,
223
+ and ``done.air`` is ``"heater"``. Without ``humidity`` the fits
209
224
  get 50 %. An input it cannot use raises ``CalibrationInputError``.
210
225
 
211
226
  On a desktop computer, 60 days of statuses (about 540,000) took about
@@ -373,8 +388,11 @@ Fit the heat book
373
388
 
374
389
  The heat book (``book_from_changes``) counts the heat the tank holds
375
390
  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
391
+ UA, and, with a circulation pump, what each run takes: its first
392
+ gallons, up to the book's circuit volume
393
+ (``CirculationParams.book_circuit_gal``), each at the tank's upper
394
+ probe less the air, the loop having cooled to the air since the last
395
+ run. ``fit_book`` fits them
378
396
  together from the heater's history, the household's draws, and the
379
397
  pump's runs with the loop's flow. The draws and the loop's flow are
380
398
  yours to give, apart from each other: the fit takes every gallon of the
@@ -402,8 +420,8 @@ draws as hot water drawn, and each run at the loop's flow.
402
420
 
403
421
  ``draws`` and ``loop_flow`` are (instant, gal/min) readings and ``runs``
404
422
  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).
423
+ takes it. ``fit.book_circuit_gal`` is the circuit volume for your
424
+ ``CirculationParams`` (None without a loop).
407
425
 
408
426
  The fit reads the pairs of quiet full tanks up to
409
427
  ``book.max_anchor_age_h`` apart, and fits each term for the least mean
@@ -415,18 +433,22 @@ pair:
415
433
  the others (the slope, with the air never off one temperature) stays
416
434
  at ``params.book``. The rest of the book (its draw line, the anchors'
417
435
  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.
436
+ - **The book's circuit volume:** on 10 pairs with a run between, with
437
+ the book's terms fitted at each volume tried. Runs closer than
438
+ ``run_gap_min`` are one run. It is an effective volume, smaller than
439
+ the loop's own (``displaced_gal``): at one installation 1.4 gal
440
+ against 6.4. Fit and keep them apart. With fewer pairs, the loop is
441
+ priced as ``params.circulation`` prices it, and without one, the
442
+ pairs with a run are left out. A pair with a run that has no flow
443
+ reading is left out.
422
444
 
423
445
  The fit needs 10 pairs. It holds out the latest fifth and replaces the
424
446
  base only when it books them no worse. A term on its bound
425
447
  (``sim.calibration.BOOK_TERM_BOUNDS``) is no fit: the book misses
426
448
  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
449
+ circuit volume on its bound (``sim.calibration.BOOK_CIRCUIT_GAL_BOUNDS``)
450
+ leaves the loop at the base's; any other term on its bound keeps the
451
+ base. ``fit.kept`` says which it
430
452
  kept and ``fit.why`` why. A span the heater wasn't heard from, a probe
431
453
  read nothing, or the draws were silent over an hour, breaks the book.
432
454
 
@@ -435,10 +457,10 @@ come slowly. With a pump running about once a day, one installation had
435
457
  5 in 14 days and 19 in 42: fit on four weeks or more, and raise Home
436
458
  Assistant's ``purge_keep_days`` to keep them.
437
459
 
438
- Book with the same inputs. Runs are priced with the loop's loss, in a
460
+ Book with the same inputs. Runs are priced at the circuit volume, in a
439
461
  ``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
462
+ since the draws hold none of the loop's water; without one, they can't
463
+ be booked. ``sa.run_readings`` gives the book each run's flow as
442
464
  the fit takes it: its own readings, the first from its start, and none
443
465
  read outside the runs.
444
466
 
@@ -447,13 +469,13 @@ read outside the runs.
447
469
  from nwp500.sim.params import CirculationParams
448
470
 
449
471
  book = None
450
- loss = fit.loss_btu_per_gal
451
- if loss is not None or not runs:
472
+ volume = fit.book_circuit_gal
473
+ if volume is not None or not runs:
452
474
  loop = None
453
- if loss is not None:
475
+ if volume is not None:
454
476
  loop = CirculationParams(
455
- displaced_gal=..., flow_gpm=..., loss_btu_per_gal=loss,
456
- meter_share=0.0,
477
+ displaced_gal=..., flow_gpm=..., loss_btu_per_gal=...,
478
+ book_circuit_gal=volume, meter_share=0.0,
457
479
  )
458
480
  book = sa.book_from_changes(
459
481
  history.rows, history.observed, replace(params, circulation=loop),
@@ -142,6 +142,13 @@ pass it with ``--sources`` (or set ``NWP500_SOURCES``):
142
142
  circulation_speed = "sensor.circulation_pump_speed"
143
143
  weather = "weather.forecast_home"
144
144
 
145
+ [home_assistant.heater] # optional: the heater's own entities
146
+ heard = "sensor.water_heater_last_status"
147
+ upper = "sensor.water_heater_upper_tank_temperature"
148
+ lower = "sensor.water_heater_lower_tank_temperature"
149
+ compressor = "binary_sensor.water_heater_compressor"
150
+ ambient = "sensor.water_heater_ambient_temperature"
151
+
145
152
  [location_air] # optional: air = damping x outdoor(t - lag) + offset
146
153
  damping = 0.638
147
154
  lag_hours = 2.0
@@ -162,7 +169,11 @@ give:
162
169
  heater that is at most an hour old; without a fresh one, the
163
170
  projection is unshifted. Without a ``[location_air]`` model, one is
164
171
  fitted on the last 3 days of the ``air`` entity's readings against
165
- the ``weather`` entity's outdoor temperature. It is used when the
172
+ the ``weather`` entity's outdoor temperature. Without an ``air``
173
+ entity, or one that gives no fit, the ``[home_assistant.heater]``
174
+ table's ``ambient`` reading while its ``compressor`` runs is fitted
175
+ instead, and the projection is shifted to pass through its newest
176
+ such reading. It is used when the
166
177
  readings span a day and follow the outdoor air at r 0.5 or more; a
167
178
  note gives the fit. Otherwise, and without a ``weather`` entity, the
168
179
  reading beside the heater is held over the span;
@@ -171,7 +182,8 @@ give:
171
182
  circulation off) in the last 72 hours, from the inlet probe and the
172
183
  ``demand`` entity: a binary sensor that is on while the household
173
184
  draws hot water, with the circulation's water not among its draws. A
174
- circulation pump with no speed reading leaves no estimate.
185
+ circulation pump with no speed reading leaves no estimate, and a draw
186
+ before its first reading is skipped.
175
187
 
176
188
  A reading's age is from when Home Assistant last heard the sensor
177
189
  report (``last_reported``). Over the REST API, Home Assistant serves
@@ -191,8 +203,34 @@ same parts. Given ``heater=``, a ``HeaterEntities`` with ``ambient`` and
191
203
  heater's own air reading every 10 minutes while the compressor runs,
192
204
  where the reading was taken 2 minutes or more into the run (earlier, or
193
205
  idle, it reads the cabinet), and not while the sensor was unavailable.
194
- The latest such reading, if under an hour old, anchors the projection.
195
- ``fitted_air`` holds the fit once ``read`` has run.
206
+ With a model, given or fitted, and no fresh reading beside the heater,
207
+ each ``read`` anchors the projection to the heater's newest such
208
+ reading from the last hour. ``fitted_air`` holds the fit once ``read``
209
+ has run.
210
+
211
+ A caller that builds a new environment every few minutes needn't refit
212
+ each time. Fit on your own cadence, and pass the fit on:
213
+
214
+ .. code-block:: python
215
+
216
+ fitted, notes = await ha.fit_location_air(
217
+ client, entities, now=now, heater=heater_entities
218
+ ) # hourly, say; None (and notes why) without enough history
219
+
220
+ environment = ha.HomeAssistantEnvironment(
221
+ client, entities, start=now, until=now + horizon, now=now,
222
+ heater=heater_entities, fitted_air=fitted,
223
+ )
224
+
225
+ Given ``fitted_air``, the environment projects through it, notes its r
226
+ and lag on every ``read``, and doesn't fit again.
227
+
228
+ The fit, the samples of the heater's air and the inlet's plateau are
229
+ CPU work: the fit alone takes about 100 ms on 3 days of 30-second
230
+ readings. To keep it off an event loop, pass ``run=``, a callable that
231
+ runs a function and awaits its result, to ``fit_location_air`` and
232
+ ``HomeAssistantEnvironment``. In Home Assistant that is
233
+ ``hass.async_add_executor_job``. Without it, the work runs inline.
196
234
 
197
235
 
198
236
  Fetch what the heater reports
@@ -226,7 +264,10 @@ A scenario starts from four things the library already reads:
226
264
  heater=sim_live.LiveHeaterSource(mqtt, device),
227
265
  schedules=sim_live.LiveScheduleSource(mqtt, api, device),
228
266
  )
229
- return sim.simulate(composed.scenario, composed.params)
267
+ # Without the feature info the tank size is unknown: the 65 gal
268
+ # defaults stand in.
269
+ params = composed.params or sim.default_params()
270
+ return sim.simulate(composed.scenario, params)
230
271
 
231
272
  ``environment=`` and ``loads=`` take any source with an ``async
232
273
  read()``; ``sim_live.Given(value)`` is one for a value in hand. Without
@@ -234,7 +275,7 @@ read()``; ``sim_live.Given(value)`` is one for a value in hand. Without
234
275
  gets no reply raises ``sim_live.LiveReadError``; so does the schedule
235
276
  source when TOU is on and its periods cannot be read. A missing
236
277
  feature info leaves ``composed.params`` None (the tank size is
237
- unknown). Change a part with ``composed.scenario.model_copy(update=...)``.
278
+ unknown), so the example falls back to ``sim.default_params()``. Change a part with ``composed.scenario.model_copy(update=...)``.
238
279
 
239
280
  With the status, the feature info, the reservations and the TOU
240
281
  periods already read some other way, build the same parts from them:
@@ -887,14 +928,106 @@ with the instants the heater was heard from:
887
928
  params,
888
929
  at=now,
889
930
  flow=draws_gpm, # gal/min, whatever the preferred units
931
+ flow_outages=flow_down, # when the flow entity was unavailable
932
+ circulation=runs, # [(start, end), ...], if you circulate
933
+ circulation_flow=pump_flow, # the loop's gal/min while it runs
934
+ )
935
+
936
+ ``flow`` is the household's draws. If your meter also carries the
937
+ circulation's water, separate the household's draws from it yourself
938
+ and pass those, with ``CirculationParams.meter_share`` 0, so the book
939
+ takes nothing off them again. Clean up the loop's flow before passing it
940
+ too (drop readings below the pump's floor, or one taken mid-ramp): while
941
+ a run is on, each step takes the last reading in force (a run before
942
+ any reading takes ``CirculationParams.flow_gpm``).
943
+
944
+ Like the history's rows, its ``flow``, ``circulation_flow``, ``inlet``
945
+ and ``air`` are changes: each value holds until the next, so a steady
946
+ draw is one row and an hour at 0 gal/min none. They are gal/min and °F,
947
+ whatever the preferred units. A span the heater wasn't heard from (an
948
+ outage, or a silence much longer than its usual reporting), or the flow
949
+ was unknown (``flow_outages``, from the history's ``outages``), breaks
950
+ the book: it is walked only from an anchor after the last one, and is
951
+ None while the heater isn't heard now. A record that keeps no instant
952
+ for each of the heater's responses passes ``heard=``, the spans the
953
+ heater was heard over, and an empty ``observed``: a gap between two
954
+ spans breaks the book the same way, and the heater is heard now only
955
+ inside a span. With no flow reading in force
956
+ at the start of the window it books over, it raises ``ValueError``.
957
+ An inlet reading holds except through ``inlet_outages``.
958
+
959
+ To keep a book current as you walk every few minutes, keep the history
960
+ rather than the book. Read only what is new since your last read,
961
+ extend what you hold with it, trim it to the book's window, and book
962
+ again from memory:
963
+
964
+ .. code-block:: python
965
+
966
+ newer = await ha.read_heater_history(client, entities, last_read, now)
967
+ history = history.extended(newer).since(now - timedelta(hours=72))
968
+ flows = flows.extended(
969
+ await client.read_history([FLOW], last_read, now)
970
+ ).since(now - timedelta(hours=72))
971
+ last_read = now
972
+
973
+ ``HeaterHistory`` and ``History`` both take ``extended`` and ``since``.
974
+ A read opens with the value in force at its start; as each value holds
975
+ until the next change, that row changes nothing. Where both reads have
976
+ a row at one instant, the later read's is kept. ``since`` keeps each
977
+ value in force at the cut. 72 hours is ``BookParams.history_h``, the
978
+ oldest anchor a book reads.
979
+
980
+ ``sim_home_assistant.HomeAssistantBook`` does all of that from Home
981
+ Assistant's entities:
982
+
983
+ .. code-block:: python
984
+
985
+ keeper = ha.HomeAssistantBook(
986
+ client,
987
+ heater_entities, # ha.HeaterEntities
988
+ "sensor.hot_water_draw_flow", # the household's draws
989
+ ha.Entities(
990
+ circulation_speed="sensor.circulation_pump_speed",
991
+ inlet="sensor.cold_supply_temperature",
992
+ demand="binary_sensor.hot_water_demand",
993
+ ),
890
994
  )
891
995
 
892
- Like the history's rows, its ``flow`` and ``circulation_flow`` are
893
- gal/min and its ``inlet`` and ``air`` °F, whatever the preferred units.
894
- A span the heater wasn't heard from (an outage, or a silence much
895
- longer than its usual reporting) breaks the book: it is walked only
896
- from an anchor after the last one, and is None while the heater isn't
897
- heard now.
996
+ await keeper.read(now) # all 72 h the first time, then what's new
997
+ answer = await hass.async_add_executor_job(keeper.book, now, params)
998
+ readiness = sim.time_to_ready(
999
+ scenario, params, book=answer.booked,
1000
+ inlet_measured=answer.inlet_measured,
1001
+ )
1002
+
1003
+ ``read`` reads the history since the last read and holds it; a read
1004
+ that fails is noted, and the next one reads from where it would have
1005
+ started. Until then, ``book`` takes the flow as unknown since the last
1006
+ read that worked, rather than holding its last value, and after a
1007
+ failed read of the heater it gives no book. An optional
1008
+ entity whose unit can't be read is left out of the book. With no air
1009
+ entity, or one that gives no reading, the book takes the heater's own
1010
+ air reading (``HeaterEntities.ambient``) once its compressor has run 5
1011
+ minutes, and holds it until the next run, and a note says so. Idle, that
1012
+ sensor reads the cabinet, about 6 °F warm, so an idle reading is never
1013
+ taken. Before the first run, and while an air entity that gives readings
1014
+ is unavailable or reads in a unit the book doesn't read, the book takes
1015
+ the default air (65 °F). ``book`` books from what it holds, with no
1016
+ request, so it can run in an executor. ``answer.notes`` say what the book rests on, or why
1017
+ there is none.
1018
+
1019
+ The flow is the household's hot water draws, without the circulation's
1020
+ water, in gal/min or any unit ``to_gpm`` reads. It is the water
1021
+ delivered at the taps, through the mixing valve: the book prices a
1022
+ delivered gallon at the valve's delivery line
1023
+ (``DrawParams.delivery_intercept_f`` and ``delivery_slope``), not as
1024
+ water withdrawn from the tank. As none of the flow is circulation,
1025
+ ``book`` takes ``CirculationParams.meter_share`` as 0. A flow entity that reports a draw late
1026
+ reaches the book late: the book holds what the history holds at
1027
+ ``read``. Each of the other entities is optional:
1028
+ ``circulation_speed`` gives the circulation's runs, ``inlet`` the inlet
1029
+ the book converts at, ``inlet`` with ``demand`` the mains, and ``air``
1030
+ the air beside the heater.
898
1031
 
899
1032
  Without a meter, start one only when you know the tank is full and
900
1033
  quiet (``sa.full_tank_book``).
@@ -1035,15 +1168,18 @@ to a day apart. Each pair's booked heat is checked against the probes':
1035
1168
  circulation=runs, circulation_flow=pump_flow, params=params,
1036
1169
  )
1037
1170
  book = calibration.RecordedBook(steps, p, params.circulation, start=since)
1038
- fit = calibration.fit_book_terms(book, book.reanchors(), p)
1171
+ fit = calibration.fit_book_terms(book, book.reanchors(), p, circuit=True)
1039
1172
  p = replace(p, hp_btu_per_h=fit.hp_btu_per_h,
1040
1173
  hp_btu_per_h_per_f=fit.hp_btu_per_h_per_f,
1041
1174
  ua_btu_per_h_f=fit.ua_btu_per_h_f)
1042
- bands = calibration.book_error_bands(book, p, fit.loss_btu_per_gal)
1043
- empty, crossings = calibration.empty_point(book, p, fit.loss_btu_per_gal)
1044
- zone = calibration.zone_empty_point(book, p, fit.loss_btu_per_gal)
1045
-
1046
- ``book_error_btu(book, p, fit.loss_btu_per_gal)`` gives the book's
1175
+ v = fit.book_circuit_gal
1176
+ bands = calibration.book_error_bands(book, p, 0.0, v)
1177
+ empty, crossings = calibration.empty_point(book, p, 0.0, circuit_gal=v)
1178
+ zone = calibration.zone_empty_point(book, p, 0.0, circuit_gal=v)
1179
+
1180
+ With ``circuit=True`` the runs are priced at a circuit volume
1181
+ (``CirculationParams.book_circuit_gal``) fitted in place of a loss per
1182
+ gallon. ``book_error_btu(book, p, 0.0, v)`` gives the book's
1047
1183
  error bands: the 90th percentile of its error pooled over re-anchors an
1048
1184
  hour to ``max_anchor_age_h`` apart, and the 18 to 24 h band's past it,
1049
1185
  each rounded up to 100 BTU. ``cold_lower_cap(draws,
@@ -154,7 +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
+ fit_terms_l1, BOOK_TERMS, BOOK_TERM_BOUNDS, BOOK_CIRCUIT_GAL_BOUNDS,
158
158
  book_error_bands, book_error_btu, empty_point,
159
159
  zone_empty_point, withheld_btu, book_walk, long_draws,
160
160
  front_frame, fit_front, FrontFit, settle_tails, fit_front_settle,
@@ -305,14 +305,17 @@ Recorded history
305
305
  fit_book, BookTermsFit, MIN_BOOK_PAIRS, MIN_RUN_PAIRS,
306
306
  score, Judged, HeldOut, GROUPS, air_unread_note,
307
307
  CalibrationInputError, DEFAULT_HUMIDITY_PCT,
308
- DEFAULT_BASE_NOTE, NO_AIR_NOTE
308
+ DEFAULT_BASE_NOTE, NO_AIR_NOTE, HEATER_AIR_BRACKET
309
309
 
310
310
 
311
311
  Home Assistant sources
312
312
  ======================
313
313
 
314
314
  .. automodule:: nwp500.sim_home_assistant
315
- :members: HomeAssistantEnvironment, HomeAssistantClient, record_inputs,
315
+ :members: HomeAssistantEnvironment, HomeAssistantClient, fit_location_air,
316
+ Run, inline, HomeAssistantBook, BookAnswer, BOOK_WINDOW,
317
+ mains_plateau, known_runs,
318
+ record_inputs,
316
319
  RecordInputs, INLET_LOOKBACK, PROBE_STEP, held, LOCATION_AIR_WINDOW,
317
320
  LOCATION_AIR_MIN_SPAN, LOCATION_AIR_MIN_R, RUN_SETTLE, Sources,
318
321
  Entities, sources_from, State, parse_state,