uz sem zaoufalej
This commit is contained in:
121
scripts/diagnose_home01_infeasible.py
Normal file
121
scripts/diagnose_home01_infeasible.py
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bisect Infeasible na reálných slotech home-01 (MCP run 16674). PYTHONPATH=backend."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "backend"))
|
||||
|
||||
from services.planning_engine import PlanningSlot, solve_dispatch, solve_dispatch_two_pass, PLANNER_BUILD_TAG
|
||||
|
||||
# Export z MCP: planning_interval run_id=16674 + fn_planning_site_context(2)
|
||||
SLOTS_JSON = Path(__file__).with_name("home01_run16674_slots.json")
|
||||
SOC_WH = 37120.0
|
||||
|
||||
|
||||
def _ctx() -> tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace, list]:
|
||||
battery = SimpleNamespace(
|
||||
usable_capacity_wh=64000.0,
|
||||
min_soc_wh=6400.0,
|
||||
arb_floor_wh=12800.0,
|
||||
reserve_soc_wh=12800.0,
|
||||
soc_max_wh=64000.0,
|
||||
charge_efficiency=0.95,
|
||||
discharge_efficiency=0.95,
|
||||
degradation_cost_czk_kwh=0.15,
|
||||
max_charge_power_w=18000,
|
||||
max_discharge_power_w=18000,
|
||||
charge_slot_buffer=1.3,
|
||||
discharge_slot_buffer=1.5,
|
||||
planner_terminal_soc_value_factor=0.9,
|
||||
planner_discharge_floor_percent=5.0,
|
||||
planner_extreme_buy_threshold_czk_kwh=-2.0,
|
||||
planner_daytime_charge_target_enabled=True,
|
||||
planner_charge_commitment_penalty_czk_kwh=0.2,
|
||||
planner_night_baseload_buffer_percent=20,
|
||||
)
|
||||
grid = SimpleNamespace(
|
||||
max_import_power_w=17000,
|
||||
max_export_power_w=13500,
|
||||
block_export_on_negative_sell=False,
|
||||
deye_gen_microinverter_cutoff_enabled=False,
|
||||
)
|
||||
hp = SimpleNamespace(rated_heating_power_w=0, tuv_min_temp_c=45.0, tuv_target_temp_c=55.0)
|
||||
vehicles = [
|
||||
SimpleNamespace(max_charge_power_w=11000, battery_capacity_kwh=75.0, default_target_soc_pct=80.0),
|
||||
SimpleNamespace(max_charge_power_w=7400, battery_capacity_kwh=52.0, default_target_soc_pct=90.0),
|
||||
]
|
||||
return battery, hp, grid, vehicles
|
||||
|
||||
|
||||
def load_slots(*, permissive_masks: bool) -> list[PlanningSlot]:
|
||||
rows = json.loads(SLOTS_JSON.read_text())
|
||||
out: list[PlanningSlot] = []
|
||||
for r in rows:
|
||||
ts = datetime.fromisoformat(r["interval_start"].replace("Z", "+00:00"))
|
||||
pv_surplus = max(0, int(r["pv_a"]) + int(r["pv_b"]) - int(r["load"]))
|
||||
out.append(
|
||||
PlanningSlot(
|
||||
interval_start=ts,
|
||||
buy_price=float(r["buy"]),
|
||||
sell_price=float(r["sell"]),
|
||||
pv_a_forecast_w=int(r["pv_a"]),
|
||||
pv_b_forecast_w=int(r["pv_b"]),
|
||||
load_baseline_w=int(r["load"]),
|
||||
ev1_connected=False,
|
||||
ev2_connected=False,
|
||||
allow_charge=True if permissive_masks else (float(r["buy"]) < 0 or (float(r["sell"]) < 0 and pv_surplus > 500)),
|
||||
allow_discharge_export=permissive_masks,
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def try_solve(label: str, slots: list[PlanningSlot], **kwargs) -> str:
|
||||
battery, hp, grid, vehicles = _ctx()
|
||||
try:
|
||||
if kwargs.pop("two_pass", False):
|
||||
solve_dispatch_two_pass(
|
||||
slots, battery, hp, grid, [None, None], vehicles, SOC_WH, 55.0,
|
||||
operating_mode="AUTO", **kwargs,
|
||||
)
|
||||
else:
|
||||
solve_dispatch(
|
||||
slots, battery, hp, grid, [None, None], vehicles, SOC_WH, 55.0,
|
||||
operating_mode="AUTO", **kwargs,
|
||||
)
|
||||
return f"OK {label}"
|
||||
except Exception as e:
|
||||
return f"FAIL {label}: {e}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not SLOTS_JSON.exists():
|
||||
print(f"Chybí {SLOTS_JSON} — spusť export z MCP (run 16674).", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("tag", PLANNER_BUILD_TAG)
|
||||
print("slots", len(json.loads(SLOTS_JSON.read_text())))
|
||||
neg_buy = [r for r in json.loads(SLOTS_JSON.read_text()) if r["buy"] < 0]
|
||||
print("neg_buy slots", len(neg_buy), "first", neg_buy[0]["interval_start"] if neg_buy else None)
|
||||
|
||||
cases = [
|
||||
("permissive masks, 1-pass", dict(permissive_masks=True, two_pass=False)),
|
||||
("permissive masks, 2-pass", dict(permissive_masks=True, two_pass=True)),
|
||||
("realistic masks, 1-pass", dict(permissive_masks=False, two_pass=False)),
|
||||
("realistic masks, 2-pass", dict(permissive_masks=False, two_pass=True)),
|
||||
("realistic + relaxed_expensive", dict(permissive_masks=False, two_pass=False, relaxed_expensive_import=True)),
|
||||
("realistic + both relaxed", dict(permissive_masks=False, two_pass=False, relaxed_expensive_import=True, relaxed_neg_buy_pressure=True)),
|
||||
]
|
||||
for label, kw in cases:
|
||||
masks = kw.pop("permissive_masks")
|
||||
slots = load_slots(permissive_masks=masks)
|
||||
print(try_solve(label, slots, **kw))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
scripts/home01_run16674_slots.json
Normal file
1
scripts/home01_run16674_slots.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"interval_start":"2026-05-24T23:45:00.000Z","buy":4.766958,"sell":2.9315,"load":579,"pv_a":0,"pv_b":0},{"interval_start":"2026-05-25T11:00:00.000Z","buy":-0.070392,"sell":-0.8225,"load":858,"pv_a":6306,"pv_b":7397},{"interval_start":"2026-05-25T12:00:00.000Z","buy":-0.337409,"sell":-1.065,"load":6267,"pv_a":5870,"pv_b":6971}]
|
||||
Reference in New Issue
Block a user