apply-freeze-export-ac-flow #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: apply-freeze-export-ac-flow | |
| on: | |
| create: | |
| permissions: | |
| contents: write | |
| jobs: | |
| apply-and-validate: | |
| if: github.ref == 'refs/heads/internal-freeze-ac-flow-trigger' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: 3.x | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.16.0 | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pre-commit | |
| - name: Apply revised Freeze Export accounting | |
| run: | | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| def repl(path, old, new): | |
| p = Path(path); text = p.read_text(); n = text.count(old) | |
| if n != 1: raise RuntimeError(f'{path}: expected one match, got {n}') | |
| p.write_text(text.replace(old, new, 1)) | |
| repl('apps/predbat/prediction.py', ''' # Some inverters (observed on AlphaESS) continue to discharge the battery\n # during Freeze Export to supply house load. The configured value is measured\n # battery-side power, so pass it through the normal discharge and inverter-loss\n # paths, cap it at remaining house demand, and never create additional export.\n if inverter_freeze_export_discharge_rate > 0 and battery_draw >= 0:\n freeze_house_demand = get_diff(battery_draw, pv_dc, pv_ac, load_yesterday, inverter_loss, inverter_loss_recp)\n if freeze_house_demand > 0:\n freeze_soc_available = min(inverter_freeze_export_discharge_rate * step / 60000.0, max(soc - reserve_expected, 0))\n freeze_draw_limit = freeze_soc_available * battery_loss_discharge\n freeze_draw_for_house = freeze_house_demand * inverter_loss_recp\n battery_draw = min(freeze_draw_limit, freeze_draw_for_house)\n''', ''' # Some inverters (observed on AlphaESS) continue a residual battery\n # discharge during Freeze Export. The configured value is measured battery-side\n # power; feed it into the normal AC energy balance so house load consumes it first\n # and any excess naturally becomes grid export.\n if inverter_freeze_export_discharge_rate > 0 and battery_draw >= 0:\n freeze_soc_available = min(inverter_freeze_export_discharge_rate * step / 60000.0, max(soc - reserve_expected, 0))\n freeze_draw_limit = freeze_soc_available * battery_loss_discharge\n battery_draw = max(battery_draw, freeze_draw_limit)\n''') | |
| repl('apps/predbat/prediction_kernel.cpp', ''' // Some inverters (observed on AlphaESS) continue to discharge the battery\n // during Freeze Export to supply house load. The configured value is battery-side\n // power; route it through normal discharge and inverter losses and cap at load.\n if (inverter_freeze_export_discharge_rate > 0 && battery_draw >= 0) {\n const double freeze_house_demand = get_diff(battery_draw, pv_dc, pv_ac, load_yesterday, inverter_loss, inverter_loss_recp);\n if (freeze_house_demand > 0) {\n const double freeze_soc_available = std::min(inverter_freeze_export_discharge_rate * step / 60000.0, std::max(soc - reserve_expected, 0.0));\n const double freeze_draw_limit = freeze_soc_available * battery_loss_discharge;\n const double freeze_draw_for_house = freeze_house_demand * inverter_loss_recp;\n battery_draw = std::min(freeze_draw_limit, freeze_draw_for_house);\n }\n }\n''', ''' // Some inverters (observed on AlphaESS) continue a residual battery\n // discharge during Freeze Export. The configured value is battery-side power; feed\n // it into the normal AC energy balance so house load consumes it first and any excess\n // naturally becomes grid export.\n if (inverter_freeze_export_discharge_rate > 0 && battery_draw >= 0) {\n const double freeze_soc_available = std::min(inverter_freeze_export_discharge_rate * step / 60000.0, std::max(soc - reserve_expected, 0.0));\n const double freeze_draw_limit = freeze_soc_available * battery_loss_discharge;\n battery_draw = std::max(battery_draw, freeze_draw_limit);\n }\n''') | |
| repl('apps/predbat/prediction_kernel.cpp', '#define PK_PARITY_REVISION 8\n', '#define PK_PARITY_REVISION 9\n') | |
| repl('apps/predbat/prediction_kernel.py', 'KERNEL_PARITY_REVISION = 8\n', 'KERNEL_PARITY_REVISION = 9\n') | |
| repl('apps/predbat/predbat.py', ' # Battery-side discharge (W) that may continue to supply house load during Freeze Export.\n', ' # Battery-side residual discharge (W) during Freeze Export; AC balance decides house use vs export.\n') | |
| repl('apps/predbat/tests/test_model.py', ''' # Freeze Export discharge should represent real battery energy supplied to house load.\n # Normal battery discharge is disabled here so only this configured path is under test.\n # 240 W for one hour is 0.24 kWh battery-side.\n''', ''' # Freeze Export residual discharge is real battery energy delivered to the AC side.\n # House load consumes it first and any excess can flow to grid export. Normal battery\n # discharge is disabled here so only this configured path is under test.\n # 240 W for one hour is 0.24 kWh battery-side.\n''') | |
| repl('apps/predbat/tests/test_model.py', ''' failed |= simple_scenario(\n "freeze_export_house_supply_no_load",\n my_predbat,\n 0,\n 0,\n assert_final_metric=0,\n assert_final_soc=10.0,\n battery_size=10.0,\n battery_soc=10.0,\n discharge=99,\n end_record=60,\n inverter_freeze_export_discharge_rate=240.0,\n battery_rate_max_charge=0.0,\n assert_battery_cycle=0.0,\n )\n''', ''' failed |= simple_scenario(\n "freeze_export_residual_exports_when_no_load",\n my_predbat,\n 0,\n 0,\n assert_final_metric=-1.2,\n assert_final_soc=9.76,\n battery_size=10.0,\n battery_soc=10.0,\n discharge=99,\n end_record=60,\n inverter_freeze_export_discharge_rate=240.0,\n battery_rate_max_charge=0.0,\n assert_battery_cycle=0.24,\n )\n''') | |
| PY | |
| - name: Pre-commit touched files | |
| run: | | |
| pre-commit run --files apps/predbat/predbat.py apps/predbat/prediction.py apps/predbat/prediction_kernel.cpp apps/predbat/prediction_kernel.py apps/predbat/tests/test_model.py || pre-commit run --files apps/predbat/predbat.py apps/predbat/prediction.py apps/predbat/prediction_kernel.cpp apps/predbat/prediction_kernel.py apps/predbat/tests/test_model.py | |
| - name: Build and test native kernel | |
| env: | |
| PREDBAT_KERNEL_REQUIRED: "1" | |
| run: | | |
| bash apps/predbat/build_kernel.sh | |
| cd coverage | |
| python3 ../apps/predbat/unit_test.py --quick | |
| cd .. | |
| - name: Cross-build all kernel binaries | |
| run: bash apps/predbat/build_kernel_cross.sh | |
| - name: Verify x86_64 kernel | |
| run: | | |
| cd coverage | |
| python3 ../apps/predbat/verify_kernel_binary.py ../apps/predbat/prediction_kernel_lib_x86_64.so | |
| - name: Publish one clean commit | |
| env: | |
| BASE_SHA: 842d69946d28d6fba46dc02f5ccacaf3db627b7c | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| rm -f .github/workflows/apply-freeze-export-ac-flow.yml | |
| git reset --soft "$BASE_SHA" | |
| git add -A | |
| git commit -m "fix(prediction): model Freeze Export residual AC flow" | |
| git push --force-with-lease origin HEAD:main |