fix: recognize corroborated empty Oracle associations
This commit is contained in:
1 parent
4559d5a642
commit
de9eb532f2
9 files changed
+735
-28
No files matched your search
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user