Repository navigation
feat: implement EBM (and other things) - #49
Merged
Merged
Conversation
- pricing_models table and repository: versioned, one active per hotel - EBMModel with conformal intervals and per-night uncertainty - Occupancy sample weighting and a shared feature builder - Decision records 0001-0009 for the model design
- Airport IATA/ICAO codes on hotels - Regenerate the corrupted uv.lock - Linearize the Alembic migration history
- Models share constraint names and an ON DELETE policy for every FK - One repository module per model; repositories never commit - Services raise domain errors (404/409/422/503 mapped in one place) - Event merging lives in a service; signals split per source - Outside adapters moved under services; async weather, flight and PredictHQ clients, airports taken from the hotel
…omain - Ingests and backfills run as background jobs started through the API; standalone scripts removed. One job kind per signal; the dates choose refresh or backfill - Cities are the market: weather, events and flights are kept per city - Hotel-scoped reads, PATCH for hotels and cities, schemas in route modules - Services split into domain, adapters, ingest and ml; outside-signal models and repositories grouped under signals/ - Production pinned to one uvicorn worker - System design overview and decision records 0010-0026
App traffic uses Neon's pooled URL and Alembic the direct one; config.py converts the libpq URLs neon env pull writes for asyncpg. `just <recipe> --neon` layers docker-compose.neon.yml over the local stack. See decision 0027.
- routes/ingest.py holds POST /hotels/{id}/ingest/*; routes/job.py keeps
job reads. URLs unchanged; date bodies document YYYY-MM-DD with examples
- OpenAPI tags capitalized
- Correct the base_rate comment: the model doesn't use it
The old tests predated the redesign and no longer imported. The new test calls GET /health in-process through httpx's ASGI transport; no database.
POST /hotels/{id}/train queues a TRAIN job that fits an EBM on ingested
performance and recorded weather and saves a new, described version.
Versions can be listed and activated. Recommendations come from the active
model, use only ingested data (recorded, forecast, then typical-week
weather) and explain the rate by its largest factors. See decisions
0028 and 0029.
…elds Every request/response field, path id and query parameter has a description with units, inclusive dates and currency. RequestSchema builds each body's /docs example from the fields' defaults and examples. PATCH bodies reuse their create body's field settings; boxed section headers. See decision 0030.
- Recommendations no longer return a signals list or per-factor signal labels; both were fixed text, not data. Factors carry detail and amount - Training no longer writes training_summary; the model response no longer returns it - copy_settings_from removed until a version's settings and window are stored in their own columns - Clear pyright errors in training, recommendations and job windows
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the EBM pricing model end to end: training runs as a background job on ingested performance
and weather data, and
GET /hotels/{id}/recommendationsnow returns a real rate from the hotel'sactive model instead of a placeholder. Also moves Postgres to Neon and standardizes the backend
layers and API docs.
Closes #43
Type of change
feat— new featurefix— bug fixdocs— documentation onlyrefactor— code change that neither fixes a bug nor adds a featureperf— performance improvementtest— adding or updating testschore/build/ci— tooling, deps, or pipeline changesChanges
uncertainty value and occupancy-weighted training (decisions 0001–0009).
POST /hotels/{id}/train(description, date window,activate, EBMsettings) runs as a job. Also
GET /hotels/{id}/models,GET /models/{id}andPOST /models/{id}/activate.ingested. Weather is recorded, then forecast, then typical for that week. Returns the rate, range,
uncertainty and the factors behind the price (
detail+amount).is_placeholderis now alwaysfalse.to
routes/ingest.py; URLs unchanged.one place, and there's one router per domain.
just <recipe> --neonswitches between local and Neon (decision 0027)./docsexample bodies are built from the fields'defaults and examples (decision 0030).
Database migrations
just migrate-create "...")just migrate) and rolls back (just migrate-down)Checklist
just lint) and code is formatted (just format)just hooksinstalled).env.examplewhere relevantjust dev) and verified the API athttp://localhost:8000/docsHow to test
neon env pull, thenjust dev --neon. Or runjust devfor local Postgres, which alsoapplies migrations.
POST /hotels/{id}/ingest/performancewith a CSV and abase_rate.POST /hotels/{id}/ingest/weatherwith{"start": "...", "end": "..."}.POST /hotels/{id}/trainwith adescription, then pollGET /jobs/{id}until itsucceeds.
GET /hotels/{id}/recommendations?date=YYYY-MM-DD.GET /hotels/{id}/modelsand switch withPOST /models/{id}/activate.just test.Notes for reviewers
jobs/cities redesign.
and there's no flights ingest. Their model inputs are left empty.
an earlier version;
training_summarycolumn;.cache.sqlite.