feat: sync latest ARR implementation

This commit is contained in:
Wyndham ARR
2026-07-31 15:11:42 +08:00
parent d6f8a747fa
commit bf7939dd1a
185 changed files with 17527 additions and 2260 deletions

View File

@@ -2,19 +2,69 @@
## Durable Rules
- Human access to desktop/H5 pages, detailed health, business APIs, uploads, traces and downloads requires an authenticated ARR Web session. Only login assets, `POST /api/login` and no-detail `/healthz` readiness are anonymous.
- The single-operator login identity is injected as `ARR_WEB_USERNAME` / `ARR_WEB_PASSWORD`; real credentials are never stored in source or returned/logged. Missing credentials fail Web startup closed.
- Login failures use a generic message and bounded attempt blocking. Logout revokes the server-side session; authenticated mutations continue to require CSRF.
- The user uploads XML; the user does not submit a report year, report month, cutoff date, or separate monthly-generation request in the primary workflow.
- The Agent returns structured data, which must be validated and successfully committed to the database before downstream monthly processing starts.
- Daily history and task trace show the validated browser-uploaded XML basename. Canonical internal source name
`source.xml` is not user-facing; historical rows with no recorded basename display an unknown marker rather than a
guessed filename.
- ARR's frozen processor generates complete structured data, which must be independently validated and successfully committed to the database before downstream monthly processing starts.
- The production XML path must not require Agent, MCP, prompts, model-generated JSON, fetch grants, or a public source URL.
- A successful database commit automatically triggers the monthly-report processing program through the business system.
- Automatic monthly processing runs in a dedicated worker process, not inside the Web request process.
- The worker derives the affected month from retained `ARRIVAL` facts belonging to the committed daily version.
- The monthly `as_of_date`/“更新至” is the greatest `ARRIVAL` actually included in the current monthly snapshot. The XML filename and wall-clock date are never authorities for it.
- Year, month, cutoff/as-of date, and similar fields are internal derived execution/audit values. Their presence in code or metadata does not make them user inputs.
- The monthly output contains the exact business dimension/field `TOTAL PRICE`.
- Each monthly XLSX data-row `TOTAL PRICE` cell is a formula whose business meaning is `REAL PRICE × NIGHTS × NO_OF_ROOMS` (current layout example: `=R2*C2*G2`).
- A stored database `total_price` may be used for integrity checking, but it does not replace the required formula in the exported workbook.
## Open Questions
- The internal event/outbox/worker mechanism for automatic dispatch is not yet selected.
- The exact business-time and cutoff derivation policy is not yet specified; the system must derive it without user input.
- Monthly business/guest rows remain derived from current Finance facts. PostgreSQL persists only publication identity, lineage, channel counts and artifact metadata.
- The outbox event is marked published only after a report is active/superseded and both workbook/result artifacts are registered; replay of the same snapshot must not create a duplicate publication.
- A published monthly version automatically appears in the open monthly list; the primary workflow has no manual list-refresh step.
- Company-channel detail generation is allowed for any period in the selected current month and for historical months. A
future report month is rejected so the system does not create an intentionally empty future workbook.
- Company-channel C/O periods keep fixed report cutoffs: `01-10` uses the 10th, `11-20` uses the 20th and
`21-month-end` uses the selected month's natural month-end. The workbook uses the current committed Finance snapshot
through that cutoff even when the calendar period has not ended; later-arriving facts require a rerun to be included.
- The company-channel UI labels calendar status as `周期未结束` or `周期已结束`. These labels describe whether the
selected period has reached its Bangkok completion boundary; they are not generation permission states.
- In a company-channel detail workbook, a Finance fact with no normalized Group Code remains visible as its own row;
`RES_COMMENT` and `Booking Room` are blank, and this expected blank does not create a warning or duplicate highlight.
- A Finance stay segment with a consistent nonblank Group Code keeps that Group Code and its normal aggregation. When
no current accepted Booking room can be resolved, only `Booking Room` is blank; the lookup miss does not create an
error or warning and does not block the company's workbook.
- The company-report Booking allocation model consists of normalized Group Code, room type and quantity. A raw intake
adapter may derive these fields from a source workbook, but raw workbook columns and normalized result dimensions are
separate contracts; worksheet, physical row, artifact hash and parse version remain provenance.
- A raw Booking workbook is recognized only on a worksheet containing one Tour Code/Group Code header and the exact
Thai `โรงแรม` header. Tour Codes are Unicode-normalized and all whitespace is removed before matching.
- For repeated Tour Codes, the physically last source row replaces earlier rows. A final row with an explicit textual
cancellation removes the Tour Code; cell color or other formatting alone never means cancellation.
- Room extraction reads parenthesized hotel segments. A `【label】` token takes the following integer as quantity and
defaults to one when no following integer exists. Multiple tokens become separate items even when they normalize to
the same room type.
- Numeric suffix variants of `U-TWN` and `U-DBL` normalize to `U-TWN` and `U-DBL`; `高级房TWN`/`高级房DBL` normalize
to `TWN`/`DBL`. Unknown bracket labels retain their raw text and parsed/default quantity as pending. Recognized
unbracketed names such as `6+4`, `Family Suite`, `Junior Suite` and `Superior Garden` also remain pending because the
source omitted brackets. Surcharge, child-breakfast and guide-room tokens are not guest-room allocations.
- Pending items do not contribute accepted Booking room quantity. Automatic and manual rows are both editable and
deletable; activation requires zero pending items and at least one confirmed item. Re-extraction rebuilds the review
draft from source and intentionally discards draft-only edits/deletions.
- The review UI pages at 50 records. A delete request contains 1-50 unique item IDs and is all-or-nothing: every item
must still be undeleted, belong to the same reviewing draft and be deletable, otherwise the transaction rolls back.
- An open Booking review displays the draft artifact's validated browser-uploaded filename directly below
`Booking记录提取`. Accepted historical-source readiness is not shown as provenance for draft extraction records.
- Once accepted, a manually uploaded Booking workbook replaces the complete current source rather than appending
silently to the previous workbook. Migrations 014/015 and the HTTP GET/POST/PATCH/DELETE adapter represent this
boundary; the first real draft activation remains a controlled business acceptance action.
- Booking normalizes the complete Group Code and globally aggregates repeated `(Group Code, room type)` rows by summed
quantity. Worksheet, company/channel and stay dates are not current Booking join dimensions. This contract therefore
requires one Group Code to identify one room allocation across those scopes; a different business reality requires a
schema/view/report-join change, not merely extra unused Excel columns.
- Contradictory Group Code source fields and invalid Finance dates, nights or price details remain strict company-report
errors; the blank fallback applies only to absent or unresolved Booking enrichment.
## Last Reviewed
2026-07-29
2026-07-31