Repository navigation
feat(ingest): persist every source through the repository layer (#36) - #47
Merged
Merged
Conversation
2 of 3 tasks
Named unique constraints on weather, flight_arrivals, competitor_rates, hotels, room_types and daily_performance so re-running an ingest updates rows instead of duplicating them. Co-authored-by: Vichu Selvaraju <vichuselvaraju@gmail.com>
get_session commits once per request on success and rolls back on error. Routes depend on it with scope="function" so the commit runs before the response is sent. The widget repository flushes instead of committing.
…hotels upsert_rows is the shared primitive: it collapses duplicate keys in a batch, touches a row only when a value changed, and returns new/updated/ unchanged counts. The events upsert moves from ingest/ into repository/. Co-authored-by: heatherlarange <heatherlarange01@gmail.com> Co-authored-by: Vichu Selvaraju <vichuselvaraju@gmail.com>
Open-Meteo weather moves into ingest/ and fixes dates landing a day early east of Greenwich. Per-flight rows collapse into daily counts. A runner fetches one source per run. The Open-Meteo client is created on first use, and climate projections live in scripts/. Co-authored-by: Vichu Selvaraju <vichuselvaraju@gmail.com>
The parser no longer writes SQL. rate is room revenue / rooms sold (the client's ARR) until listed-rate history arrives; unsold nights have no rate. The CLI that opens a session lives in scripts/load_hotel_data.py. Co-authored-by: Vichu Selvaraju <vichuselvaraju@gmail.com>
ingest/ is part of the services layer; only scripts/ open sessions. Co-authored-by: Vichu Selvaraju <vichuselvaraju@gmail.com>
selvarajuv
force-pushed
the
persist-pg
branch
from
October 13, 2026 17:44
b0353a6 to
aaa547b
Compare
selvarajuv
self-requested a review
October 13, 2026 17:59
selvarajuv
approved these changes
Oct 13, 2026
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
Every ingestion source now writes to Postgres through the repository layer. Weather and flights gained normalizers and a runner; the hotel parser stopped writing its own SQL.
Refs #36, not
Closes:flight_arrivalsis wired and tested but needs OpenSky and RapidAPI credentials to populate. The other two criteria are met.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
repository/gains weather, flights and hotel repositories.upsert_rowsis the shared primitive, repositories never commit, the caller owns the transaction.upsert_rowscollapses duplicate keys within a batch, and stampscaptured_atonly where the column exists. Both were crashes.backend/src/datatoforesight/ingest. Behaviour fix: Open-Meteo'sTime()is local midnight expressed in UTC, so every reading landed a day early east of Greenwich.weather_datejoinsstay_date, so this mis-attributed every weather feature by a night.ingest/flights.py, per-flight rows to daily counts, andingest/external.py, a runner taking--source weather|opensky|aerodatabox.create_engineorto_sql, which was the last adapter writing SQL directly. A separatepersist()writes through the repository.upsert_hotelandupsert_room_typelet a reload correctbase_rateand coordinates. Coordinates coalesce, so a loader that omits them does not erase them.docs/external-signals.mdcovers the contract, both CLIs and the known gaps.Database migrations
just migrate-create "...")just migrate) and rolls back (just migrate-down)b9719be9df40adds natural keys for the external tables,c97a8a991e88for the hotel tables. Verified up, down, up, and from scratch on an empty database.Checklist
just lint) and code is formatted (just format)just hooksinstalled) —pre-commit run --all-filespasses ruff and ruff-format; every commit checked against the pinnedconventional-pre-commit==4.1.0.env.examplewhere relevant — newdocs/external-signals.md, plusingest/added to the README structure. No new env varjust dev) and verified the API athttp://localhost:8000/docs— booted via uvicorn rather than compose, since Postgres is local here./health200,/docs200, no startup errors. No route changed, but the parser gained an import edge intorepository/worth provingHow to test
just migratecd backend && uv run python -m foresight.ingest.external --source weather --city Dublin --lat 53.3498 --lon -6.2603. Expect16 new, then16 unchangedon a re-run.uv run --group ml pytest -q. Expect 195 passing. Barejust testfails on a missingstatsmodels, which is pre-existing: it lives in themlgroup and CI installs it.Notes for reviewers
repository/hotel.pywithcreate_hotelandcreate_room_type. Different functions so the merge is additive, but whoever lands second resolves the file. Those versions commit inside the repository while this path commits in the caller, which is worth settling as a team.daily_performance.rateis null on every row this writes. It is the training target incompare_models.py, and the export's unmappedARRcolumn could fill it, but that is a modelling decision rather than a persistence one.