Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .github/workflows/publish_package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ name: Publish Package
# (create a Release from a vX.Y.Z tag). Auth is PyPI Trusted Publishing (OIDC) —
# no stored API token. See docs/guides/publishing-to-pypi.md.
#
# One-time PyPI setup (project Settings -> Publishing -> add a trusted publisher,
# or a *pending* publisher for the first-ever release before the project exists):
# One-time PyPI setup (already done for this project — kept for reference when
# bootstrapping a NEW package: a *pending* publisher covers the first-ever release):
# Publisher: GitHub | Owner: views-platform | Repo: views-baseline
# Workflow: publish_package.yml | Environment: (leave blank)
#
# NOTE: views-baseline depends on views-pipeline-core (>=3.0.0), which is not yet
# on PyPI — `pip install views-baseline` will not resolve until that dependency is
# published. Uploading here still succeeds and reserves the name/version.
# NOTE: views-pipeline-core (>=3.0.0) reached PyPI on 2026-08-03 and is now at 3.2.0,
# so `pip install views-baseline` resolves. 1.0.0 and 1.0.1 were published while it did
# not, and were installable in name only. 1.0.2 is therefore the FIRST release whose
# clean-room install-back can actually be exercised — do it (docs/guides/publishing-to-pypi.md
# §A). This note said the opposite until 2026-09-09; it was the third copy of that claim
# and the one the doc sweep missed.

on:
release:
Expand Down
43 changes: 35 additions & 8 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ name: Lint and Test

# uv-based CI (mirrors the views-frames / views-datafactory / views-reporting pattern).
#
# NOTE: `uv sync` cannot currently resolve the dependency graph because
# views-pipeline-core (>=3.0.0) is not on PyPI yet, so this workflow is expected to
# be red until that dependency is published. The editable `views_pipeline` conda env
# is the working test gate meanwhile (README / CONTRIBUTING).
# This workflow RUNS and passes again as of 2026-09-09 (issue #90). It is NOT yet a
# blocking gate: neither `main` nor `development` declares required status checks
# (verified via the branch-protection API), so a red run here still does not stop a
# merge. Turning these into required checks is a repository-settings change for the
# maintainer — worth doing now that the job is reliably green, and tracked in C-49.
# If you enable it, use the CURRENT job name `check (3.11)`; the pre-2026-09-09 runs
# report `check (3.12)`/`check (3.13)`, which no longer exist and would block every PR.
#
# History, so the next person does not re-diagnose it: it was red from 2026-08-02 and the
# note here blamed views-pipeline-core not being on PyPI. pipeline-core 3.0.0 published on
# 2026-08-03 and the workflow was never re-run, so the note went stale and the real cause
# went unseen for five weeks — which is also how the #84 outage survived, since nothing was
# gating merges. The actual blocker was different and would have outlived the publish:
# uv resolves a universal lockfile including win32, and
# pipeline-core -> viewser -> docker<6 -> pywin32==227 has no wheels above cp39. Fixed by
# `[tool.uv] environments = ["sys_platform == 'linux'"]` in pyproject.toml.

on:
push:
Expand All @@ -19,9 +31,8 @@ on:
workflow_dispatch:

jobs:
# Lint runs project-independently (uvx), so it stays a reliable green signal even
# while `uv sync` cannot resolve views-pipeline-core (see the header note) — the
# `check` job below is gated behind that resolution and is expected red until then.
# Lint runs project-independently (uvx), so it does not depend on the project
# resolving. Kept separate for that reason, though `check` now resolves too.
lint:
runs-on: ubuntu-latest
steps:
Expand All @@ -34,7 +45,23 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
# 3.11 only, and not by preference (issue #95).
#
# views-pipeline-core -> viewser 6.6.4 -> `pandas<2.0.0,>=1.4.0`, and pandas 1.5.3
# (the last 1.x) publishes wheels for cp38-cp311 only. On 3.12/3.13 the resolver
# must build pandas from source, which fails outright:
# ModuleNotFoundError: No module named 'pkg_resources'
# So views-baseline is not installable on 3.12/3.13 at all — a transitive
# constraint two hops upstream, not something this repo can fix, and true long
# before this matrix was written.
#
# `pyproject.toml` still declares `requires-python = ">=3.11,<3.14"` and 3.12/3.13
# classifiers. That claim is currently false; it is tracked in the register (C-47)
# rather than quietly narrowed here, because what the package claims to support is
# a distribution decision, not a CI one.
#
# Restore the full matrix when viewser drops the `pandas<2` pin.
python-version: ["3.11"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Key characteristics:
Input DataFrame:

* Must be indexed by `(time, entity)` as a MultiIndex
* Must contain target columns specified in `config['targets']`
* Must contain target columns specified in `config['regression_targets']` (the `targets` key was retired upstream in views-pipeline-core #380 and is now *rejected* — do not add it back)

Example index:

Expand Down
22 changes: 13 additions & 9 deletions docs/ADRs/009_boundary_contracts_and_configuration_validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ Each module boundary is documented with an explicit contract. Where validation e
`MODEL_GENOMES` encodes the required keys per model:

```python
MODEL_GENOMES = {
"ZeroModel": [],
"LocfModel": [],
"AverageModel": ["window_months"],
"ConflictologyModel": ["window_months", "n_samples"],
"MixtureBaseline": ["window_months", "lambda_mix", "n_samples"],
CORE_GENOME = ["steps", "time_steps", "prediction_format", "regression_targets", "level"]

MODEL_GENOMES = { # alias of ReproducibilityGate.Config.ALGORITHM_GENOMES
"ZeroModel": [],
"LocfModel": [],
"AverageModel": ["window_months"],
"ConflictologyModel": ["window_months", "n_samples", "seed"],
"MixtureBaseline": ["window_months", "lambda_mix", "n_samples", "seed"],
"ParametricConflictology": ["window_months", "n_samples", "seed", "family", "transform"],
"ParametricHurdleConflictology": ["window_months", "n_samples", "seed", "family", "transform"],
}
```

Expand All @@ -50,7 +54,7 @@ All model-specific parameters are required and must be explicitly declared in th
**What is not validated:**

- Value ranges: `window_months=0`, `window_months=-1`, `lambda_mix=2.0`, `n_samples=0` all pass validation.
- `targets` is required by all models but is not listed in `MODEL_GENOMES` (it is assumed to always be present). If `targets` is missing from config, construction of the model object will raise a `KeyError` from within the factory method, not from the validation check.
- ~~`targets` is required by all models but is not listed in `MODEL_GENOMES`~~ — **closed 2026-09-09 (issue #85).** This bullet is retained rather than deleted, because what it recorded is worth remembering: the gap was written down as accepted debt in March 2026, and it is exactly what the August outage was made of. views-pipeline-core retired the synthesised `targets` key on 2026-08-02 (#380); the catalog kept reading it; every baseline model raised the predicted `KeyError` from inside the factory method for five weeks, with the audit reporting success one line earlier. The catalog now reads `regression_targets`, and both it and `level` are declared in `CORE_GENOME`, so absence is a `MissingHyperparameterError` naming the key at the boundary. **Accepted debt in a boundary contract is a prediction, not an observation.**
- `partition_dict` is passed directly to model constructors without any structural check. The expected structure is `{"test": (start, end)}` where `start` and `end` are integer month IDs, but this is not enforced.

---
Expand All @@ -63,7 +67,7 @@ All model-specific parameters are required and must be explicitly declared in th

Each factory method maps config keys to constructor parameters. The mapping is explicit and readable:

- `config["targets"]` → `targets=`
- `config["regression_targets"]` → `targets=` (derived **once** in `__init__` as `self.targets`, not re-read at each of the seven factory sites, so a future rename of the platform's target vocabulary is one edit rather than seven)
- `config["window_months"]` → `window_months=` (for `AverageModel`, `ConflictologyModel`, and `MixtureBaseline`)
- `config["lambda_mix"]` → `lambda_mix=` (for `MixtureBaseline`)
- `config["n_samples"]` → `n_samples=` (for `ConflictologyModel` and `MixtureBaseline`)
Expand Down Expand Up @@ -170,7 +174,7 @@ from views_pipeline_core.data.prediction_frame import PredictionFrame

| Boundary | What is missing |
|---|---|
| Config → Catalog | No validation that `targets` is present; no value-range validation for numeric params |
| Config → Catalog | No value-range validation for numeric params. (Target/level presence: **closed** — `regression_targets` and `level` are in `CORE_GENOME` since #85.) |
| Catalog → Model | No constructor-level range checks; no type coercion |
| Model → Pipeline | No validation of distributional output structure or shape |
| Data → Model | No MultiIndex structure validation; no column presence check; no time-range sanity check |
Expand Down
2 changes: 1 addition & 1 deletion docs/ADRs/012_model_addition_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ This ADR serves as the authoritative checklist for adding a new model.
**File:** `views_baseline/model/catalog.py`

1. Add an import for the new class at the top of the file.
2. Add an entry to `MODEL_GENOMES` listing required config keys (beyond `targets`). For a **stochastic** model this MUST include `seed` — it is a required, audited genome key with no magic default (ADR-021). For a **parametric-family** model (ADR-022) it MUST also include `family` and `transform`; the constructor is responsible for failing loud on an unsupported family or an illegal `family×transform` combination.
2. Add an entry to `MODEL_GENOMES` listing required config keys (beyond the `CORE_GENOME` keys, which now include `regression_targets` and `level`). For a **stochastic** model this MUST include `seed` — it is a required, audited genome key with no magic default (ADR-021). For a **parametric-family** model (ADR-022) it MUST also include `family` and `transform`; the constructor is responsible for failing loud on an unsupported family or an illegal `family×transform` combination.
3. Add a factory method `_get_<model_name>()` that reads config and constructs the model.
4. Add the model name → factory method mapping in `self.models` inside `__init__`.

Expand Down
26 changes: 20 additions & 6 deletions docs/CICs/BaselineForecastingModelManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ All models now return `dict[str, PredictionFrame]` from `predict()`. The manager
| Source | What is consumed | Notes |
|---|---|---|
| `model_path` (constructor) | `ModelPathManager` instance | Provides `.artifacts` path. `.data_raw` is no longer accessed directly (data path comes from base class `_cached_data_path`). |
| `self.config` | `dict` | Set via property inherited from base class. Must contain `"run_type"`, `"level"`, `"algorithm"`, `"targets"`. |
| `self.config` | `dict` | Set via property inherited from base class. Must contain `"run_type"`, `"algorithm"`, and every `CORE_GENOME` key — `"steps"`, `"time_steps"`, `"prediction_format"`, `"regression_targets"`, `"level"` — all audited by `audit_manifest` before the catalog is built. (`targets` was retired upstream in pipeline-core #380; see #85. `run_type` is read one line after the audit but is NOT audited — it is injected at runtime, not declared in a config file; see C-48.) |
| `_data_loader.partition_dict` | `dict` | Must contain `"test"` key. |
| `_config_manager` | `ConfigurationManager` | Used by base class and accessed directly in `_evaluate_model_artifact` and `_forecast_model_artifact` via `add_config()` to persist the artifact timestamp. |

Expand Down Expand Up @@ -156,7 +156,11 @@ manager.config = {
"run_type": "calibration",
"level": "pg_id",
"algorithm": "LocfModel",
"targets": ["y1", "y2"],
"regression_targets": ["y1", "y2"],
# CORE_GENOME keys — audit_manifest raises before the catalog is reached without them
"steps": [*range(1, 37)],
"time_steps": 36,
"prediction_format": "prediction_frame",
}
# Training
model = manager._train_model_artifact() # fits, pickles, returns model
Expand Down Expand Up @@ -196,15 +200,25 @@ preds = mgr._evaluate_model_artifact(eval_type="temporal")

```python
# Using an algorithm name not registered in the catalog
manager.config = {"run_type": "eval", "level": "pg_id", "algorithm": "SVR", "targets": ["y1"]}
manager.config = {
"run_type": "eval", "level": "pg_id", "algorithm": "SVR",
"regression_targets": ["y1"], "steps": [1], "time_steps": 36,
"prediction_format": "prediction_frame",
}
manager._evaluate_model_artifact(eval_type="temporal")
# ValueError: "Model 'SVR' is not in the catalog. Available: ..."
# MissingHyperparameterError: "Unknown algorithm 'SVR'. Available: [...]"
# (the GATE rejects it first — the catalog's own ValueError is unreachable in a
# pipeline run, though still raised on the direct-API path)

# Missing required config key
manager.config = {"run_type": "eval", "level": "pg_id", "algorithm": "AverageModel", "targets": ["y1"]}
manager.config = {
"run_type": "eval", "level": "pg_id", "algorithm": "AverageModel",
"regression_targets": ["y1"], "steps": [1], "time_steps": 36,
"prediction_format": "prediction_frame",
}
# Missing "window_months"
manager._evaluate_model_artifact(eval_type="temporal")
# ValueError: "Model 'AverageModel' requires config keys ['window_months'] but they are missing"
# MissingHyperparameterError: "Algorithm 'AverageModel' requires missing parameters: ['window_months']"

# Indexing _forecast_model_artifact by position (it returns dict[str, PredictionFrame], keyed by target)
forecast = manager._forecast_model_artifact()
Expand Down
Loading
Loading