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

7.8 KiB
Raw Blame History

Business Rules

Processing order

  1. Parse the fixed RES_DETAIL XML and require exactly one XML business date.
  2. Create one in-memory source record for every reservation in XML order; no later stage may discard that audit record.
  3. Require every reservation to contain RATE_CODE so whitelist membership is knowable.
  4. Classify rows whose trimmed, uppercased RATE_CODE is not in the whitelist as excluded_rate_code.
  5. Validate required fields on the remaining rows and classify invalid rows as validation_failed.
  6. Deduplicate remaining valid rows by DISP_ROOM_NO + ARRIVAL; retain the first XML occurrence and classify later occurrences as duplicate with a pointer to the first source sequence.
  7. Preserve the relative XML order of all retained rows and compute static non-negative integer NIGHTS = DEPARTURE - ARRIVAL. Same-day arrival and departure is a valid zero-night record; a departure before arrival is invalid.
  8. Validate the entire bundled price table, then apply the approved zero-price exception or match and write static daily REAL PRICE; calculate static daily TOTAL PRICE = REAL PRICE * NO_OF_ROOMS * NIGHTS. Classify an unmatched row as price_unmatched.
  9. Assign deterministic channel keys and conditional KB values, then generate the daily workbook. Only legacy daily-monthly mode also routes the same in-memory rows into a monthly workbook.
  10. Write result.json and the all-source-record structured-result.json, then independently validate every requested XLSX output, artifact hash, derived key, outcome reconciliation, and structured payload before reporting success.

Any row-level validation or price error fails the formal invocation. Rows that were valid candidates but could not reach a fully validated formal result become validation_failed with BATCH_NOT_VALIDATED; filtered, duplicate, and price-unmatched conclusions remain unchanged in the failed structured payload.

Rate-code whitelist

GRPA1, GRPA2, GRPA3, GRPA4, GRP1, WHO1, WHO2, WHO3, WHO4, LTLT, LBLT, LBSM, LBMS, LBW1, LBKB, LBLS, WHKR2100B, GL2100B, GL2200KR, GLSPCB.

Multi-value 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.

Price matching

Use the bundled latest references/价格对照.xlsx as a fixed, non-user-editable rule source.

  • Normalize company identity on both the price-table side and the XML/output side before price matching or standard-channel routing:
    1. trim, uppercase, and remove every space and punctuation character, retaining only A-Z and 0-9;
    2. use fixed substring keywords, not edit distance, to recognize LIANTAI → LIAN TAI, QBD → QBD, RAINBOW → RAINBOW/AI, FENGRUN → FENGRUN, HANATOUR or HANA → HANA TOUR, HONGTAI → HONGTAI, and GUANGZHOUGOEASY or GOEASY → GUANGZHOU GO EASY;
    3. if no keyword group matches, use the compact normalized name as an exact identity;
    4. if more than one keyword group matches, fail rather than guessing.
  • Match normalized COMPANY_NAME + RATE_CODE + EFFECTIVE_RATE_AMOUNT to normalized price-table COMPANY'S NAME + RATE CODE + Opera展示的价格.
  • Trim and uppercase rate codes.
  • Compare price amounts numerically, so 900 equals 900.00.
  • Reject negative Opera display amounts or total prices in the fixed price table.
  • Before the three-key lookup, write numeric REAL PRICE = 0 when and only when both conditions hold:
    • normalized company identity is RAINBOW/AI or GUANGZHOU GO EASY; and
    • normalized RATE_CODE is LBMS or LBSM.
  • The zero-price exception ignores EFFECTIVE_RATE_AMOUNT. It writes daily REAL PRICE = 0 and therefore daily TOTAL PRICE = 0; the monthly row receives the same two numeric zeros. Other rate codes for those companies still require an exact normalized three-key price match.
  • Write the matched price-table 总价 directly as daily REAL PRICE, then calculate daily TOTAL PRICE = REAL PRICE * NO_OF_ROOMS * NIGHTS.
  • Validate the complete normalized three-field price key before processing. Any duplicate, including same-total duplicates unused by the current XML, fails the entire run.
  • Any unmatched retained reservation fails the entire run.

Channel routing

Standard routing uses the same normalized company identity described above. The original XML COMPANY_NAME remains unchanged in output cells.

Condition Worksheet
Normalized identity QBD QBD
Normalized identity LIAN TAI and RATE_CODE in {LBLT, LTLT} LIANTAI-FIT
Normalized identity LIAN TAI and other whitelisted RATE_CODE LIANTAI-GROUP
Normalized identity RAINBOW/AI or GUANGZHOU GO EASY (all rate codes) DY-AI-Easy-KB
Normalized identity FENGRUN FENGRUN
Any other company Sanitized actual company name

For other companies, trim the name, remove : \\ / ? * [ ], and truncate to 31 characters. Reuse the existing sheet for the same actual company. If a different company collides after sanitizing/truncation, append -2, -3, and so on while shortening the base to keep the result at most 31 characters.

Business date and filenames

  • Derive the date from the XML arrival/group date, not the runtime clock or upload filename.
  • Daily filename and sheet name: M.D.xlsx and M.D (for example 7.20.xlsx).
  • Month key: XML ARRIVAL year and month.
  • Monthly filename: 各渠道情况-YYYY年MM月-更新至M.D.xlsx.
  • 更新至 is the maximum ARRIVAL present anywhere in the monthly workbook, so an older backfill cannot move the filename backwards.

Monthly update

These rules apply only to legacy daily-monthly mode. The daily database-ingestion mode never reads or writes a monthly workbook.

  • When no monthly base is supplied, start from the bundled blank monthly template.
  • A supplied monthly base must already follow the current 19/20-column contract. Legacy 18-column monthly workbooks and the transitional 19-column KB layout are rejected rather than upgraded.
  • Keep different natural months in different workbooks.
  • Before inserting the current date, delete that ARRIVAL from every worksheet.
  • Insert the newly routed rows, then stably sort each sheet by ARRIVAL ascending.
  • Preserve original daily/XML order within the same date.
  • Preserve all other dates and their within-day order.
  • Existing sheets retain their formatting. New company sheets copy the standard channel-page header and style.
  • In every monthly row, write static REAL PRICE = daily REAL PRICE and static TOTAL PRICE = daily TOTAL PRICE. Independently enforce TOTAL PRICE = REAL PRICE * NO_OF_ROOMS * NIGHTS in both reports.
  • Only DY-AI-Easy-KB has KB100/晚/间); write it as static NO_OF_ROOMS * 100. Do not multiply KB by NIGHTS, and do not include KB in TOTAL PRICE.

Output types and formatting

  • Use the daily and monthly templates independently; never copy columns by position between them.
  • Headers are in row 1 and data starts in row 2.
  • Store ARRIVAL and DEPARTURE as real Excel dates with display format DD-MMM-YY.
  • Store adults, children, room count, nights, rates, real prices, totals, and KB as numbers.
  • Store confirmation numbers, room numbers, codes, names, comments, and other identifiers as text.
  • Write NIGHTS, REAL PRICE, TOTAL PRICE, and KB100/晚/间) as static values, never formulas.
  • Require ADULTS >= 0, CHILDREN >= 0, NO_OF_ROOMS > 0, EFFECTIVE_RATE_AMOUNT >= 0, and DEPARTURE >= ARRIVAL on every whitelist candidate. DEPARTURE = ARRIVAL produces NIGHTS = 0; DEPARTURE < ARRIVAL is invalid.
  • Preserve complete comment/trace text and leave allowed empty values blank.
  • Do not add filters, freezes, colors, or decorations absent from the templates.