Skip to content

Ask a time-series model for a forecast or an anomaly score - #35

Draft
Manushpm8 wants to merge 2 commits into
mainfrom
kumo-timeseries-pql
Draft

Manushpm8 wants to merge 2 commits into
mainfrom
kumo-timeseries-pql

Conversation

@Manushpm8

Copy link
Copy Markdown
Collaborator

Adds the language layer for two time-series models, kumo-forecast and kumo-anomaly, agreed in
the proposal doc. Draft: the models do not exist in the serving library yet, so nothing here has
run against an endpoint. Everything in the diff is unit-testable without one, and that is the whole
scope: no model handles, no adapters, no wire shaping.

The two queries

PREDICT readings.value FORECAST 72 TIMEFRAMES FOR EACH readings.reading_id
PREDICT readings.*     DETECT ANOMALY         FOR EACH readings.reading_id

FORECAST takes a bare column, so a prepared series is forecast as the model performs it rather
than as a degenerate aggregation. The endpoint counts the horizon in rows and cannot resample, so an
aggregation would be either inert or a resample it cannot honour.

DETECT ANOMALY sits after the target, where RANK, CLASSIFY and FORECAST already sit, so a
problem type is never encoded in the target expression. The target is a wildcard because the score
is joint: the model takes the mean absolute reconstruction error across every channel and thresholds
on all of them, so naming one column would promise a score specific to it that the model never
produces.

Capabilities

Two flags, series_forecasting and anomaly_detection, set explicitly on all four profiles because
Capabilities defaults every flag to permitting.

TIMESERIES and ANOMALY were declared as a bare Capabilities(...) and so permitted entity
filters, counterfactuals and derived targets by omission. They are now declared flag by flag, and
test_profiles reads the flag list off the dataclass, so a flag added and left out of a matrix
fails rather than reaching a profile on its permissive default.

Three things found on the way

  • The relational engine ran only the resolved capability check, never the parsed one, so a refusal
    needing nothing but the parse tree never reached it. It runs both now, which is what refuses a
    bare-column forecast there.
  • error_translator keys off ANTLR state numbers. Both moved by two, silently turning two friendly
    syntax errors back into raw parser output, and nothing in this package failed when they did. The
    numbers are re-mapped and the two messages are pinned by a test.
  • The wildcard rule permits table.* only inside COUNT, so validate refused every anomaly query
    before the new checks ran. An anomaly target is exempt; a wildcard anywhere else is refused as
    before.

Open, and why it is a draft

The language cannot yet express a series. Two halves, both open.

Identity: FOR EACH sensors.sensor_id does not resolve, for the forecast and the anomaly query
alike. A sensor has many readings, so a bare column is not one value per sensor and resolution
refuses it with PQL_JOIN_AMBIGUOUS_TARGET_VALUE. The aggregation form used to collapse the many to
one and a bare column has nothing in its place. Naming the row resolves, but the query then no
longer says which series is being forecast. A derived entity table, the answer discussed on the
proposal, would also need TIMESERIES to permit a second table and a relationship, which it does
not. A test records the failure rather than hiding it.

Ordering: nothing requires the table to have a time column, so a bare-column forecast validates
against a table with no notion of order at all. The accepted form proves a row-level numerical
prediction parses, not that a series can be forecast.

An anomaly target has no honest type. Wildcards only ever existed under COUNT, so the type
validator gives every wildcard int and categorical. A validated anomaly query therefore reports
a categorical int target, where the model produces a numerical score. Nothing consumes it yet.

Two gaps this PR surfaces without fixing, both predating it:

  • KumoRelational accepts an already-validated query and returns it unchanged, so a query validated
    under one profile can enter the engine under another.
  • check_capabilities tests num_forecasts > 1 rather than the problem type, so a one-step
    forecast slips past forecasting=False.

Neither is reachable from a shipping model today, and TIMESERIES and ANOMALY are still
referenced by no model handle. Both profiles also accept a query carrying neither FORECAST nor
DETECT ANOMALY, which has to be settled before either is wired to one: a profile today can only
reject a clause, not require one.

Manushpm8 and others added 2 commits October 13, 2026 13:54
`FORECAST` takes a bare column, so a prepared series is forecast as the
model performs it rather than as a degenerate aggregation, and
`DETECT ANOMALY` joins `RANK`, `CLASSIFY` and `FORECAST` after the target
so a problem type is never encoded in the target expression.

Two capabilities carry them, and the time-series profiles are declared
flag by flag: they were written as a bare `Capabilities(...)` and so
permitted filters, counterfactuals and derived targets by omission.
`test_profiles` now reads the flag list off the dataclass, so a flag
added and left out of a matrix fails rather than reaching a profile on
its permissive default.

The relational engine ran only the resolved capability check, so a
refusal that needs nothing but the parse tree never reached it. It runs
both now, which is what refuses a bare-column forecast there.

`error_translator` keys off ANTLR state numbers. Both moved by two, and
nothing in this package failed when they did, so the two messages that
depend on them are pinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Manush Murali <manushm@nvidia.com>
The wildcard rule permits `table.*` only inside `COUNT`, so `validate`
refused every anomaly query before its own checks ran. The checks passed
on their own, which is why nothing failed: they were being called
directly rather than through the entry point a caller reaches. The
target of a `DETECT ANOMALY` query is exempt; a wildcard anywhere else
is refused as before.

`to_string` rendered the problem type as its enum value for everything
but `FORECAST`, so an anomaly query was written back out as `ANOMALY`,
which does not parse. Materializers persist what it returns.

`TaskType.ANOMALY_DETECTION` is dropped. Nothing reads it, no task
family claims it, and the serving path that would give it meaning does
not exist yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Manush Murali <manushm@nvidia.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant