feat: prepare ARR for controlled public deployment

This commit is contained in:
Wyndham ARR
2026-07-29 16:38:05 +08:00
commit a701de9f0e
271 changed files with 48472 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# 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.

View File

@@ -0,0 +1,136 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ARR Opera daily processing result",
"type": "object",
"additionalProperties": false,
"required": [
"version",
"status",
"business_date",
"message",
"metrics",
"outputs",
"errors"
],
"properties": {
"version": { "type": "string", "const": "3.0" },
"status": { "type": "string", "enum": ["success", "failed"] },
"business_date": { "type": ["string", "null"], "format": "date" },
"message": { "type": "string" },
"metrics": {
"type": "object",
"additionalProperties": false,
"required": [
"source_rows",
"removed_by_rate_code",
"removed_as_duplicates",
"output_rows",
"channels"
],
"properties": {
"source_rows": { "type": "integer", "minimum": 0 },
"removed_by_rate_code": { "type": "integer", "minimum": 0 },
"removed_as_duplicates": { "type": "integer", "minimum": 0 },
"output_rows": { "type": "integer", "minimum": 0 },
"channels": {
"type": "array",
"items": { "$ref": "#/$defs/channel" }
}
}
},
"outputs": {
"type": "object",
"additionalProperties": false,
"required": [
"daily_report",
"structured_result",
"exception_report"
],
"properties": {
"daily_report": {
"type": ["string", "null"],
"pattern": "^[^/\\\\]+\\.xlsx$"
},
"structured_result": {
"type": ["string", "null"],
"pattern": "^[^/\\\\]+\\.json$"
},
"exception_report": {
"type": ["string", "null"],
"pattern": "^[^/\\\\]+\\.xlsx$"
}
}
},
"errors": {
"type": "array",
"items": { "$ref": "#/$defs/error" }
}
},
"allOf": [
{
"if": {
"properties": { "status": { "const": "success" } },
"required": ["status"]
},
"then": {
"properties": {
"business_date": { "type": "string", "format": "date" },
"outputs": {
"properties": {
"daily_report": { "type": "string" },
"structured_result": { "type": "string" },
"exception_report": { "type": "null" }
}
},
"errors": { "maxItems": 0 }
}
},
"else": {
"properties": {
"outputs": {
"properties": {
"daily_report": { "type": "null" },
"exception_report": { "type": "string" }
}
},
"errors": { "minItems": 1 }
}
}
}
],
"$defs": {
"channel": {
"type": "object",
"additionalProperties": false,
"required": ["worksheet", "rows"],
"properties": {
"worksheet": { "type": "string", "minLength": 1 },
"rows": { "type": "integer", "minimum": 0 }
}
},
"error": {
"type": "object",
"additionalProperties": false,
"required": [
"code",
"stage",
"source_location",
"company_name",
"rate_code",
"effective_rate_amount",
"confirmation_no",
"message"
],
"properties": {
"code": { "type": "string", "minLength": 1 },
"stage": { "type": "string", "minLength": 1 },
"source_location": { "type": ["string", "null"] },
"company_name": { "type": ["string", "null"] },
"rate_code": { "type": ["string", "null"] },
"effective_rate_amount": { "type": ["number", "null"] },
"confirmation_no": { "type": ["string", "null"] },
"message": { "type": "string", "minLength": 1 }
}
}
}
}

View File

@@ -0,0 +1,59 @@
# Error Contract
## Atomic invocation
Treat one XML invocation as an atomic file-processing unit.
On any input, XML, price, routing, write, structured-completeness, or independent-validation error:
- do not return a formal daily workbook;
- delete candidate daily/structured files from the failed attempt;
- keep the source XML unchanged;
- write `异常清单.xlsx`;
- write failed `result.json` when its requested path is valid;
- write failed `structured-result.json` when its requested path is valid;
- set `activation_eligible: false`.
The failed structured payload still preserves every parsed reservation and its final outcome. It never references a daily artifact.
## Exit codes
| Exit | Meaning |
|---:|---|
| `0` | Processing and independent validation succeeded |
| `2` | Business-data failure |
| `3` | Invocation/input-contract failure |
| `4` | Unexpected dependency/internal failure |
## Exception workbook
Columns:
`ERROR_CODE`, `STAGE`, `SOURCE_LOCATION`, `COMPANY_NAME`, `RATE_CODE`, `EFFECTIVE_RATE_AMOUNT`, `CONFIRMATION_NO`, `MESSAGE`.
Write one row per independently actionable error. Leave unavailable fields blank.
## Stable error families
- `INPUT_*`: path, extension, output isolation, unsafe declaration, or invocation error.
- `XML_*`: parse/root/structure/date/required-field/value error. Zero nights are valid; negative nights use `XML_NEGATIVE_NIGHTS`.
- `PRICE_*`: price table, ambiguity, duplicate normalized key, or unmatched candidate.
- `ROUTING_*`: ambiguous or unusable deterministic `channel_key`.
- `OUTPUT_*`: missing/malformed output, wrong header/type/value/order/hash/filename, formula, duplicate, or independent replay mismatch.
- `STRUCTURED_*` / `OUTPUT_STRUCTURED_*`: invalid path-free artifact, outcome reconciliation, derived key, channel fact, or record payload.
- `INTERNAL_ERROR`: concise unexpected failure without secrets.
## Success/failure invariants
- `status` is `success` or `failed`.
- Output references in `result.json` are basenames, never paths.
- Success lists daily and structured files, lists no exception file, and has no errors.
- Failure lists no daily file, lists an exception file, and has at least one error.
- `metrics.channels` is a deterministic array of `{ "worksheet": string, "rows": integer }`.
- Structured success requires exact artifact metadata, balanced five-outcome counts, no `validation_failed`/`price_unmatched`, and `activation_eligible: true`.
- Structured failure has `output_rows = 0`, no channel metrics, no daily artifact, a non-empty error array, and `activation_eligible: false`.
- Never place guest details in the public `message`.
## Database boundary
This Skill never executes database DDL or DML. ARR may start its transaction only after process exit `0`, Schema validation, and independent re-hashing. ARR activates a new same-day version only as the last step of a successful transaction; any failure must roll back and leave the previous current version unchanged.

View File

@@ -0,0 +1,85 @@
# Field Contracts
## XML structure
Root:
`/RES_DETAIL`
Reservations:
`/RES_DETAIL/LIST_G_GROUP_BY1/G_GROUP_BY1/LIST_G_RESERVATION/G_RESERVATION`
| XML source | Output field |
|---|---|
| `ADULTS` | `ADULTS` |
| `BLOCK_CODE` | `BLOCK_CODE` |
| `CF_CHILDREN` | `CHILDREN` |
| `COMPANY_NAME` | `COMPANY_NAME` |
| `CONFIRMATION_NO` | `CONFIRMATION_NO` |
| `DISP_ROOM_NO` | `DISP_ROOM_NO` |
| `EFFECTIVE_RATE_AMOUNT` | `EFFECTIVE_RATE_AMOUNT` |
| `FULL_NAME` | `FULL_NAME` |
| first non-empty `LIST_G_COMMENT_RESV_NAME_ID/.../RES_COMMENT` | `RES_COMMENT` |
| first non-empty `LIST_G_DEPT_ID/.../TRACE_TEXT` | `TRACE_TEXT` |
| `NO_OF_ROOMS` | `NO_OF_ROOMS` |
| `PRODUCTS` | `PRODUCTS` |
| `RATE_CODE` | `RATE_CODE` |
| `ROOM_CATEGORY_LABEL` | `ROOM_CATEGORY_LABEL` |
| `TRUNC_BEGIN` | `ARRIVAL` |
| `TRUNC_END` | `DEPARTURE` |
| computed | `NIGHTS` |
| price rule | `REAL PRICE` |
| computed | `TOTAL PRICE` |
Group date candidates are `GROUPBY1_SORT_COL` (`YYYYMMDD`) and `GROUPBY1_COL` (`DD-MM-YY`). Both must agree when both exist. Every whitelist candidate `ARRIVAL` must equal the single group business date.
## Daily XLSX: exactly 19 columns
1. `BLOCK_CODE`
2. `ADULTS`
3. `CHILDREN`
4. `COMPANY_NAME`
5. `CONFIRMATION_NO`
6. `DISP_ROOM_NO`
7. `EFFECTIVE_RATE_AMOUNT`
8. `FULL_NAME`
9. `RES_COMMENT`
10. `TRACE_TEXT`
11. `NO_OF_ROOMS`
12. `PRODUCTS`
13. `RATE_CODE`
14. `ROOM_CATEGORY_LABEL`
15. `ARRIVAL`
16. `DEPARTURE`
17. `NIGHTS`
18. `REAL PRICE`
19. `TOTAL PRICE`
`TOTAL PRICE = REAL PRICE * NO_OF_ROOMS * NIGHTS`. All derived values are static numbers.
## Direct XML lineage
- `source_sequence`: one-based XML reservation order.
- `source_location`: `reservation[N]`.
- `source_worksheet`: null.
- `source_row_no`: null.
- `channel_key`: deterministic downstream channel fact; it is not an XML source worksheet.
## Derived keys
```text
normalized_rate_code = upper(trim(rate_code))
group_code_key = upper(trim(res_comment))
company_key = deterministic company keyword normalization
```
When trimmed `RES_COMMENT` is empty, `group_code_key` is null and `booking_source_match_status` is `missing_group_code`. Never substitute `BLOCK_CODE`, confirmation number, or another value.
## Required whitelist-candidate values
`RATE_CODE`, `COMPANY_NAME`, `CONFIRMATION_NO`, `DISP_ROOM_NO`, `EFFECTIVE_RATE_AMOUNT`, `FULL_NAME`, `ADULTS`, `CF_CHILDREN`, `NO_OF_ROOMS`, `ARRIVAL`, and `DEPARTURE` must be present and valid.
`BLOCK_CODE`, `PRODUCTS`, `ROOM_CATEGORY_LABEL`, `RES_COMMENT`, and `TRACE_TEXT` may be blank.
The complete record field list and conditional nullability rules are authoritative in [structured-result.schema.json](structured-result.schema.json).

View File

@@ -0,0 +1,82 @@
# Structured Result Contract
## Purpose
`structured-result.json` version `3.0` is the daily fact payload consumed by the trusted ARR adapter. It contains every XML source reservation, including filtered, duplicate, invalid, and unmatched rows.
`result.json` is the Agent/front-end run result. Do not add database records to it. Do not reconstruct database rows from the XLSX.
## Transport boundary
- `fetch_oss_file` and its OSS credentials belong to the Agent runtime, not this Skill.
- The Agent passes the fetched local XML path into the processor.
- ARR already knows the job/source-file identity from the upload context and binds that identity to this payload after comparing the XML hash.
- Structured artifact objects contain no local absolute path, OSS key, signed URL, AccessKey, or database credential.
- Agent-local paths are ephemeral orchestration data and must not be persisted as business facts.
## Batch fields
The payload includes:
- `business_date`
- `processor_version`
- `rule_set_sha256`
- `result_schema_version`
- `source_rows`
- `removed_by_rate_code`
- `removed_as_duplicates`
- `output_rows`
- five-outcome reconciliation
- channel counts
- path-free XML/daily/result/exception artifact metadata
- every XML source record in original order
- structured errors
`rule_set_sha256` hashes only resources that determine daily processing: Skill instructions, processor, independent validator, daily contracts/Schemas, price table, and daily template.
The structured file does not hash itself, avoiding self-hash recursion.
## Outcomes and lineage
Allowed outcomes:
- `retained`
- `excluded_rate_code`
- `duplicate`
- `validation_failed`
- `price_unmatched`
Duplicates point to an earlier `source_sequence`. `decision_codes` explain deterministic choices.
For direct XML:
- `source_sequence` is continuous and one-based;
- `source_location = reservation[N]`;
- `source_worksheet = null`;
- `source_row_no = null`.
## Finance/booking semantics
- `effective_rate_amount` comes from XML/daily `EFFECTIVE_RATE_AMOUNT`.
- `real_price` comes from the approved price rule.
- `total_price = real_price * no_of_rooms * nights`.
- `nights` may be zero; it may not be negative for retained rows.
- `kb_amount = no_of_rooms * 100` only when `channel_key = DY-AI-Easy-KB`.
- `room_category_label` remains the Opera actual room category.
- `group_code_key` comes only from normalized `RES_COMMENT`.
- Non-empty Group Codes begin with `booking_source_match_status = not_checked`; empty values use `missing_group_code`.
ARR performs the later exact booking lookup:
```text
structured group_code_key
= booking.booking_source_rows.group_code_key
```
It may then expose `booking.booking_source_rows.hotel_raw` as the original booking `type_of_room` text. It must not overwrite `room_category_label`.
## Activation boundary
`activation_eligible: true` means only that the deterministic file pipeline passed. It does not mean database commit succeeded.
ARR must revalidate the Schema and hashes, write file/version/record/channel/lookup rows in one transaction, and switch the current daily version last. A rerun of the same business date creates a new immutable version; only the successful committed version becomes current.

View File

@@ -0,0 +1,327 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://arr.local/schemas/opera-daily-structured-result-v3.json",
"title": "ARR Opera daily finance-ready structured result",
"type": "object",
"additionalProperties": false,
"required": [
"result_schema_version",
"status",
"activation_eligible",
"ingestion_mode",
"business_date",
"processor_version",
"rule_set_sha256",
"source_rows",
"removed_by_rate_code",
"removed_as_duplicates",
"output_rows",
"outcome_counts",
"channels",
"artifacts",
"records",
"errors"
],
"properties": {
"result_schema_version": { "type": "string", "const": "3.0" },
"status": { "type": "string", "enum": ["success", "failed"] },
"activation_eligible": { "type": "boolean" },
"ingestion_mode": { "type": "string", "const": "opera_xml" },
"business_date": { "type": ["string", "null"], "format": "date" },
"processor_version": { "type": "string", "minLength": 1 },
"rule_set_sha256": { "$ref": "#/$defs/sha256" },
"source_rows": { "type": "integer", "minimum": 0 },
"removed_by_rate_code": { "type": "integer", "minimum": 0 },
"removed_as_duplicates": { "type": "integer", "minimum": 0 },
"output_rows": { "type": "integer", "minimum": 0 },
"outcome_counts": {
"type": "object",
"additionalProperties": false,
"required": [
"duplicate",
"excluded_rate_code",
"price_unmatched",
"retained",
"validation_failed"
],
"properties": {
"duplicate": { "type": "integer", "minimum": 0 },
"excluded_rate_code": { "type": "integer", "minimum": 0 },
"price_unmatched": { "type": "integer", "minimum": 0 },
"retained": { "type": "integer", "minimum": 0 },
"validation_failed": { "type": "integer", "minimum": 0 }
}
},
"channels": {
"type": "array",
"items": { "$ref": "#/$defs/channel" }
},
"artifacts": {
"type": "object",
"additionalProperties": false,
"required": [
"source_xml",
"daily_report",
"result_json",
"exception_report"
],
"properties": {
"source_xml": { "$ref": "#/$defs/nullableArtifact" },
"daily_report": { "$ref": "#/$defs/nullableArtifact" },
"result_json": { "$ref": "#/$defs/nullableArtifact" },
"exception_report": { "$ref": "#/$defs/nullableArtifact" }
}
},
"records": {
"type": "array",
"items": { "$ref": "#/$defs/record" }
},
"errors": {
"type": "array",
"items": { "$ref": "#/$defs/error" }
}
},
"allOf": [
{
"if": {
"properties": { "status": { "const": "success" } },
"required": ["status"]
},
"then": {
"properties": {
"activation_eligible": { "const": true },
"business_date": { "type": "string", "format": "date" },
"errors": { "maxItems": 0 },
"artifacts": {
"properties": {
"source_xml": { "$ref": "#/$defs/artifact" },
"daily_report": { "$ref": "#/$defs/artifact" },
"result_json": { "$ref": "#/$defs/artifact" },
"exception_report": { "type": "null" }
}
}
}
},
"else": {
"properties": {
"activation_eligible": { "const": false },
"output_rows": { "const": 0 },
"channels": { "maxItems": 0 },
"errors": { "minItems": 1 },
"artifacts": {
"properties": {
"daily_report": { "type": "null" },
"exception_report": { "$ref": "#/$defs/artifact" }
}
}
}
}
}
],
"$defs": {
"sha256": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
},
"artifact": {
"type": "object",
"additionalProperties": false,
"required": [
"file_kind",
"original_filename",
"sha256",
"byte_size",
"mime_type"
],
"properties": {
"file_kind": {
"type": "string",
"enum": [
"opera_xml",
"daily_xlsx",
"result_json",
"exception_xlsx"
]
},
"original_filename": { "type": "string", "minLength": 1 },
"sha256": { "$ref": "#/$defs/sha256" },
"byte_size": { "type": "integer", "minimum": 0 },
"mime_type": { "type": "string", "minLength": 1 }
}
},
"nullableArtifact": {
"oneOf": [
{ "$ref": "#/$defs/artifact" },
{ "type": "null" }
]
},
"channel": {
"type": "object",
"additionalProperties": false,
"required": ["worksheet", "rows"],
"properties": {
"worksheet": { "type": "string", "minLength": 1 },
"rows": { "type": "integer", "minimum": 0 }
}
},
"record": {
"type": "object",
"additionalProperties": false,
"required": [
"source_sequence",
"source_location",
"source_worksheet",
"source_row_no",
"outcome",
"decision_codes",
"duplicate_of_source_sequence",
"adults",
"children",
"block_code",
"no_of_rooms",
"company_name",
"company_key",
"confirmation_no",
"disp_room_no",
"effective_rate_amount",
"full_name",
"res_comment",
"group_code_key",
"booking_source_match_status",
"trace_text",
"products",
"rate_code",
"normalized_rate_code",
"room_category_label",
"arrival",
"departure",
"nights",
"real_price",
"total_price",
"kb_amount",
"channel_key",
"pricing_method"
],
"properties": {
"source_sequence": { "type": "integer", "minimum": 1 },
"source_location": { "type": "string", "minLength": 1 },
"source_worksheet": { "type": "null" },
"source_row_no": { "type": "null" },
"outcome": {
"type": "string",
"enum": [
"retained",
"excluded_rate_code",
"duplicate",
"validation_failed",
"price_unmatched"
]
},
"decision_codes": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 }
},
"duplicate_of_source_sequence": {
"type": ["integer", "null"],
"minimum": 1
},
"adults": { "type": ["integer", "null"] },
"children": { "type": ["integer", "null"] },
"block_code": { "type": "string" },
"no_of_rooms": { "type": ["integer", "null"] },
"company_name": { "type": "string" },
"company_key": { "type": ["string", "null"], "minLength": 1 },
"confirmation_no": { "type": "string" },
"disp_room_no": { "type": "string" },
"effective_rate_amount": { "type": ["number", "null"] },
"full_name": { "type": "string" },
"res_comment": { "type": "string" },
"group_code_key": { "type": ["string", "null"], "minLength": 1 },
"booking_source_match_status": {
"type": "string",
"enum": ["missing_group_code", "not_checked"]
},
"trace_text": { "type": "string" },
"products": { "type": "string" },
"rate_code": { "type": "string" },
"normalized_rate_code": { "type": ["string", "null"], "minLength": 1 },
"room_category_label": { "type": "string" },
"arrival": { "type": ["string", "null"], "format": "date" },
"departure": { "type": ["string", "null"], "format": "date" },
"nights": { "type": ["integer", "null"] },
"real_price": { "type": ["number", "null"] },
"total_price": { "type": ["number", "null"] },
"kb_amount": { "type": ["number", "null"] },
"channel_key": { "type": ["string", "null"], "minLength": 1 },
"pricing_method": {
"type": ["string", "null"],
"enum": ["zero_price_exception", "price_reference_exact", null]
}
},
"allOf": [
{
"if": {
"properties": { "outcome": { "const": "duplicate" } },
"required": ["outcome"]
},
"then": {
"properties": {
"duplicate_of_source_sequence": {
"type": "integer",
"minimum": 1
}
}
},
"else": {
"properties": {
"duplicate_of_source_sequence": { "type": "null" }
}
}
},
{
"if": {
"properties": { "outcome": { "const": "retained" } },
"required": ["outcome"]
},
"then": {
"properties": {
"adults": { "type": "integer", "minimum": 0 },
"children": { "type": "integer", "minimum": 0 },
"no_of_rooms": { "type": "integer", "minimum": 1 },
"effective_rate_amount": { "type": "number", "minimum": 0 },
"nights": { "type": "integer", "minimum": 0 },
"real_price": { "type": "number", "minimum": 0 },
"total_price": { "type": "number", "minimum": 0 },
"kb_amount": { "type": ["number", "null"], "minimum": 0 }
}
}
}
]
},
"error": {
"type": "object",
"additionalProperties": false,
"required": [
"code",
"stage",
"source_location",
"company_name",
"rate_code",
"effective_rate_amount",
"confirmation_no",
"message"
],
"properties": {
"code": { "type": "string", "minLength": 1 },
"stage": { "type": "string", "minLength": 1 },
"source_location": { "type": ["string", "null"] },
"company_name": { "type": ["string", "null"] },
"rate_code": { "type": ["string", "null"] },
"effective_rate_amount": { "type": ["number", "null"] },
"confirmation_no": { "type": ["string", "null"] },
"message": { "type": "string", "minLength": 1 }
}
}
}
}

Binary file not shown.