Files
wyndham-ARR/arr-opera-daily-ingest/references/business-rules.md
2026-07-29 16:38:05 +08:00

93 lines
4.9 KiB
Markdown

# Business Rules
## Fixed processing order
1. Validate the invocation and parse one fixed `RES_DETAIL` XML.
2. Require exactly one XML business date and keep one audit record for every `G_RESERVATION` in XML order.
3. Require `RATE_CODE` so whitelist membership is knowable.
4. Classify a trimmed, uppercased rate code outside the whitelist as `excluded_rate_code`.
5. Validate every whitelist candidate; classify invalid rows as `validation_failed`.
6. Deduplicate valid candidates by `DISP_ROOM_NO + ARRIVAL`; retain the first XML occurrence and classify later occurrences as `duplicate` pointing to the first source sequence.
7. Compute integer `NIGHTS = DEPARTURE - ARRIVAL`. Zero is legal; negative is invalid.
8. Validate the complete fixed price table, then apply the approved zero-price exception or exact three-key match. Classify unmatched candidates as `price_unmatched`.
9. Assign `channel_key` and conditional `kb_amount` as row facts.
10. Generate the 19-column daily XLSX, `result.json`, and path-free `structured-result.json` from the same in-memory records.
11. Independently replay the XML, rules, prices, routing, workbook, artifacts, outcomes, and structured records before reporting success.
Any row-level validation or price error fails the formal invocation. On failure, already excluded, duplicate, or price-unmatched outcomes remain explicit. Other candidates that did not reach a validated result become `validation_failed` with `BATCH_NOT_VALIDATED`.
## Rate-code whitelist
`GRPA1`, `GRPA2`, `GRPA3`, `GRPA4`, `GRP1`, `WHO1`, `WHO2`, `WHO3`, `WHO4`, `LTLT`, `LBLT`, `LBSM`, `LBMS`, `LBW1`, `LBKB`, `LBLS`, `WHKR2100B`, `GL2100B`, `GL2200KR`, `GLSPCB`.
## Reservation text
For reservation-level `RES_COMMENT` and `TRACE_TEXT`, take the first non-empty value in XML order. Leave blank when every candidate is empty. Never concatenate values or expand one reservation into multiple rows.
## Company normalization
Normalize company identity for price matching and channel facts:
1. trim, uppercase, and keep only `A-Z` and `0-9`;
2. use fixed substring keywords:
- `LIANTAI``LIAN TAI`
- `QBD``QBD`
- `RAINBOW``RAINBOW/AI`
- `FENGRUN``FENGRUN`
- `HANATOUR` or `HANA``HANA TOUR`
- `HONGTAI``HONGTAI`
- `GUANGZHOUGOEASY` or `GOEASY``GUANGZHOU GO EASY`
3. when no keyword matches, use the compact normalized name;
4. when more than one keyword group matches, fail instead of guessing.
This is deterministic keyword matching, not edit distance.
## Pricing
Use bundled `价格对照.xlsx` as a fixed, non-user-editable rule source.
- Normalize `RATE_CODE` with `upper(trim(value))`.
- Compare amounts numerically, so `900` equals `900.00`.
- Reject negative reference amounts/totals, invalid rows, ambiguous companies, and every duplicate normalized `COMPANY + RATE_CODE + Opera amount` key.
- Before the normal lookup, set numeric `REAL PRICE = 0` only when:
- company identity is `RAINBOW/AI` or `GUANGZHOU GO EASY`; and
- rate code is `LBMS` or `LBSM`.
- The exception ignores `EFFECTIVE_RATE_AMOUNT`.
- Every other retained row must exactly match normalized `COMPANY_NAME + RATE_CODE + EFFECTIVE_RATE_AMOUNT`.
- Write reference `总价` as static `REAL PRICE`.
- Write static `TOTAL PRICE = REAL PRICE * NO_OF_ROOMS * NIGHTS`.
- An unmatched candidate fails the entire invocation.
## Channel facts
Keep original XML `COMPANY_NAME`; assign only the derived `channel_key`.
| Condition | `channel_key` |
|---|---|
| `QBD` | `QBD` |
| `LIAN TAI` and rate in `{LBLT, LTLT}` | `LIANTAI-FIT` |
| `LIAN TAI` and another whitelisted rate | `LIANTAI-GROUP` |
| `RAINBOW/AI` or `GUANGZHOU GO EASY`, any rate | `DY-AI-Easy-KB` |
| `FENGRUN` | `FENGRUN` |
| any other company | sanitized actual company name |
For other companies, trim, remove `: \ / ? * [ ]`, and truncate to 31 characters. Resolve different-company collisions with `-2`, `-3`, and so on. Set `kb_amount = NO_OF_ROOMS * 100` only for `DY-AI-Easy-KB`; do not multiply by nights and do not add it to `TOTAL PRICE`.
## Business date, XLSX, and types
- Derive the business date only from XML group dates, never runtime time or filename.
- Daily filename and sheet name: `M.D.xlsx` and `M.D`.
- Preserve retained XML order.
- Use the bundled daily template; do not add monthly or channel worksheets.
- Headers occupy row 1; data begins row 2.
- Write dates as real Excel dates with `DD-MMM-YY`.
- Write counts and prices as static numbers, never formulas.
- Write identifiers and descriptive fields as text.
- Require `ADULTS >= 0`, `CHILDREN >= 0`, `NO_OF_ROOMS > 0`, `EFFECTIVE_RATE_AMOUNT >= 0`, and `DEPARTURE >= ARRIVAL`.
- `DEPARTURE = ARRIVAL` yields `NIGHTS = 0` and `TOTAL PRICE = 0`.
- Preserve allowed blank fields and complete selected comment/trace text.
## Prohibited behavior
Do not access OSS, embed credentials, generate/update a monthly workbook, query/write a database, infer Group Code from `BLOCK_CODE`, or derive structured facts by reopening the generated XLSX.