Architecture

The layers of the package and the direction imports run

postforecast is one pure package, postforecast, arranged in layers. A module may import from the layers below it and never from the layers above. uv run lint-imports enforces the order through .importlinter, so a wrong import fails just check rather than waiting for review.

block
  columns 3

  space
  down<["<b>layers</b><br>imports point down"]>(down)
  space

  anchored["<b>methods.anchored</b><br>consensus-anchored estimate"]
  apart<["<b>independence</b><br>no imports between"]>(x)
  bayesian["<b>methods.bayesian</b><br>consensus as prior"]

  history["<b>history</b><br>previous actuals, growth, revisions"]:3
  anchor["<b>anchor</b><br>the panel as it stood at one moment"]:3
  method["<b>method</b><br>compatibility import for core"]:3
  core["<b>core</b><br>fit/apply protocol and registry"]:3
  subjects["<b>subjects</b><br>compatibility import for validation"]:3
  validation["<b>validation</b><br>subject and reading-policy contracts"]:3
  panel["<b>panel</b><br>input panel and its Pandera schema"]:3
  util["<b>util</b><br>dtype predicates, series primitives"]:3
  defaults["<b>defaults</b><br>every constant, imports nothing"]:3

  forbidden["<b>forbidden</b><br>no module in postforecast imports these"]:3
  files["pathlib · os · io<br>tempfile · shutil · sys"]
  processes["subprocess · threading<br>multiprocessing · time · random"]
  network["socket · urllib · http · requests<br>httpx · pickle · json"]

  classDef family fill:#e3f1e6,stroke:#2e7d4f,color:#12301d
  classDef layer fill:#e6eef9,stroke:#3b6db3,color:#132a4a
  classDef compat fill:#f3f4f6,stroke:#9aa1ab,stroke-dasharray:4 3,color:#555
  classDef contract fill:#fff4d6,stroke:#b58400,color:#4a3700
  classDef forbid fill:#fdecec,stroke:#c0392b,color:#5a1a14
  classDef module fill:#fff,stroke:#c0392b,color:#5a1a14
  class anchored,bayesian family
  class history,anchor,core,validation,panel,util,defaults layer
  class method,subjects compat
  class down,apart contract
  class forbidden forbid
  class files,processes,network module
Figure 1: The three import-linter contracts. Each layer may import only the layers below it, the two method families never import each other, and no module in the package imports the forbidden modules.

Layers

From top to bottom:

  • methods: The method families. Each family is a subpackage that owns its parameters, its preparation and its publication boundary.
  • history: Realised history per entity: previous actuals, growth and consensus revisions. It sits above anchor because a revision is one quantity read at two moments, and both reads have to be anchored.
  • anchor: Reading the panel as it stood at one moment: strictly before the reading time, and no older than a maximum age.
  • method: Compatibility imports for the method protocol and registry, kept so older import paths still work.
  • core: The fit/apply protocol, declared requirements and the registry that finds a method by name.
  • subjects: Compatibility import for subject-frame validation.
  • validation: Contracts shared by preparation and anchoring: subject columns, reading-policy scalars and caller-supplied publication schedules.
  • panel: The input panel and its Pandera schema, the one input format every method reads.
  • util: Dtype predicates, point-in-time series primitives and the helper that keeps a renamed constant importable.
  • defaults: Every default and numerical constant in the package. It imports nothing, so any layer can read it.

Rules the contracts enforce

  • One direction: The layer order above is an import-linter layers contract. A lower layer never imports a higher one.
  • Families are independent: methods.anchored and methods.bayesian never import each other. Code that two families need moves down a layer, never sideways.
  • Pure: No module imports file, network, process, clock or randomness modules. Serialising an audit record is the caller’s job, at the caller’s edge.

Package layout

Modules are listed from the bottom layer up.

src/postforecast/
├── __init__.py                 # public API, re-exported from the layers below
├── defaults.py                 # every default and research constant
├── util/
   ├── dtypes.py               # dtype predicates for dataframe contracts
   ├── renames.py              # renamed constants, importable with a warning
   └── series.py               # point-in-time series primitives
├── panel.py                    # the input panel and its Pandera schema
├── validation/
   ├── publication.py          # caller-supplied publication schedules
   ├── subjects.py             # numeric and age columns methods consume
   └── temporal.py             # reading-policy scalar contracts
├── subjects.py                 # compatibility import for validation.subjects
├── core/
   └── method.py               # fit/apply protocol, requirements, registry
├── method.py                   # compatibility import for core.method
├── anchor.py                   # read the panel strictly before one moment
├── history.py                  # previous actuals, growth, revisions
└── methods/
    ├── __init__.py             # imports each family so it registers
    ├── anchored/               # deterministic consensus-anchored estimate
       ├── parameters.py       # shipped, versioned coefficient sets
       ├── gap.py              # forecaster distance from consensus
       ├── eligibility.py      # publishable or not, with the reason
       ├── estimate.py         # the published number and its parts
       ├── preparation.py      # long panel to prepared subjects
       └── publication.py      # dataframe publication boundary
    └── bayesian/               # consensus as prior, forecaster as evidence
        ├── settings.py         # posterior arguments at shipped defaults
        ├── presets.py          # named, documented settings bundles
        ├── series_math.py      # growth, bias and moments
        ├── posterior.py        # precision-weighted posterior
        ├── intervals.py        # interval shape and directional confidence
        ├── empirical.py        # intervals from pooled residuals
        ├── eligibility.py      # publishable or not, with the reason
        ├── subjects.py         # subject frame for BayesianPosterior
        ├── preparation.py      # point-in-time preparation
        ├── prediction_settings.py  # full-workflow configuration and names
        ├── prediction_math.py  # independent and correlation-adjusted math
        └── prediction.py       # full Bayesian workflow over a tidy panel