8.1 KiB
Business Rules
Fixed processing order
- Validate the invocation and parse one fixed
RES_DETAILXML. - Require exactly one XML business date and keep one audit record for every
G_RESERVATIONin XML order. - First exclude explicitly cancelled reservations as
excluded_cancelledwithRESERVATION_CANCELLED_EXCLUDED, then exclude an explicitROOM_CATEGORY_LABELofPMasexcluded_pmwithROOM_TYPE_PM_EXCLUDED. Preserve the original row and source sequence. Other records requireRATE_CODEso whitelist membership is knowable. - Classify a trimmed, uppercased rate code outside the whitelist as
excluded_rate_code. - Validate every whitelist candidate; classify invalid rows as
validation_failed. - Deduplicate valid candidates by
DISP_ROOM_NO + ARRIVAL; retain the first XML occurrence and classify later occurrences asduplicatepointing to the first source sequence. - Compute integer
NIGHTS = DEPARTURE - ARRIVAL. Zero is legal; negative is invalid. - Validate the complete fixed price table, then apply the approved zero-price exception or exact three-key match. Classify unmatched candidates as
price_unmatched. - Assign
channel_keyand conditionalkb_amountas row facts. - If all actionable errors are
PRICE_UNMATCHED, emitreview_required: exact fixed-price rows become controlledcandidaterows, unmatched rows stay explicit, issues are grouped by normalized company, rate code and Opera price, and no XLSX is made. - Otherwise generate the 19-column daily XLSX,
result.json, and path-freestructured-result.jsonfrom the same in-memory records. - 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:
- trim, uppercase, and keep only
A-Zand0-9; - use fixed substring keywords:
LIANTAI→LIAN TAIQBD→QBDRAINBOW→RAINBOW/AIFENGRUN→FENGRUNHANATOURorHANA→HANA TOURHONGTAI→HONGTAIGUANGZHOUGOEASYorGOEASY→GUANGZHOU GO EASY
- when no keyword matches, use the compact normalized name;
- 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_CODEwithupper(trim(value)). - Compare amounts numerically, so
900equals900.00. - Reject negative reference amounts/totals, invalid rows, ambiguous companies, and every duplicate normalized
COMPANY + RATE_CODE + Opera amountkey. - Before the normal lookup, set numeric
REAL PRICE = 0only when:- company identity is
RAINBOW/AIorGUANGZHOU GO EASY; and - rate code is
LBMSorLBSM.
- company identity is
- The exception ignores
EFFECTIVE_RATE_AMOUNT. - Every other retained row must exactly match normalized
COMPANY_NAME + RATE_CODE + EFFECTIVE_RATE_AMOUNT. - Write reference
总价as staticREAL 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_priceis a non-negative two-decimal value (including0.00), usespricing_method = manual_reviewandMANUAL_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.xlsxandM.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, andDEPARTURE >= ARRIVAL. DEPARTURE = ARRIVALyieldsNIGHTS = 0andTOTAL 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.
Cancellation exclusion approved 2026-10-08
Processors4.3.0 and later exclude cancelled reservations whether or not a room is assigned, before all business-field checks and room/date deduplication. OHIP uses reservation_status, retained from matching search/detail responses. XML uses the explicitly named RESV_STATUS, RESERVATION_STATUS, SHORT_RESV_STATUS fields. Trimmed, case-insensitive CXL, CANCELLED, CANCELED identify cancellation. A cancellation marker conflicting with a different nonempty status is not sufficient to exclude. Missing/unknown statuses (including CA/CD), NoShow and reservation types are not interpreted as cancellation. The retired direct-MCP v3 replay keeps its old rules.
Every source row stays in the structured audit. outcome_counts.excluded_cancelled is always present for4.3.0 and later, including zero; it is separate from removed_by_rate_code. Excluded cancellations have no pricing, amount or channel facts and never participate in daily or monthly totals. Original source files and captured HTTP responses remain read-only.
PM room-type scope
Processor4.4.0 excludes records only when the explicit ROOM_CATEGORY_LABEL, after trimming and case normalization, equals PM. This rule is shared by XML and direct OHIP data. Cancellation takes precedence: a cancelled PM record is counted only as excluded_cancelled. PM exclusion precedes rate-code/required-field checks, room/date deduplication and pricing, so a PM row cannot consume a real room's duplicate key or produce a manual-price issue. A room number, zero price, guest count, missing room type, or a similar label such as PM1 never proves PM.
The source record and source sequence remain audited with excluded_pm and exactly ROOM_TYPE_PM_EXCLUDED; the original room-type value retains its case after normal text trimming. outcome_counts.excluded_pm is always present for4.4.0, including zero, and remains separate from cancelled/rate-code exclusions. PM rows have no price, total, KB, channel or pricing-method facts and do not enter daily/monthly totals or manual pricing. The frozen direct-MCP v3 path retains its original scope and never emits the new outcome.