feat: prepare ARR for controlled public deployment
This commit is contained in:
92
arr-opera-daily-ingest/references/business-rules.md
Normal file
92
arr-opera-daily-ingest/references/business-rules.md
Normal 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.
|
||||
136
arr-opera-daily-ingest/references/codex-result.schema.json
Normal file
136
arr-opera-daily-ingest/references/codex-result.schema.json
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
arr-opera-daily-ingest/references/error-contract.md
Normal file
59
arr-opera-daily-ingest/references/error-contract.md
Normal 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.
|
||||
85
arr-opera-daily-ingest/references/field-contracts.md
Normal file
85
arr-opera-daily-ingest/references/field-contracts.md
Normal 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).
|
||||
82
arr-opera-daily-ingest/references/structured-output.md
Normal file
82
arr-opera-daily-ingest/references/structured-output.md
Normal 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.
|
||||
327
arr-opera-daily-ingest/references/structured-result.schema.json
Normal file
327
arr-opera-daily-ingest/references/structured-result.schema.json
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
arr-opera-daily-ingest/references/价格对照.xlsx
Normal file
BIN
arr-opera-daily-ingest/references/价格对照.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user