File size: 4,784 Bytes
5d7f266
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""The three small JSON files that decide WHAT is published, and read them defensively.

publish_gate.json   (checked in; written by build_gate.py from the research backtest)   zone -> the horizons that may be published
accuracy_eu.json    (checked in; same script)                                           zone -> what the forecast is worth, per horizon
calibration_eu.json (written by calibrate_bands.py, refreshed monthly)                  zone -> the P10 / P90 band offsets

A file that is missing, unreadable or of another `version` is treated as ABSENT, and absent never means "publish anyway": without the gate
nothing is published, without the accuracy block nothing is published (a forecast without its honest numbers is not shown), without the
calibration the days are published without a band. A file in the first build's format (no `version`) is therefore harmless.
"""
import json
import os

VERSION = 2
LOW_GAIN = 0.10          # an improvement below this (in the whole backtest, the hold-out or since September 2026) is low confidence
MIN_GAIN_VS_BEST_NAIVE = 0.05


def dumps_compact(obj, level=0):
    """JSON for files people review in a diff: a dict or list that holds only scalars (or lists of scalars) is one line, anything else is
    indented. Same content as json.dumps(obj, indent=1), a tenth of the lines."""
    scalar = lambda v: not isinstance(v, (dict, list))
    flat = lambda v: all(scalar(x) or (isinstance(x, list) and all(scalar(y) for y in x)) for x in (v.values() if isinstance(v, dict) else v))
    if isinstance(obj, list) and all(scalar(x) for x in obj):
        return json.dumps(obj)
    if isinstance(obj, dict) and obj and flat(obj) and level >= 2:
        return json.dumps(obj)
    pad = " " * (level + 1)
    if isinstance(obj, dict):
        return "{\n" + ",\n".join(f"{pad}{json.dumps(k)}: {dumps_compact(v, level + 1)}" for k, v in obj.items()) + "\n" + " " * level + "}"
    if isinstance(obj, list):
        return "[\n" + ",\n".join(pad + dumps_compact(v, level + 1) for v in obj) + "\n" + " " * level + "]"
    return json.dumps(obj)


def _load(path, version=VERSION):
    try:
        with open(path, encoding="utf-8") as f:
            v = json.load(f)
    except (OSError, ValueError):
        return None
    return v if isinstance(v, dict) and v.get("version") == version else None


def load_gate(path):
    """{zone: {"recipe": 'ens'|'lgb', "fuel": [h...], "nofuel": [h...]}} or None."""
    g = _load(path)
    z = g.get("zones") if g else None
    return z if isinstance(z, dict) and z else None


def horizons(gate, zone, fuel_ok):
    """The set of horizons the gate lets this zone publish, in the fuel mode the run is in. A zone the gate does not list publishes nothing."""
    e = (gate or {}).get(zone)
    if not isinstance(e, dict):
        return set()
    return {int(h) for h in e.get("fuel" if fuel_ok else "nofuel", [])}


def load_accuracy(path):
    return _load(path)


def accuracy_block(acc, zone, fuel_ok, hs):
    """The `accuracy` object of the published payload for the horizons `hs` actually published, or None when the file has no numbers for
    one of them (the whole zone is then not published: see published_horizons)."""
    z = ((acc or {}).get("zones") or {}).get(zone)
    if not z:
        return None
    cells = z.get("fuel" if fuel_ok else "nofuel") or {}
    by = {str(h): cells[str(h)] for h in sorted(hs) if str(h) in cells}
    if not by:
        return None
    return {"version": VERSION, "period": acc.get("period"), "baseline": acc.get("baseline"), "recent": acc.get("recent"),
            "fuel": bool(fuel_ok) and z.get("recipe") != "lgb", "showEur": bool(z.get("showEur")), "byHorizon": by}


def published_horizons(gate, acc, zone, fuel_ok):
    """The horizons to publish: those the gate allows AND for which the accuracy file has numbers (an unmeasured horizon is not published)."""
    cells = (((acc or {}).get("zones") or {}).get(zone) or {}).get("fuel" if fuel_ok else "nofuel") or {}
    return {h for h in horizons(gate, zone, fuel_ok) if str(h) in cells}


def load_calibration(path):
    """{"quantile": {zone: {h: {lo, hi}}}, "residual": {...}} or None."""
    c = _load(path)
    if not c:
        return None
    return {k: (c.get(k) if isinstance(c.get(k), dict) else {}) for k in ("quantile", "residual")}


def offsets(cal, kind, zone, h):
    """{'lo','hi'} for a zone and horizon, or None. A horizon without its own entry borrows the nearest horizon of the same zone (the cells
    are thin only at the edges of the horizon range), a zone without any returns None."""
    z = ((cal or {}).get(kind) or {}).get(zone) or {}
    if str(h) in z:
        return z[str(h)]
    if not z:
        return None
    return z[min(z, key=lambda k: abs(int(k) - h))]