Files

93 lines
3.5 KiB
Python

"""Explicit row values for ARR processing XML, not an OHIP mapping policy.
Callers must already have resolved every value, note/trace selection and row
order from independently accepted source rules. No fields have defaults.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import date
import re
from .collect_arr_source import require
VERSION = "arr-processing-xml/v1"
# Match the existing frozen handoff/upload boundary, not the larger diagnostic
# native-XML comparator limit. A serialization result must fit that entrypoint.
MAX_BYTES = 25 * 1024 * 1024
MAX_ROWS = 10000
MAX_ELEMENTS = 500000
# Declarative correspondence only; shared by serializer and independent parser.
SCALARS = (
("block_code", "BLOCK_CODE"), ("adults", "ADULTS"), ("children", "CF_CHILDREN"),
("company_name", "COMPANY_NAME"), ("confirmation_no", "CONFIRMATION_NO"),
("room_no", "DISP_ROOM_NO"), ("effective_rate", "EFFECTIVE_RATE_AMOUNT"),
("full_name", "FULL_NAME"), ("no_of_rooms", "NO_OF_ROOMS"), ("products", "PRODUCTS"),
("rate_code", "RATE_CODE"), ("room_category_label", "ROOM_CATEGORY_LABEL"),
("arrival", "TRUNC_BEGIN"), ("departure", "TRUNC_END"),
)
@dataclass(frozen=True)
class SourceRow:
reservation_id: str
block_code: str
adults: str
children: str
company_name: str
confirmation_no: str
room_no: str
effective_rate: str
full_name: str
no_of_rooms: str
products: str
rate_code: str
room_category_label: str
arrival: str
departure: str
notes: tuple[str, ...]
traces: tuple[str, ...]
@dataclass(frozen=True)
class SourceDocument:
hotel_id: str
report_date: date
rows: tuple[SourceRow, ...]
def _text(value):
require(type(value) is str, "arr_xml_explicit_text_required")
require(all(char in "\t\n\r" or 0x20 <= ord(char) <= 0xD7FF
or 0xE000 <= ord(char) <= 0xFFFD or 0x10000 <= ord(char) <= 0x10FFFF
for char in value), "arr_xml_invalid_character")
return len(value.encode("utf-8"))
def validate_document(document: SourceDocument) -> None:
"""Validate transport shape only; existing processor owns business rules.
An explicit blank is retained even on a required whitelist field. A missing
value/None is rejected. This preserves invalid and non-whitelist source rows so
serialization cannot silently remove records to make processing succeed.
"""
require(type(document) is SourceDocument, "arr_xml_document_required")
require(type(document.hotel_id) is str and bool(re.fullmatch(r"[A-Za-z0-9_-]{1,64}", document.hotel_id)),
"arr_xml_hotel_required")
require(type(document.report_date) is date, "arr_xml_explicit_date_required")
require(type(document.rows) is tuple and 1 <= len(document.rows) <= MAX_ROWS, "arr_xml_record_count")
size, elements = 0, 6
for row in document.rows:
require(type(row) is SourceRow, "arr_xml_row_required")
size += _text(row.reservation_id)
require(bool(row.reservation_id.strip()), "arr_xml_reservation_identity_required")
for attribute, _ in SCALARS:
size += _text(getattr(row, attribute))
for collection in (row.notes, row.traces):
require(type(collection) is tuple, "arr_xml_explicit_collection_required")
for value in collection:
size += _text(value)
elements += 19 + 2 * (len(row.notes) + len(row.traces))
require(size <= MAX_BYTES and elements <= MAX_ELEMENTS, "arr_xml_input_budget_exceeded")