# Data Flow ## Web Access Boundary 1. An anonymous browser requesting `/` is redirected to `/login` with an allowlisted return target. `/h5` and its H5 assets are public; anonymous access is limited to `/api/public/h5/months`, `/api/public/h5/analytics` and the no-detail `/healthz` signal. Other anonymous API/download requests receive JSON `401 AUTH_REQUIRED`. 2. `POST /api/login` accepts strict same-origin JSON, verifies runtime-owned credentials under a bounded per-client attempt ledger and issues a random server-side session plus CSRF token in an `HttpOnly`, `SameSite=Strict` cookie. 3. Every desktop portal page, detailed health response, generic read API, upload/mutation, task trace and artifact download validates the authenticated session. Public H5 endpoints return only sanitized aggregate dashboard data; mutations additionally validate the session CSRF token. 4. `POST /api/logout` validates CSRF, revokes the server-side session and expires the cookie. `/healthz` remains a no-detail anonymous readiness signal for container orchestration. 5. `GET /api/history-months` is an authenticated read-only discovery path. It combines daily processing/monthly publication counts from PostgreSQL with durable company-job counts; each desktop history then reads its selected month through the existing paged list endpoint. ## Primary XML Flow | Step | Source | Destination | Gate | |---|---|---|---| | 1 | Browser XML upload | Uploaded-basename task provenance + private canonical source object + queued DB job | Filename/content/size and immutable object identity | | 2 | Committed source object | Isolated processor input | Materialization rechecks stored bytes | | 3 | Fixed `process_daily.py` | Daily/result/structured or failure artifacts | Timeout, confined paths, exit/JSON agreement | | 4 | Validated processor/report artifacts | Private committed OSS objects | Role, MIME, size and SHA-256 | | 5 | Canonical `DeliveryEnvelope` | `DeliveryValidator` | Strict Schema, reconciliation, source/artifact hashes and independent validation | | 6 | Verified delivery | PostgreSQL Finance facts | Serializable atomic commit/version activation | | 7 | Accepted/failed run | Task trace and outbox | Persisted state is authoritative | | 8 | `arr.daily_version_committed` | Dedicated monthly worker | Lease, `SKIP LOCKED`, bounded retry/dead-letter | | 9 | Retained Finance `ARRIVAL` facts | Monthly snapshot request | Affected month from event version; “更新至” is max included `ARRIVAL` | | 10 | Monthly snapshot | XLSX/result + `reporting` metadata | Formula reopen-validation, current-pin recheck, atomic activation | | 11 | Registered active report | Web list/download + outbox acknowledgement | Real report ID; visible monthly tab polls automatically; path/size/SHA-256 recheck before download | ## State Ownership - PostgreSQL owns job/attempt/delivery state, Finance facts, current version and outbox. - PostgreSQL `reporting` owns monthly publication versions, daily-version lineage, channel manifest and artifact identities; it does not duplicate monthly business rows. - OSS owns immutable source/output bytes; ARR stores only controlled object identities in the database. - `processing_runs.uploaded_filename` owns the user-facing browser basename; the source artifact remains canonically named `source.xml` for processing and validation. - Temporary processor/validator paths are deleted after each request. - New monthly/company XLSX and `result.json` bytes live in immutable private OSS objects; the database stores provider, bucket alias, object key and identity. The controlled local reader remains available for historical local records, while `/app/outputs` is only staging/cache and local `.web-jobs` state. ## Booking Source And Company-Report Flow | Step | Source | Destination | Gate | |---|---|---|---| | 1 | Authenticated Booking XLSX upload | Bounded parser 2.0 | Requires Tour Code + exact `โรงแรม`, latest-row/cancellation semantics and room-item extraction/review | | 2 | Parsed raw items | Private artifact + extraction draft | Live migration 015 preserves source coordinates, confirmed/pending/deleted item state and extracted values | | 3 | Human review | Draft item updates | Confirm/edit or atomically soft-delete 1-50 unique selected items while canonical current Booking source remains unchanged | | 4 | Fully reviewed draft | Accepted canonical batch + `booking.current_source_batch` | Live 014/015 plus repository zero-pending/atomic activation; real PostgreSQL transaction-only vertical slice passes | | 5 | Current Finance facts | Company-report stay segments | Finance supplies company/channel/dates/pricing; normalized Group Code is the Booking lookup key; open draft blocks new job | | 6 | Current Booking room summary | `Booking Room` output | Sum by Group Code + room type; missing or unmatched Group Code leaves Booking Room blank | Migrations 014/015 are formally applied: batch 1 is the current source and the draft tables are empty. The source contains a PostgreSQL draft repository, backend routes, browser editor and GET/POST/PATCH/DELETE transport coverage. A real-PostgreSQL transaction-only probe created a draft, confirmed its pending item, activated immutable facts and then rolled everything back, preserving the current source and leaving no synthetic residue. First activation of an actual operator workbook remains a controlled business action. ## Monthly Derivation Boundary The worker never infers a date from an XML filename or the current clock. It treats the event's `daily_version_id` only as a lookup key, derives scope from retained Finance `ARRIVAL`, and verifies that the generated report's maximum arrival equals its persisted `as_of_date`. Every data-row `TOTAL PRICE` cell is `=R[row]*C[row]*G[row]`. ## Last Updated 2026-07-31