* feat(core): say why an elevation left nothing to answer with
A station whose …own height is unknown is turned away from a quantity that
falls with height, and thirteen providers have such stations -- every one
of FMI's, IPMA's and the Environment Agency's among them. Ask FMI for an
interpolated temperature at 50 m and every station in reach is left out:
the answer was an empty frame, a 200, and the reason in a server-side
log.info -- the one place the caller cannot look.
A parameter emptied that way is not "no data for those dates". It is a
question that cannot be answered as asked, and the caller can fix it by
not asking about a height. So the parameters that lost their stations are
collected as the stations go by, and at the end:
no station of known height is in reach, so there is no answer at 50.0 m
for hourly/data/temperature_air_mean_2m. Ask without an elevation to
take each station's readings as they came, or use a provider that
publishes the heights of its stations.
Raised where nothing is left, since there is no result for a warning to be
read against; logged as a warning where another parameter still answered,
the rest of the result standing. A station turned away where three others
were taken says nothing at all -- it cost the answer nothing.
The REST API reports it as a 400 rather than the catch-all 404, which
reads as "no such thing" for a request that was understood. Both geo
endpoints decide that from one place now, so /api/summarize answers a
window that ends before it starts with a 400 as /api/interpolate already
did. The CLI prints the message without a traceback, and the app needs no
change: DataViewer already puts a 400's detail in the error toast.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): judge an elevation's cost on the answer, not on the stations collected
Whether a parameter was answered was read off the columns collected for it,
which is a summary's question rather than an interpolation's. A summary
answers from one station, so a column is an answer; an interpolation wants
four of known height that surround the point, so a parameter the exclusions
leave with three holds columns and still comes back all null. That case was
filtered out of the report and went back to the caller as the silent empty
result this set out to do away with -- and the same test, asked the other
way round, let a parameter of nothing but nulls stand in for a result and
downgrade a refusal to a log line.
The finished frame knows. The report moved after the calculation and asks
it: a parameter with a non-null value somewhere was answered, and every
parameter that lost a station and has none was not.
Two more from the same review:
The walk down the ranking could stop before reaching a station that had a
height. A parameter every station so far was turned away from never opens
an entry, so `all(finished)` passed over it and the parameters that did
open ended the walk -- reporting it as unanswerable while a station further
out, inside the radius, could have answered it. The condition is a named
function now, `collection_is_done`, and a parameter that has yet to take a
station keeps the walk going.
And the remedy the message named was not available on the station-id path,
where the elevation is the station's own height and never asked for: there
is no value of `elevation` through `interpolate_by_station_id`, `--station`
or `?station=` that means "no height". It says what to ask instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): blame the heights only for what they did, and stop waiting where nothing is coming
Two from the second review.
The refusal fired whenever nothing was answered and any parameter had lost a
station, without asking whether the loss was the reason. One height-less
station among six leaves five that can be brought to the target, and if the
answer is still null it is the geometry of where those five stand or the data
they hold -- so the caller was told to ask without an elevation, which returns
the same nulls, and a frame they used to get became a 400. A parameter is
reported now only where it kept fewer stations than its calculation asks for:
four surrounding the point for an interpolation, one for a summary.
And a parameter that lost a station held the walk down the ranking open even
where no station in reach reports a height at all -- FMI, IPMA and the
Environment Agency publish none -- so the walk could never end early and had
nothing to gain by going on. It is asked once, off the ranked frame, before
the walk starts. Measured on a two-parameter FMI request the walk covers 11
stations either way, the radius rather than the provider's 441 stations being
what bounds it, so this is a cheap guard against a bounded cost rather than
the rescue it first looked like.
Both are tested, and both tests fail with the guards removed. The exception
is documented where a caller meets it: `interpolate`, `summarize` and their
by-station-id forms, which say what to ask instead, having no elevation to
leave out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* perf(core): count what each parameter has in reach before downloading anything
Three from the third review, and the first two come to one place: the walk
knew nothing about what it was walking towards.
The short-circuit added last round only worked for a request that had
already collected something, since `bool(param_dict)` gates the whole
condition -- and a single-parameter request against a provider that
publishes no heights collects nothing, so the very shape the docstring
named still downloaded the whole ranking to say so. And the answer it
gave was global where the radius is per parameter: temperature reaching
20 km with no height inside it, precipitation reaching 40 with one at 35,
and the walk held open for a parameter whose own reach holds nothing.
Both are answered by counting off the ranked frame before the first
download: per parameter, inside its own radius, how many stations there
are and how many report a height. Where none does and every parameter
asked for falls with height, the request returns without fetching a single
station -- a test asserts the count of downloads is zero. Where only some
parameters are in that position, they no longer hold the walk open.
The same counts fix the third: `kept < stations_needed` could not tell a
parameter the exclusions took below the line from one that was never going
to reach it. Three stations in the whole radius and one without a height
leaves two, and an interpolation wants four -- but keeping all three would
still have been one short, so the heights are not what emptied it and the
caller is not sent back for the same nulls. It is reported only where
enough stations were in reach to have answered: `kept < needed <= total`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): weigh what the exclusions took, not what stood in the radius
Three from the fourth review.
The test for "are the heights to blame" counted stations in the metadata
index inside the radius, which is not the same as stations that hold the
parameter in the window asked for. Six in reach and two carrying the
parameter, one of them without a height: the caller was told to ask again
without an elevation, and got the same two stations and the same nulls.
The tally is a count again -- stations that held the parameter and were
turned away -- and the exclusions are blamed only where giving them back
would have made up what the calculation needs.
The counts read whichever row of a station sorted last, where the walk
reads the nearest: the ranking carries a row per station and dataset, two
dataset indexes can disagree about a height, and the disagreement decided
whether a request was refused before anything was downloaded.
And a parameter whose one station of known height turns out to hold no
data kept the walk open to the end of the ranking. The counts carry how
far out the last station with a height stands, and the walk goes outwards,
so past that distance the parameter has nothing coming and stops waiting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): let the refusal that costs no download make its own claim
The short-circuit that refuses before downloading anything reported itself
through the count the walk uses, synthesising one from the stations in the
radius -- which is the comparison the last round removed, for the reason it
removed it: stations in the index are not stations that hold the parameter.
Thirty in reach and none carrying it in the window, and the caller was told
the heights were why, and sent back to ask again for the same empty frame.
It is a different claim and it deserves its own words. Not one station near
the point says how high it stands, so nothing can be brought to the height
asked about -- true of the request whatever those stations hold, which is
exactly why it can be said without downloading them. The remedy is shared
between the two messages; the diagnosis is not.
The docstring of the other one claimed more than it does: a parameter that
kept four stations standing together on one side of the point interpolates
to nothing, and the exclusion that left the surrounding ones out really was
the cause. Telling that apart wants a hull test per parameter, and a wrong
accusation costs the caller more than a silence, so it says which mistake
it is making.
And the test for the partial case asserted on rows rather than on values,
where a parameter with a station collected for it has rows either way -- so
"the other parameter still answered" was not established before the warning
was looked for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): refuse only what the counting can be sure of
Three from the sixth review.
A point with no station in reach at all -- a mistyped coordinate, or
anywhere outside the provider's network -- was refused for missing
heights, since "no station reports one" is also true of no stations. The
caller was sent off to drop an elevation that was never the trouble, for
an empty frame either way. The refusal is a claim about heights, so it is
made only where there were heights to miss, and where enough stations
stood in reach for their heights to have mattered: `StationsInReach.total`
earns its keep, having until now been collected and never read.
And the refusal itself is narrowed to what a count can be sure of. A
parameter that took not one station is the whole story: nothing to
interpolate from, nothing left to wonder about. One that kept three and
lost one is an inference -- those four may be four that would never have
surrounded the point -- so it is named in the log and the caller keeps
their frame. The docstring says which of the two mistakes it is making,
in both directions now.
The docs said the pre-download refusal applies wherever no station
reports a height. It applies where every quantity asked for falls with
height, one that does not being answerable from those same stations; a
mixed request fetches for the second and names the first once the answer
is in. Said plainly rather than left to be discovered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): count only what the walk would collect, and name every loss
Four from the seventh review.
A parameter that cannot be interpolated at all was counted alongside the
rest, and it can never be unanswerable for want of a height -- so its mere
presence left the counted set larger than the unanswerable one for ever,
and the refusal that asks whether those two are the same was never reached.
Ask for `precipitation_form` beside a temperature at an elevation, against
a provider that publishes no heights, and every station inside the radius
was fetched to arrive at the answer the station list already held. It is
`daily/kl` that has such a parameter, so a whole-dataset request is the
common shape of this.
On the way to the refusal the warning branch is passed over, so a parameter
emptied by the same exclusions that kept a station or two went unmentioned
altogether: the exception speaks only for what is certain, and the rest is
said in the log beside it.
The ranking was filtered twice per parameter, once for the total and once
for the heights, two copies of one predicate free to drift apart.
And the paragraph in the docs had come to say the same thing three times
over, in two styles of dash.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): weigh a station near enough to answer alone as the one it is
Three from the eighth review; the fourth does not hold.
An interpolation answers from a single station standing inside the
nearby-station distance, without the four a hull wants around the point.
Held to four, a caller standing on top of the only station in reach -- and
it without a height -- was told nothing at all, though that station is
exactly what naming an elevation cost them. The tally carries how near the
nearest station it turned away stood, and where that is near enough to have
answered alone, one station is what the exclusions cost.
The counting filtered the ranking by distance alone, though it carries a
row per station *and* dataset. A station standing in the hourly index and
reporting a height there said there was a height in reach for a daily
parameter it can never answer -- holding the walk open out to its distance
and suppressing the refusal that costs no download.
And a warning told a caller whose every parameter came back null that the
rest of their result stands. There is no rest of it.
The fourth was that holding the walk open grows the station set, and with
it the group enumeration that runs over it. It does not: a parameter that
is finished takes no column (interpolate.py:237), so no station joins the
set during the extra steps -- the only station that can is the first one
the held-open parameter takes, which is the whole reason for waiting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): count a station's height as the walk will read it
Three from the ninth review, and the first is the other half of the last
round's.
Counting per dataset fixed which stations can answer a parameter and broke
what they report: the walk keeps one row per station, the nearest over
every dataset in the ranking, and reads the height off that. Two indexes
can disagree -- a height in one, none in the other -- and the count, taking
the requested dataset's row, could find no height in reach and refuse
before downloading a request the walk would have answered from that very
station.
They are two questions and they are asked separately now. Which stations
hold the dataset comes from the dataset's own rows; what those stations
report comes from the row the walk will read.
The nearby-station distance is compared as `calculate_interpolation`
compares it, strictly: a station standing at exactly the distance is not
one the interpolation would have answered from alone, so an exclusion that
took it away did not cost the answer a station that could stand in for
four.
And the two comment paragraphs describing `stations_by_id` had been left
stranded above the block inserted before it, where they read as an account
of the counting -- which is the one thing they are not true of.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
* fix(core): ask whether a nearby station was lost, not how many were
Two from the tenth review, and the first was a fix that did not fire.
Losing the station standing under the point was written as a smaller
number of stations to want -- one instead of four -- but the test around it
still asks whether fewer were kept than are wanted, and three kept is not
fewer than one. So the case the shortcut exists for went unmentioned
exactly when it mattered: the near station dropped for its height, three
farther ones kept, no hull to close around the point, and a null column
with nothing said about it. It is a different question and it is asked as
one now -- was a station near enough to have answered alone taken away --
however many farther ones were kept in its place.
And what the ranking proves before any download was thrown away unless it
covered the whole request. A parameter with no station of known height
anywhere inside its radius has nothing for the walk to wait for, so the
walk stops as soon as the other parameters are done, and the few drops it
managed by then were counted against the four a hull wants and found
wanting. The proof travels with the walk now, and a parameter it covers is
named whatever the walk had time to see.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMmz23eRedUH9VTYaMQzV
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>