Santa Tracker / foundation audit

What the Flask app actually does

A behavior inventory for issue #217, with one disposition for every current surface and a short list of findings that should shape the Christmas 2026 migration.

Status audit completeScope current repository at 41c96a6Date 18 August 2026Owner issue #217
Verdict. Flask is a working seasonal shell, admin API, and file-backed content editor. It should not be carried into the new application as a second runtime. Keep the behavior and data contracts that users need, move domain logic into the target TypeScript application, and deliberately delete the compatibility edges that only exist because Flask is still here.

This is an inventory and disposition audit, not an implementation PR. The recommendation follows the repository's accepted architecture ADR: one Next.js App Router application, protected admin route groups, and shared pure route logic rather than another deployable service.

31registered Flask routes
22authenticated admin endpoints
2mutable JSON stores

How to read this

Migrate Preserve the user-visible behavior or data contract while moving ownership into the target app. Replace Rebuild the capability around the target architecture because the Flask implementation is the wrong boundary. Archive Keep a historical copy or export for reference, but remove it from the running product. Retire Delete the route or asset after callers are removed.

Evidence links point to the repository or the issue. Line anchors are tied to the audited commit where possible. The source inventory came from the route decorators in src/app.py, the browser callers in src/templates and src/static, and the route/data utilities under src/utils.

Public pages and compatibility routes

RouteCurrent behaviorDispositionRationale
GET /Renders home.html.MigrateKeep the public landing experience and its tracker entry point.
GET /trackerRenders the live map template.MigrateCore public product surface. Rebuild the page and its data reads in the target app.
GET /adventRenders the Advent page only when ADVENT_ENABLED is true.MigrateSeasonal feature is part of the launch scope. Preserve the server-side gate in the new app.
GET /adminRenders the admin dashboard without checking auth in Flask.ReplaceMove the page behind the target app's protected admin route group. API auth alone is not a sufficient page boundary.
GET /admin/route-simulatorRenders the current simulator; JavaScript performs auth/API work.ReplaceMake simulation a target-app admin tool backed by shared route logic.
GET /admin/route-simulator-legacyRenders the explicitly deprecated simulator.RetireIt is a compatibility page with no stated future role. Remove after the new simulator is accepted.
GET /indexDocstring says redirect, implementation renders tracker.html.ArchiveRecord it as a legacy alias. Preserve temporarily only if external links require it, then issue a real redirect or remove it.

Public APIs

EndpointCurrent behaviorDispositionRationale
GET /api/advent/manifestReturns day metadata and unlock state when Advent is enabled.MigrateClient contract is useful. Reissue it from the target app with explicit schema tests.
GET /api/advent/day/<day>Returns unlocked content, or 403 metadata without the payload while locked.MigrateSecurity behavior is intentional and should remain server-authoritative.
/api/santa/* documented in docs/API.mdNo matching Flask routes exist. Browser code reads static route JSON instead.RetireDo not port an API that is not implemented. Decide separately whether the target app needs a public read API.

Admin API inventory

All rows below are behind require_admin_auth, except the login endpoint. The current decorator accepts a 24-hour signed token and also accepts the raw ADMIN_PASSWORD as a backward-compatible bearer value.

SurfaceEndpointsDispositionRationale
AuthenticationPOST /api/admin/loginReplaceKeep password login as a migration input, but replace raw-password bearer fallback with target-app sessions or short-lived credentials, revocation, and rate limits.
Location CRUDGET/POST /api/admin/locations
PUT/DELETE /api/admin/locations/<id>
MigrateThese are the core route editing operations. Preserve validation and response behavior while changing the storage owner.
Location validation/importPOST /locations/validate
POST /locations/import
MigrateUseful authoring workflows. The target implementation needs transaction or atomic-write semantics for bulk replace.
Route status and validationGET /route/status
POST /route/precompute
ReplaceThe current precompute handler validates rather than computing, which is a misleading contract. Expose a named validation action and a separate compute/publish action if needed.
Route simulationPOST /route/simulateReplaceMove deterministic simulation into shared domain logic so editor, admin UI, and tests use one implementation.
Trial route lifecycleGET/POST/DELETE /route/trial
POST /route/trial/apply
POST /route/trial/simulate
ReplaceRetain preview and apply behavior, but give the target app explicit draft/version semantics instead of a mutable sidecar JSON file.
Route backupGET /backup/exportMigrateKeep export before the storage move and make restore/import a tested, explicit operation.
Advent editorGET /advent/days, GET/PUT /advent/day/<day>
POST /advent/day/<day>/toggle-unlock
POST /advent/validate
GET /advent/export
POST /advent/import
MigratePreserve the seasonal editorial workflow, validation, export, and unlock override. Put it behind the target admin boundary.

Route tools and data files

AssetCurrent roleDispositionRationale
tools/route-editorStandalone React/Vite editor with its own package boundary and JSON export.ReplaceAbsorb the useful editor flows into the target admin surface. Keep its algorithm ideas as migration evidence, not a second deployable frontend.
src/static/data/santa_route.jsonPrimary route data. Flask admin writes it; public browser code reads it directly.MigrateMove to the target app's explicit route/content store. Preserve a versioned export and add atomic publish/rollback.
src/static/data/advent_calendar.jsonAdvent content, unlock timestamps, and admin overrides.MigrateImport into the target content model. Validate year-sensitive unlock timestamps before the 2026 season.
Route Data/**Legacy text, candidate route lists, trial routes, and map visualization inputs.ArchiveKeep a dated snapshot for provenance. Do not let the new runtime read multiple competing route sources.
src/utils/locations.py and src/utils/advent.pyParsing, normalization, validation, simulation support, caching, and file persistence.ReplacePort the domain rules deliberately. The late module-level rebinding of load_santa_route_from_json is a migration smell, not a contract to preserve.

PWA and delivery behavior

SurfaceCurrent behaviorDispositionRationale
/sw.js and src/static/sw-register.jsService worker uses a cache-first strategy, caches a fixed list of paths, and serves /offline.html for failed navigations.ReplaceRebuild against the target app's generated asset paths and cache policy. Current list includes /index.html, while Flask's /index is a route, so the cache contract is already split.
Third-party CDN assetsTemplates load Tailwind and Leaflet from CDNs. The service worker avoids caching external origins.ReplaceMake production asset ownership and CSP explicit in the target build. Offline behavior should be tested from a clean browser cache.
offline.htmlStatic offline fallback exists at repository root and under templates.MigrateKeep the user-facing fallback, consolidate to the target app's public asset path, and verify the service worker scope.

Deployment and operations

AssetCurrent roleDispositionRationale
.github/workflows/deploy-on-release.yml
first-deploy.yml
Release-based VPS deployment and post-deploy checks.MigrateKeep release ownership, permissions, restart, health, and rollback checks. Change the process entrypoint and artifact contents.
docs/DEPLOY.mdDetailed systemd/VPS ownership and release guidance.MigrateRetain the operational lessons. Update service commands, health checks, and data paths for the target app.
docs/DEPLOYMENT.mdBroad Heroku, Vercel, Netlify, Docker, AWS, VPS, Supervisor, and example guidance.ArchiveIt contains mutually inconsistent deployment options and placeholder configurations. Keep as historical reference, then write one supported production path.
src/app.py process entrypointCan run Flask directly or under Gunicorn; dotenv path is computed from the source tree.RetireRemove after the target app owns production startup. Keep environment names only where the new deployment contract still uses them.

Findings that affect migration

High

Admin page boundary is weaker than the API boundary

/admin and both simulator pages render without Flask auth. The dashboard's JavaScript then calls protected APIs. A user can load the page shell without credentials, and the actual access model is split between browser code and the API decorator. The target app should protect the route group before rendering the shell.

Evidence: page handlers

High

Password fallback weakens token intent

The auth decorator accepts a signed 24-hour token, then falls through to direct comparison with ADMIN_PASSWORD. That means the password itself can be replayed as a bearer token. The target should preserve the login input only long enough to migrate clients, then remove the fallback and add rate limiting and revocation or short expiry.

Evidence: auth decorator

High

Mutable JSON is the write authority

Location and Advent mutations load a whole JSON document, modify it in memory, and write it back. Bulk replace, trial apply, and Advent import can overwrite the only current copy. The migration needs atomic writes or a transactional store, versioned snapshots, and a tested restore path before moving admin write traffic.

Evidence: route persistence · Advent persistence

Medium

Documentation describes a different API

docs/API.md documents /api/santa/location, route, distance, and stats endpoints, plus non-prefixed admin URLs. None are registered in Flask. The browser instead reads static route data. Treat the document as stale evidence and do not use it as a migration contract without product confirmation.

Evidence: documented API · Evidence: browser data read

Medium

Configuration has conflicting defaults

Config.SECRET_KEY has a placeholder default, while src/app.py uses a different default string. The app logs whether ADMIN_PASSWORD is configured, and the docs describe several configuration classes that are not used by the current app. The target should fail closed in production and have one typed configuration source.

Evidence: config.py · Evidence: app startup config

Medium

There is no executable test result in this audit environment

The repository contains broad pytest coverage and CI workflows, but this checkout has no installed pytest module. Source-level evidence is complete; runtime behavior remains unverified here. The migration PRs should run the existing suite in CI and add browser smoke tests for every accepted public and admin flow.

Evidence: CI test workflow · Evidence: test suite

Recommended migration sequence

  1. Freeze the contract. Accept this inventory as the baseline. Mark the undocumented /api/santa/* family as either intentionally retired or separately specified.
  2. Move pure behavior. Port location normalization, validation, route simulation, Advent unlock logic, and schemas into the target domain package. Test them without a web server.
  3. Import and version data. Convert both JSON stores, archive the legacy Route Data inputs, and prove export, restore, atomic publish, and rollback.
  4. Build protected admin flows. Recreate location editing, route preview/apply, backup, and Advent editing behind one server-owned admin boundary.
  5. Cut public pages and PWA. Migrate tracker, Advent, offline behavior, and route-data reads. Retire Flask only after route-by-route smoke checks pass.

Migration issue linkage

This audit is the accepted inventory candidate for issue #217, under parent tracker #199. Future implementation issues should link back here and name the rows they consume. No implementation issue was created by this audit.

Review checklist

  • Accept or edit the disposition for each row before porting code.
  • Decide whether any external consumer depends on /index or the undocumented APIs.
  • Confirm the target data store and rollback mechanism before enabling admin writes.
  • Require evidence for public pages, protected admin pages, Advent lock behavior, PWA offline behavior, and deployment health checks.