Files
2026-08-06 22:40:18 +08:00

5.7 KiB

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. If all actionable errors are PRICE_UNMATCHED, emit review_required: exact fixed-price rows become controlled candidate rows, unmatched rows stay explicit, issues are grouped by normalized company, rate code and Opera price, and no XLSX is made.
  11. Otherwise generate the 19-column daily XLSX, result.json, and path-free structured-result.json from the same in-memory records.
  12. Independently replay XML, rules, prices, routing, workbook (when present), artifacts, outcomes, and structured records before reporting a final result.

Only a non-empty error set composed entirely of PRICE_UNMATCHED may become review_required. Any XML, fixed-table, routing, output, structural, or mixed error remains a formal failure. 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:
    • LIANTAILIAN TAI
    • QBDQBD
    • RAINBOWRAINBOW/AI
    • FENGRUNFENGRUN
    • HANATOUR or HANAHANA TOUR
    • HONGTAIHONGTAI
    • GUANGZHOUGOEASY or GOEASYGUANGZHOU 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 normally requires review only when it is the sole actionable error family. The review artifact contains no price substitution.
  • A final review replay may price only the exact unmatched keys listed in its frozen manifest. real_price is a non-negative two-decimal value (including 0.00), uses pricing_method = manual_review and MANUAL_PRICE_APPLIED, and never changes the fixed table or any XML field.

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.