- Fix gather_competitor_data() tier filter: it queried tier IN ('primary','secondary'),
values that never exist (real values are 'own'/'competitor'/'market'), so the
cheapest-competitor comparison has always silently returned nothing.
- Feed the previous insight back into the prompt so the model can note what's
changed/resolved instead of repeating itself.
- Extend forecast horizon from 14 to 30 days; add a per-day revenue table
alongside the existing occupancy table.
- Annotate the occupancy table with UK (England) bank holidays.
- Add a same-channel market-movement section (B.com vs B.com, rack vs rack)
diffing rates against the last insight's snapshot, threshold £3.
- Add a parsed headline field + insight history list on the Dashboard,
collapsed to headline/age and expandable to full content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same fix as kitchen/kds: AuthGate unconditionally hard-navigated
window.top to the central /login on session expiry, even when not
embedded in the portal iframe — dropping an installed/directly-opened
forecasting session into the portal's framed browser view instead of
staying in its own window.
Now only bounces to central login when actually embedded (passing
?from= so it returns here afterwards); standalone or directly-opened
tabs get an in-app login form and never navigate away. Also wired up
the inactivity auto-logout timer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Backend returns { keys: [...] } but frontend was treating the whole
object as the array, so apiKeys.length was always falsy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
registerSW.js was 404ing in the browser — the PWA plugin was a leftover
from the original port and this is an internal hotel app with no PWA requirement.
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>
The management monitor checks http://{host}:{port}/health (no slug prefix).
Adds a location = /health alias alongside the existing /forecasting/health
so both the slug URL (browser/NPM) and the bare path (monitor) work.
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>
The renderContent() function used dangerouslySetInnerHTML without
first sanitizing the AI-generated text, allowing any HTML in the
model response to execute in the browser. Added escHtml() helper
and applied it before the bold-substitution regex.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the JWT expired, AuthGate redirected window.location to /auth/login
which loaded inside the portal iframe. The portal detected window.self !==
window.top and showed the EmbeddedFallback ("This app isn't available yet")
instead of the login page.
Redirect window.top instead so the portal itself navigates to /login.
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>
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>
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>
page-content kept grid-row 1/-1 from the desktop rule, so the
top-bar auto-placed into an implicit row below the content —
the nav menu rendered at the bottom of the page.
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>