fix: recognize corroborated empty Oracle associations

This commit is contained in:
Wyndham ARR committed 2026-10-08 18:58:18 +08:00
1 parent 4559d5a642
commit de9eb532f2
9 files changed
+735 -28

No files matched your search

+42 -3
View File
@@ -27,6 +27,7 @@ from .data_client import DataReader, document, json_bytes, typed_id
VERSION = "arr-ohip-data/v1"
OPTIONAL_OMISSION_POLICY = "oracle-optional-association/v1"
FIELDS = ("BLOCK_CODE", "ADULTS", "CHILDREN", "COMPANY_NAME", "CONFIRMATION_NO", "DISP_ROOM_NO",
"EFFECTIVE_RATE_AMOUNT", "FULL_NAME", "RES_COMMENT", "NO_OF_ROOMS", "PRODUCTS", "RATE_CODE",
"ROOM_CATEGORY_LABEL", "ARRIVAL", "DEPARTURE")
@@ -167,6 +168,14 @@ def _block(search, detail, reader):
except base.CollectionError:
pass
day = _day(detail, reader.options.arrival_date)
# Successful Reservation/DailySummary reads omit this optional association
# for ordinary unblocked reservations. Require agreement across the search
# and arrival-day detail, rather than treating any missing code as empty.
searched_stay = search.get("roomStay")
if ("reservationBlock" not in day and type(searched_stay) is dict
and "reservationBlock" not in searched_stay
and "reservationBlock" not in detail["roomStay"]):
return ""
block = day.get("reservationBlock")
require(type(block) is dict, "missing_reservation_block")
require("hotelId" not in block or block["hotelId"] == reader.hotel_id, "block_association_hotel_mismatch")
@@ -174,6 +183,16 @@ def _block(search, detail, reader):
if identifiers == []:
search_block = search.get("roomStay", {}).get("reservationBlock")
require(type(search_block) is dict and search_block.get("blockIdList") == [], "block_association_changed")
associations = [block, search_block]
if "reservationBlock" in detail["roomStay"]:
associations.append(detail["roomStay"]["reservationBlock"])
for association in associations:
require(type(association) is dict and association.get("blockIdList") == [],
"block_association_changed")
require("hotelId" not in association or association["hotelId"] == reader.hotel_id,
"block_association_hotel_mismatch")
require("blockName" not in association or (type(association["blockName"]) is str
and not association["blockName"].strip()), "block_association_changed")
return ""
identity = typed_id(identifiers, "Block")
# An explicit code disagreement must remain visible, not be overwritten by
@@ -197,9 +216,28 @@ def _block(search, detail, reader):
return code
def _packages(detail, reader, related):
def _packages(search, detail, reader, related):
# Oracle maps retrieval of booked packages to getReservation with Packages
# fetch instructions. Its optional array may be omitted for zero packages.
# An explicit, valid search-indicator list must corroborate that reading;
# missing indicators, positive/unknown counts and malformed structures are
# still unresolved. A failed detail read never reaches this selector.
absent = "reservationPackages" not in detail
if absent or detail["reservationPackages"] == []:
indicators = search.get("reservationIndicators")
if absent or indicators is not None:
require(type(indicators) is list and all(type(item) is dict for item in indicators),
"missing_reservation_packages")
require(all(type(item.get("indicatorName")) is str and bool(item["indicatorName"].strip())
for item in indicators), "invalid_package_indicators")
package_counts = [item.get("count") for item in indicators if item.get("indicatorName") == "PACKAGEITEM"]
require(len(package_counts) <= 1, "ambiguous_package_indicator")
require(not package_counts or base.integer(package_counts[0], "invalid_package_indicator_count") == 0,
"package_indicator_detail_mismatch")
if absent:
return []
packages = detail.get("reservationPackages")
require(type(packages) is list and all(type(p) is dict for p in packages), "missing_reservation_packages")
require(type(packages) is list and all(type(p) is dict for p in packages), "invalid_reservation_packages")
result = []
related["reservation_packages"] = result
for item in packages:
@@ -330,7 +368,7 @@ def _record(search, detail, reader, sequence):
"FULL_NAME": observe(lambda: _name(detail, reader)),
"RES_COMMENT": observe(lambda: list(fields.reservation_gen_notes(detail))),
"NO_OF_ROOMS": observe(lambda: fields.arrival_number_of_units(detail, day)),
"PRODUCTS": observe(lambda: _packages(detail, reader, related)),
"PRODUCTS": observe(lambda: _packages(search, detail, reader, related)),
"RATE_CODE": observe(lambda: fields.arrival_rate_code(detail, day)),
"ROOM_CATEGORY_LABEL": observe(lambda: fields.agreed_room_type(search, detail, day)),
"ARRIVAL": observe(lambda: dates().arrival.isoformat()),
@@ -408,6 +446,7 @@ def collect(reader):
payload = {"version": VERSION, "hotel_id": options.hotel_id, "report_date": options.arrival_date,
"source_kind": reader.source_kind,
"status": status, "collection_complete": complete and not failed, "input_complete": ready and not failed,
"optional_omission_policy": OPTIONAL_OMISSION_POLICY,
"field_names": list(FIELDS), "trace_included": False, "atomic_snapshot": False, "records": rows}
raw = json_bytes(payload)
require(len(raw) <= MAX_DATA_BYTES, "data_output_too_large")