|
18 | 18 |
|
19 | 19 | import math |
20 | 20 | import copy |
| 21 | +from html import escape as escape_html |
21 | 22 | from datetime import datetime, timedelta |
22 | 23 | from config import THIS_VERSION |
23 | 24 | from const import TIME_FORMAT, PREDICT_STEP, EXPORT_LIMIT_FREEZE, EXPORT_LIMIT_IDLE, MINUTE_WATT |
@@ -1069,6 +1070,16 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, |
1069 | 1070 | if self.rate_best_cost_threshold_export: |
1070 | 1071 | export_cost_threshold = self.rate_best_cost_threshold_export |
1071 | 1072 |
|
| 1073 | + def import_rate_color(rate): |
| 1074 | + """Colour an import rate the same way as the plan's own Import p column (blue/green/yellow/red).""" |
| 1075 | + if rate <= 0: |
| 1076 | + return "#74C1FF" |
| 1077 | + elif rate <= import_cost_threshold: |
| 1078 | + return "#3AEE85" |
| 1079 | + elif rate > (import_cost_threshold * 1.5): |
| 1080 | + return "#F18261" |
| 1081 | + return "#FFFFAA" |
| 1082 | + |
1072 | 1083 | raw_plan["import_cost_threshold"] = import_cost_threshold |
1073 | 1084 | raw_plan["export_cost_threshold"] = export_cost_threshold |
1074 | 1085 | raw_plan["reason_templates"] = REASON_TEMPLATES |
@@ -1310,12 +1321,7 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, |
1310 | 1321 | if plan_debug and load_forecast10 > 0.0: |
1311 | 1322 | load_forecast += " (%s)" % (str(load_forecast10)) |
1312 | 1323 |
|
1313 | | - if rate_value_import <= 0: # colour the import rate, blue for negative, then green, yellow and red |
1314 | | - rate_color_import = "#74C1FF" |
1315 | | - elif rate_value_import <= import_cost_threshold: |
1316 | | - rate_color_import = "#3AEE85" |
1317 | | - elif rate_value_import > (import_cost_threshold * 1.5): |
1318 | | - rate_color_import = "#F18261" |
| 1324 | + rate_color_import = import_rate_color(rate_value_import) # blue for negative, then green, yellow and red |
1319 | 1325 |
|
1320 | 1326 | if rate_value_export >= (1.5 * export_cost_threshold): |
1321 | 1327 | rate_color_export = "#F18261" |
@@ -1501,16 +1507,26 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, |
1501 | 1507 | cost_color = "#FFFFFF" |
1502 | 1508 |
|
1503 | 1509 | # Car charging? |
| 1510 | + car_rate = None |
1504 | 1511 | if self.num_cars > 0: |
1505 | 1512 | car_charging_kwh = self.car_charge_slot_kwh(minute_start, minute_end) |
1506 | 1513 | car_total += car_charging_kwh |
1507 | 1514 | if car_charging_kwh > 0.0: |
1508 | 1515 | car_charging_str = str(car_charging_kwh) |
1509 | 1516 | car_color = "FFFF00" |
| 1517 | + car_rate = self.car_charge_slot_rate(minute_start, minute_end) |
1510 | 1518 | else: |
1511 | 1519 | car_charging_str = "⚊" |
1512 | 1520 | car_color = "#FFFFFF" |
1513 | 1521 |
|
| 1522 | + # The car's own rate can diverge from the general household rate once its IOG dispatch |
| 1523 | + # cap is used up for the day - the car falls back to the peak rate while the house keeps |
| 1524 | + # its real (possibly still cheap) rate for the same clock-time (batpred#4646). Split the |
| 1525 | + # Import p cell to show both when that happens. |
| 1526 | + rate_split = car_rate is not None and abs(car_rate - rate_value_import) > 0.01 |
| 1527 | + if rate_split: |
| 1528 | + car_rate_color = import_rate_color(car_rate) |
| 1529 | + |
1514 | 1530 | # iBoost |
1515 | 1531 | iboost_amount_str = "⚊" |
1516 | 1532 | iboost_color = "#FFFFFF" |
@@ -1582,7 +1598,16 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, |
1582 | 1598 | # Table row |
1583 | 1599 | html += '<tr style="color:black">' |
1584 | 1600 | html += "<td id=time bgcolor=#FFFFFF>" + rate_start.strftime("%a %H:%M") + "</td>" |
1585 | | - html += "<td id=import data-minute=" + str(minute) + " data-rate=" + str(rate_value_import) + " " + cell_style + " bgcolor=" + rate_color_import + ">" + str(rate_str_import) + " </td>" |
| 1601 | + if rate_split: |
| 1602 | + house_title = escape_html("House rate: {:.2f}{}/kWh".format(rate_value_import, self.currency_symbols[1]), quote=True) |
| 1603 | + car_title = escape_html("Car rate: {:.2f}{}/kWh (differs from house rate)".format(car_rate, self.currency_symbols[1]), quote=True) |
| 1604 | + html += "<td id=import data-minute=" + str(minute) + " data-rate=" + str(rate_value_import) + ' style="padding:0;">' |
| 1605 | + html += '<div style="display:flex;">' |
| 1606 | + html += '<div style="flex:1;padding:4px;background-color:' + rate_color_import + ';" title="' + house_title + '">' + str(rate_str_import) + "</div>" |
| 1607 | + html += '<div style="flex:1;padding:4px;background-color:' + car_rate_color + ';" title="' + car_title + '">' + "{:.2f}".format(car_rate) + "</div>" |
| 1608 | + html += "</div></td>" |
| 1609 | + else: |
| 1610 | + html += "<td id=import data-minute=" + str(minute) + " data-rate=" + str(rate_value_import) + " " + cell_style + " bgcolor=" + rate_color_import + ">" + str(rate_str_import) + " </td>" |
1586 | 1611 | html += "<td id=export data-minute=" + str(minute) + " data-rate=" + str(rate_value_export) + " " + cell_style + " bgcolor=" + rate_color_export + ">" + str(rate_str_export) + " </td>" |
1587 | 1612 | if start_span: |
1588 | 1613 | if split: # for slots that are both charging and exporting, just output the (split cell) state |
@@ -1689,6 +1714,9 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, |
1689 | 1714 | if self.num_cars > 0: |
1690 | 1715 | json_row["car_charging"] = car_charging_kwh |
1691 | 1716 | json_row["car_color"] = car_color |
| 1717 | + json_row["car_rate"] = car_rate |
| 1718 | + json_row["car_rate_color"] = car_rate_color if rate_split else None |
| 1719 | + json_row["rate_split"] = rate_split |
1692 | 1720 | if self.iboost_enable: |
1693 | 1721 | json_row["iboost"] = iboost_amount |
1694 | 1722 | json_row["iboost_change"] = iboost_change |
|
0 commit comments