AutoRpa/选品流程/pipeline.py

122 lines
3.0 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from datetime import date
from rules import (
REASON_EXPIRY,
REASON_EXPIRY_UNPARSEABLE,
REASON_MARGIN,
REASON_NOT_SELF,
Allocation,
Candidate,
PurchaseLine,
Rejected,
allocate,
validate_purchase,
)
KIND_SUCCESS = "SUCCESS"
KIND_BUSINESS_FAIL = "BUSINESS_FAIL"
KIND_SYSTEM_FAIL = "SYSTEM_FAIL"
@dataclass
class MallOutcome:
mall_name: str
candidates: list[Candidate] | None = None
failed_reason: str | None = None
@dataclass
class SelectionResult:
kind: str
reason: str
allocations: list[Allocation]
rejected: list[Rejected]
malls: list[str]
mall_errors: dict[str, str] = field(default_factory=dict)
def run_selection(
purchase: PurchaseLine,
mall_outcomes: list[MallOutcome],
today: date | None = None,
) -> SelectionResult:
malls = [item.mall_name for item in mall_outcomes]
mall_errors = {
item.mall_name: item.failed_reason
for item in mall_outcomes
if item.failed_reason
}
row_reason = validate_purchase(purchase)
if row_reason:
return SelectionResult(
kind=KIND_BUSINESS_FAIL,
reason=row_reason,
allocations=[],
rejected=[],
malls=malls,
mall_errors=mall_errors,
)
merged: list[Candidate] = []
had_hit = False
for item in mall_outcomes:
if item.failed_reason:
continue
rows = item.candidates or []
if rows:
had_hit = True
merged.extend(rows)
allocation = allocate(merged, purchase, today=today)
if allocation.allocations:
return SelectionResult(
kind=KIND_SUCCESS,
reason="SELECTED",
allocations=allocation.allocations,
rejected=allocation.rejected,
malls=malls,
mall_errors=mall_errors,
)
if mall_errors:
return SelectionResult(
kind=KIND_SYSTEM_FAIL,
reason="SYSTEM_FAULT",
allocations=[],
rejected=allocation.rejected,
malls=malls,
mall_errors=mall_errors,
)
return SelectionResult(
kind=KIND_BUSINESS_FAIL,
reason=_business_reason(allocation.passed_before_fold, allocation.rejected, had_hit),
allocations=[],
rejected=allocation.rejected,
malls=malls,
mall_errors={},
)
def _business_reason(
passed_before_fold: list[Candidate],
rejected: list[Rejected],
had_hit: bool,
) -> str:
if passed_before_fold:
return "STOCK_INSUFFICIENT"
reasons = [item.reason for item in rejected]
if REASON_MARGIN in reasons:
return REASON_MARGIN
if REASON_EXPIRY in reasons or REASON_EXPIRY_UNPARSEABLE in reasons:
return REASON_EXPIRY
if REASON_NOT_SELF in reasons:
return REASON_NOT_SELF
if not had_hit:
return "NO_HIT"
return "NO_CANDIDATE_AFTER_RULES"