dodil/erp-brick-sales-order

DODIL ERP brick: sales order

src/brick_sales_order/infrastructure/ai_steps.py

"""AI steps (the AI-step contract): a step only SUGGESTS. Its output lands in so_suggestion and reaches an order only
when a person accepts it; the owner holds each step's mode (off | suggest) under so:settings:controls, and turning a
step on is recorded as the owner's consent (who, when) in the dated so_settings row.

The brick never calls a model itself. The step runner (the capability's provider, or the order inbox's poller) reads the
PO and hands its output to `ai_order_from_po` / `ai_check_po`; with no provider the fallback is the rep's own work:
the PO attached to an empty draft, or comparing by eye. Tests never call a model.
"""
from brick_sales_order.domain.values import AI_KEYS

AI_STEPS = {
    "ai_order_from_po": {
        "mode": "off", "modes_allowed": ["off", "suggest"], "safe_for_auto": False,
        "fallback": "the PO is attached to an empty draft for the rep",
        "benefit": "a customer's PO by email becomes a checked draft in seconds instead of ten minutes of typing",
        "audit": "so_suggestion: model, the PO's object key, the output, and who accepted, changed or rejected what, when",
    },
    "ai_check_po": {
        "mode": "off", "modes_allowed": ["off", "suggest"], "safe_for_auto": False,
        "fallback": "the rep compares by eye",
        "benefit": "a PO that quietly changes a price or a quantity is caught before confirmation, not at invoicing",
        "audit": "so_suggestion, as for ai_order_from_po",
    },
}


def mode_on(settings, step: str, day) -> str:
    """The step's mode in force on a date: the owner's dated control row, else off."""
    return settings.value_on(AI_KEYS[step], day) or "off"