Covers model returns data (covers * spend) but if resos spend settings are
absent the result is zero, which previously bypassed the DOW fallback. Now
fall back to DOW when the covers-based dry or wet is zero.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ON CONFLICT clause was missing arrival_date, departure_date, nights, and
room_number — so early checkouts (where NewBook updates booking_departure
to the actual checkout date) never propagated to the DB columns. Reverts
the COUNT(DISTINCT) workaround since the root cause is now fixed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multiple bookings per room (early-checkout Departed + new Arrived, or
NewBook double-bookings) caused COUNT(*) to exceed total room capacity.
Switch to COUNT(DISTINCT COALESCE(room_number, newbook_id)) so each
physical room counts once regardless of how many booking rows span it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guests who check out early keep their original departure_date in NewBook,
so Departed status with departure_date > stay_date were being counted as
live OTB — inflating room count above physical capacity. Unconfirmed
bookings are also provisional so excluded from live OTB.
Prior-year queries retain all statuses since historical bookings will
legitimately be in Departed state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When forecast_rooms_for_date throws inside covers_model, asyncpg aborts the
whole transaction. The exception was being caught with a warning but no rollback,
so every subsequent query in the same request failed. Added db.rollback() in
all three exception handlers (breakfast pickupv2, dinner pickupv2, per-day loop)
and moved public.py's rollback into a finally block so it always fires even
when covers_model returns normally with a corrupted transaction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When forecast_covers_range or forecast_revenue_for_date throw DB exceptions,
the swallowed exception leaves the async session in an aborted transaction state.
Subsequent queries (DOW averages, per-date accom) then fail with
InFailedSQLTransactionError. Adding db.rollback() in the except blocks
resets the session so the fallback path can continue cleanly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The table is newbook_occupancy_report_data (per-category rows). Aggregate
SUM(available - maintenance) WHERE category_id != '5' to get the correct
available room count excluding overflow and maintenance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static system_config.total_rooms (80, includes overflow) with
per-day available_rooms from daily_occupancy table. This ensures
occupancy % is calculated against the correct base (non-overflow rooms)
consistent with how the rest of the app reports occupancy. Falls back
to system_config.total_rooms when daily_occupancy has no data for a date.
Also adds available_rooms per-day to the response payload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetches daily ERA5 weather from Open-Meteo (no API key).
Configurable location, timezone and sync time via Settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates to single sync path: resos_bookings_sync.py →
resos_bookings_data table. Removes sync_resos_data(), duplicate
load_resos_custom_field_mappings(), resos_api_key and
sync_resos_enabled seed rows, and the dead resos_sync scheduler job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Yield to event loop after each booking DB commit so FastAPI can
respond to Docker health checks during long syncs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
resos_custom_field_mapping, resos_opening_hours_mapping,
resos_manual_breakfast_periods, resos_bookings_data,
resos_bookings_stats, resos_booking_pace were never added to the
schema — they only existed in the original standalone Postgres instance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the removed API key form with a status card showing whether the
Resos key is configured in the central Settings app, plus a Test Connection
button. Adds back the GET /settings/resos and POST /settings/resos/test
endpoints (now reading from central_settings rather than system_config).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the standalone resos_api_key from the forecasting app's own
system_config table. All credential fetches now go through
central_settings.get_resos_credentials() / get_resos_credentials_sync()
which pull from the Settings app (LXC 116) via the internal integration
endpoint — the same pattern already used for NewBook. The Resos API
Config section is removed from the forecasting Settings page; users
manage the key in the central Settings app instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deletes bookability.py, competitor_rates.py, Bookability.tsx,
CompetitorRates.tsx. Removes their imports, router mounts, and nav items.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
forecast_runs was missing, causing all manual/scheduled forecast jobs to
fail on startup. pickup_snapshots and pickup_curves are referenced by the
pickup model but were absent from schema.sql.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a container restart any in-progress jobs leave orphaned 'running' rows
with no completed_at. Added startup cleanup to mark them failed, so the UI
doesn't show a permanently stuck status.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend now awaits the refresh in a thread executor instead of queuing a
background task, so the response returns only after data is written. Frontend
drops the 3s setTimeout and invalidates the cache immediately on success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend adds valid_from to DateRateInfo and computes date_last_updated dict
(max valid_from across categories per date). Frontend renders it as a small
timestamp below the date in each column header — time only if today, date+time
if older. Full ISO string in title tooltip.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add booking_com_hotels and booking_com_rates (competitor rate scraper tables)
- Fix forecast_snapshots: was using old pivoted schema (snapshot_date,
prophet_value etc.); code expects normalised rows (perception_date, model,
forecast_value, actual_value). Add DO block migration to drop+recreate if
old schema is detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These two endpoints were missed in the central settings migration. They now try
the Settings service first and fall back to app-local system_config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Missing from initial schema; used by competitor rates scraper and cleanup_stale_batches on startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SQLAlchemy's text() treats $1/$2 in PL/pgSQL blocks as bindparams and raises
InvalidRequestError. exec_driver_sql sends raw SQL directly to psycopg2,
bypassing that processing. Without this all tables are missing on startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stack-wide NewBook config lives in the Settings app (LXC 116) and is
fetched live via SETTINGS_URL/SETTINGS_SECRET — same pattern as cashup,
room-planner and maintenance. App-local system_config credentials remain
as a fallback for standalone/dev use. The app's Settings → Newbook page
no longer edits credentials; it points to the central app and keeps
Test Connection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
config.py and public.py import get_api_key_auth/get_all_api_keys/
create_api_key/revoke_api_key/delete_api_key — the public-API key
system is app-local (api_keys table), not part of the central auth
that was replaced, so it should have been kept in the port.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Python FastAPI ML backend kept intact; auth replaced with central hnf_session cookie verification. Frontend rebuilt on React 18 + TS + Vite with stack design system, Plotly charts retained. Shared Postgres via DATABASE_URL; schema applied on startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>