# 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` | Final daily/result/structured, pure-price review JSON, 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 v4 Schema, reconciliation, source/artifact hashes and independent final/review replay |
| 6 | Pure `PRICE_UNMATCHED` delivery | PostgreSQL review case/items/events | No daily XLSX, Finance version, failure event or monthly event; task remains active as `awaiting_review` |
| 7 | Frozen complete review manifest + registered original source | Same processor and independent validator | UI/API accepts only a non-negative integer for a missing key; storage and the manifest normalize it to exact `.00`, and the manifest binds job/case/source/date/processor/rules/full key set |
| 8 | Final verified delivery | PostgreSQL Finance facts | Serializable atomic version activation, review completion and one commit outbox event |
| 9 | Accepted/failed run | Task trace and outbox | Persisted state is authoritative; retryable final infrastructure failure returns to `generation_failed` with manifest retained |
| 10 | `arr.daily_version_committed` | Dedicated monthly worker | Lease, `SKIP LOCKED`, bounded retry/dead-letter |
| 11 | Retained Finance `ARRIVAL` facts | Monthly snapshot request | Affected month from event version; “更新至” is max included `ARRIVAL` |
| 12 | Monthly snapshot | XLSX/result + `reporting` metadata | Formula reopen-validation, current-pin recheck, atomic activation |
| 13 | 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.
- `daily_review_cases`/items/events retain only normalized price keys, aggregate impact, actor/revision and before/after price; they deliberately contain no guest names, comments or raw traces. Frozen manifests and items are immutable.
- 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.1 | Requires one approved exact Tour Code/Group Code alias plus one approved exact hotel-detail alias on the same row; preserves 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-08-11
