Finalise/simplify GB model names; make the trend adjuster a boolean - #379
Finalise/simplify GB model names; make the trend adjuster a boolean#379braddf wants to merge 4 commits into
Conversation
Rename the user-facing GB forecast models to describe their input sets, so they line up across products. Internal DP forecaster names are unchanged. blend blend ecmwf_mo pvnet_day_ahead ecmwf_mo_sat_8h pvnet_v2 ecmwf pvnet_ecmwf sat_8h pvnet_sat_only mo pvnet_ukv_only The trend adjuster moves out of the model name and into an `adjusted` boolean, defaulting to on. Region types declare `supports_adjusted`, surfaced on GET /region-types, so a caller can tell where the param applies without trial and error; where it does not (GB gsp, NL province) it is ignored rather than rejected. Every retired slug still resolves via ForecastModel.aliases / adjust_aliases, unadvertised in /region-types, the OpenAPI enum and 400 messages. An alias pins its own adjuster state, so an existing client keeps the exact forecast it had. The one exception is `blend`, which is both a current and a pre-rename name: it takes the new default and now returns the adjusted blend. Pass adjusted=false for the old behaviour. Also closes a latent hole where an intraday-only caller who omitted `model` could fall back to the unrestricted default — the model naming it explicitly would have returned 403. Unreachable from the current config; the config invariant test now requires an intraday default whenever intraday_models is set.
| def accepts(self, api_name: str) -> bool: | ||
| """True if `api_name` names this model, current or legacy.""" | ||
| return ( | ||
| api_name == self.api_name |
There was a problem hiding this comment.
I think you can do api_name in [self.api_name, *self.aliases, *self.adjust_aliases]. to make it neater
There was a problem hiding this comment.
Yep updated, used a tuple instead of a list so we're not building a throwaway list on every call, but same idea.
Then I actually dropped this accepts() altogether. It only had one caller, and then from tweaks re Suvan's comment, that caller always passed in the region type's own supports_adjusted flag just to get it handed back – so the check now lives in get_model_by_api_name, right next to get_model_by_internal_name, which already reads those fields anyway.
|
What country is this for? |
|
Would anything in the api checks here need to change? |
| label: str | ||
| slug: str | None = None | ||
| adjust_name: str | None = None | ||
| aliases: tuple[str, ...] = () |
There was a problem hiding this comment.
how come you didnt make these lists of strings?
There was a problem hiding this comment.
I'll admit my view of Python dataclasses rules and regs is still mixed, but I spent a bit of time thinking about this config, and there are a couple of things here trying to keep these top Classes tight as poss:
A plain list default won't actualy import – dataclasses reject it: ValueError: mutable default <class 'list'> for field aliases is not allowed: use default_factory. So every alias field would need wrapping in field(default_factory=list), which feels like a bit of shoehorning in comparison to a tuple, which does work.
Secondly, frozen=True only stops you replacing a field, not changing what's inside it. fm.aliases = (...) is blocked, but fm.aliases.append(...) would work fine on a list. COUNTRIES is built once when the module loads and shared by every request, so a stray append anywhere would change model routing for everything that worker handles afterwards... and only that worker, which would be horrible to debug. A tuple has no append, so it can't happen by design, so I'd say this is the pattern we want to adopt
There was a problem hiding this comment.
Thanks, that sounds sensible
peterdudfield
left a comment
There was a problem hiding this comment.
Ive approved,
Ive add a few questions, but nothing major
| location_type = locs[0].location_type or models.LocationType.NATION | ||
| rt = country.location_type_to_region_type(location_type) | ||
| model = resolve_forecast_model(model, rt, is_intraday_only) | ||
| model = resolve_forecast_model(model, rt, is_intraday_only, adjusted) |
There was a problem hiding this comment.
I think before this add validate_model(model_name, rt, rt.type)
There was a problem hiding this comment.
Agreed, added. It was the only forecast route not validating, potentially hitting the DP instead of returning 400, must have missed that one!
| return ( | ||
| api_name == self.api_name | ||
| or api_name in self.aliases | ||
| or api_name in self.adjust_aliases |
There was a problem hiding this comment.
I think here if someone tries to get GSP with blend_adjust this will allow it, check this behavior against the old object check once
There was a problem hiding this comment.
Amended! Now checks for these "non-existent" regional models if it's an adjust_alias and returns false if they shouldn't be allowed. Good catch 👍
suvanbanerjee
left a comment
There was a problem hiding this comment.
Couple of comments from my side but overall looks solid!
Reject `_adjust` aliases where no adjusted variant exists. GB gsp and NL province have none, so `?model=blend_adjust` was matching the alias and then resolving to plain `blend` — accepting the name and quietly serving something else. It 400s again, as it does on main. Validate the model on /forecast/last-updated, the one forecast route that passed an unrequested name straight to the DP. Also fold the three-way `or` in ForecastModel.accepts into a tuple membership test.
The method had one caller, which always passed the region type's own supports_adjusted and got it handed straight back — a boolean parameter carrying no information the caller didn't already have. Matching now happens where that policy lives, alongside get_model_by_internal_name, which already reads the same fields directly.
Both GB and NL national – thanks, the description said just GB but this does apply to NL going forwards too, even if it's backward compatible for both countries for other model names. |
Had a look – nothing needs to change. Separate thing I noticed while I was in there, in the DAGs repo rather than this one – |
|
cc. @peterdudfield in case you miss these comments ☝️ |
Sounds good, do you mind adding the link of that issue here, just so it links back to this comment? |
|
@peterdudfield added link to my reply and also added issue for check here: https://github.com/openclimatefix/airflow-dags/issues/806 |
| label="PV + ECMWF + Met Office + Satellite, Uncurtailed (Trend Adjusted)", | ||
| slug="ecmwf_mo_sat_uncurtailed_adjust", | ||
| adjust_name="nl_regional_pv_ecmwf_mo_sat_uncurtailed_adjust", | ||
| adjust_aliases=("ecmwf_mo_sat_uncurtailed_adjust",), |
There was a problem hiding this comment.
change slug to ="pv_ecmwf_mo_sat_uncurtailed_adjust"
aliases = "ecmwf_mo_sat_uncurtailed",
The slug and the alias were the wrong way round: the old name `ecmwf_mo_sat_uncurtailed` was still advertised and still picked up the new adjusted-by-default, while the new PV-inclusive name was hidden as a pinned alias no client had ever used. Swapped, so `ecmwf_mo_pv_sat_uncurtailed` is the advertised name and the old one is a pinned alias returning exactly what it did on main. `blend` is now the only name whose behaviour changes in this PR.
Pull Request
Description
Rename the user-facing GB forecast models to describe their input sets rather than internal model names, so they line up across products, and move the trend adjuster out of the model name into an
adjustedboolean. Internal DP forecaster names are unchanged — this is a naming and parameter change only.modelblendblendblend_adjustecmwf_mopvnet_day_aheadpvnet_day_ahead,pvnet_day_ahead_adjustecmwf_mo_sat_8hpvnet_v2pvnet_intraday,pvnet_intraday_adjustecmwfpvnet_ecmwfpvnet_ecmwf,pvnet_ecmwf_adjustsat_8hpvnet_sat_onlypvnet_sat,pvnet_sat_adjustmopvnet_ukv_onlypvnet_ukv,pvnet_ukv_adjustNational exposes all six; GSP exposes
blend,ecmwf_mo_sat_8handecmwf_mo.adjustedboolean, defaulting to on —?adjusted=true|falseon/regions/{id}/forecast,/forecast/last-updatedand/forecasts/snapshot. It is orthogonal to model choice and every adjustable model has an adjusted variant, so defaulting it on means callers get the best available forecast without knowing to ask. Leaves room to become a select later, or to disappear if the adjustment moves into the model pipeline.Not every region type has adjusted variants — GB
gspand NLprovincehave none. There the param is ignored rather than rejected: a 400 would force a caller sweeping several region types to special-case GSP.GET /region-typesnow returnssupports_adjustedper type so that stays discoverable instead of silent.model_namein responses no longer carries an_adjustsuffix, matching v0, which stripped it before returning it too.Scope
adjustedparam and the alias mechanism for free./forecasts/periodor/generation/period— those are cache-only and serve the pre-warmed default, and adding the param would mean warming two variants per region.Backwards compatibility
The retired names in the table above are carried on
ForecastModel.aliases/adjust_aliasesand stay out of/region-types, the OpenAPI enum and 400 messages, so existing integrations keep working while new ones only ever see the current names. An alias pins its own adjuster state — the_adjustones pin it on, the rest pin it off — so an old name returns byte-for-byte what it did before.blendis both a current name and a pre-rename name, so it cannot carry an alias override: it takes the newadjusted=truedefault and now returns the adjusted blend where it previously returned the plain one.?adjusted=falserestores the old result. Our one external tester is onblend, so this wants a heads-up before it ships.Also included
An intraday-only caller who omitted
modelcould fall back to the unrestricted default — the model that naming it explicitly would have returned 403 for. Unreachable from the current config, but the fallback now stays within the intraday set, and the config invariant test requires an intraday default wheneverintraday_modelsis set.Checklist: