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,230 @@
"""Typed contracts for database-backed monthly channel reports."""
from __future__ import annotations
import hashlib
import json
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from typing import Any, Dict, Mapping, Optional, Sequence, Tuple
PROCESSOR_VERSION = "1.0.0"
RESULT_SCHEMA_VERSION = "1.0"
XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
CHANNEL_HEADERS: Tuple[str, ...] = (
"ARRIVAL",
"DEPARTURE",
"NIGHTS",
"ADULTS",
"CHILDREN",
"BLOCK_CODE",
"NO_OF_ROOMS",
"COMPANY_NAME",
"CONFIRMATION_NO",
"DISP_ROOM_NO",
"RATE_AMOUNT",
"FULL_NAME",
"RES_COMMENT",
"TRACE_TEXT",
"PRODUCTS",
"RATE_CODE",
"ROOM_CATEGORY_LABEL",
"REAL PRICE",
"TOTAL PRICE",
)
KB_HEADER = "KB100/晚/间)"
KB_SHEET = "DY-AI-Easy-KB"
KB_CHANNEL_HEADERS: Tuple[str, ...] = CHANNEL_HEADERS + (KB_HEADER,)
STANDARD_SHEETS: Tuple[str, ...] = (
"LIANTAI-GROUP",
"LIANTAI-FIT",
"QBD",
KB_SHEET,
"FENGRUN",
)
class ErrorCode:
REQUEST_INVALID = "MONTHLY_REPORT_REQUEST_INVALID"
SOURCE_INVALID = "MONTHLY_REPORT_SOURCE_INVALID"
SOURCE_SNAPSHOT_STALE = "MONTHLY_REPORT_SOURCE_SNAPSHOT_STALE"
OUTPUT_VALIDATION_FAILED = "MONTHLY_REPORT_OUTPUT_VALIDATION_FAILED"
PUBLISH_FAILED = "MONTHLY_REPORT_PUBLISH_FAILED"
DATABASE_FAILED = "MONTHLY_REPORT_DATABASE_FAILED"
INTERNAL_ERROR = "MONTHLY_REPORT_INTERNAL_ERROR"
class MonthlyReportError(RuntimeError):
def __init__(self, code: str, safe_message: str):
super().__init__(safe_message)
self.code = code
self.safe_message = safe_message
@dataclass(frozen=True)
class DailyVersionPin:
business_date: date
daily_version_id: int
@dataclass(frozen=True)
class ChannelObservation:
business_date: date
daily_version_id: int
worksheet: str
worksheet_order: Optional[int]
row_count: int
@dataclass(frozen=True)
class MonthlyFact:
daily_record_id: int
daily_version_id: int
business_date: date
channel_key: str
arrival: date
departure: date
nights: int
adults: int
children: int
block_code: Optional[str]
no_of_rooms: int
company_name: str
confirmation_no: str
disp_room_no: str
effective_rate_amount: Decimal
full_name: str
res_comment: Optional[str]
trace_text: Optional[str]
products: Optional[str]
rate_code: str
room_category_label: Optional[str]
real_price: Decimal
total_price: Decimal
kb_amount: Optional[Decimal]
@staticmethod
def _decimal_text(value: Decimal) -> str:
rendered = format(value, "f")
if "." in rendered:
rendered = rendered.rstrip("0").rstrip(".")
return rendered or "0"
def to_payload(self, include_kb: bool) -> Dict[str, Any]:
payload: Dict[str, Any] = {
"ARRIVAL": self.arrival.isoformat(),
"DEPARTURE": self.departure.isoformat(),
"NIGHTS": self.nights,
"ADULTS": self.adults,
"CHILDREN": self.children,
"BLOCK_CODE": self.block_code or "",
"NO_OF_ROOMS": self.no_of_rooms,
"COMPANY_NAME": self.company_name,
"CONFIRMATION_NO": self.confirmation_no,
"DISP_ROOM_NO": self.disp_room_no,
"RATE_AMOUNT": self._decimal_text(self.effective_rate_amount),
"FULL_NAME": self.full_name,
"RES_COMMENT": self.res_comment or "",
"TRACE_TEXT": self.trace_text or "",
"PRODUCTS": self.products or "",
"RATE_CODE": self.rate_code,
"ROOM_CATEGORY_LABEL": self.room_category_label or "",
"REAL PRICE": self._decimal_text(self.real_price),
"TOTAL PRICE": self._decimal_text(self.total_price),
}
if include_kb:
if self.kb_amount is None:
raise MonthlyReportError(
ErrorCode.SOURCE_INVALID,
"KB channel source amount is missing",
)
payload[KB_HEADER] = self._decimal_text(self.kb_amount)
return payload
@dataclass(frozen=True)
class MonthlySnapshot:
facts: Tuple[MonthlyFact, ...]
daily_versions: Tuple[DailyVersionPin, ...]
channel_observations: Tuple[ChannelObservation, ...]
preferred_channel_order: Tuple[str, ...] = tuple()
@dataclass(frozen=True)
class ChannelSheet:
worksheet: str
worksheet_order: int
rows: Tuple[MonthlyFact, ...]
@property
def headers(self) -> Tuple[str, ...]:
return KB_CHANNEL_HEADERS if self.worksheet == KB_SHEET else CHANNEL_HEADERS
def to_payload(self) -> Dict[str, Any]:
include_kb = self.worksheet == KB_SHEET
return {
"worksheet": self.worksheet,
"worksheet_order": self.worksheet_order,
"headers": list(self.headers),
"rows": [row.to_payload(include_kb) for row in self.rows],
}
@dataclass(frozen=True)
class MonthlyReport:
report_year: int
report_month: int
as_of_date: date
filename: str
channels: Tuple[ChannelSheet, ...]
daily_versions: Tuple[DailyVersionPin, ...]
@property
def row_count(self) -> int:
return sum(len(channel.rows) for channel in self.channels)
@property
def channel_manifest(self) -> Tuple[Tuple[str, int, int], ...]:
return tuple(
(channel.worksheet, channel.worksheet_order, len(channel.rows))
for channel in self.channels
)
def to_workbook_payload(self) -> Dict[str, Any]:
return {
"schema_version": RESULT_SCHEMA_VERSION,
"report_year": self.report_year,
"report_month": self.report_month,
"as_of_date": self.as_of_date.isoformat(),
"filename": self.filename,
"channels": [channel.to_payload() for channel in self.channels],
}
def rule_set_sha256() -> str:
payload: Mapping[str, Any] = {
"processor_version": PROCESSOR_VERSION,
"result_schema_version": RESULT_SCHEMA_VERSION,
"headers": CHANNEL_HEADERS,
"kb_header": KB_HEADER,
"standard_sheets": STANDARD_SHEETS,
"source": "finance.v_active_daily_facts",
"row_order": ["arrival", "daily_record_id"],
"unknown_channel_order": [
"current_report_manifest",
"daily_worksheet_first_seen",
"lexical_fallback",
],
"filename": "各渠道情况-YYYY年MM月-更新至M.D.xlsx",
"formula_policy": "no_formulas",
}
return hashlib.sha256(
json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8")
).hexdigest()
def immutable_tuple(values: Sequence[Any]) -> Tuple[Any, ...]:
return tuple(values)