AI insights now fetches the Claude API key from the central settings
service instead of storing its own encrypted copy, so wages and other
apps can share the same key. Also wires up the update-available banner
using the existing version-check hook pattern.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>