Робот сверял платежи и нашёл перевод без номера счёта. Он выбрал похожего контрагента и закрыл дебиторку. Бухгалтер обнаружил это при сверке месяца. Агент сэкономил четыре минуты и подарил два дня раскопок. Такой обмен в бухгалтерии называется убытком, а в презентации — автоматизацией.
Workflow с ограниченным выбором
Для back office обычно нужен не свободный агент, а конечный автомат. Код определяет состояния и допустимые переходы; модель извлекает данные и предлагает решение внутри конкретного шага.
RECEIVED -> PARSED -> MATCH_CANDIDATES
-> AUTO_MATCHED | NEEDS_REVIEW
-> POSTED -> VERIFIEDasync def reconcile(payment):
parsed = await extract_payment(payment)
candidates = await invoices.find(amount=parsed.amount,
counterparty=parsed.counterparty)
decision = await rank_matches(parsed, candidates)
if decision.score < 0.98 or decision.margin < 0.10:
return await queue_review(payment, decision)
posting = await ledger.preview(decision.invoice_id, parsed.amount)
await approvals.require(posting, policy="reconciliation")
return await ledger.commit(posting, idempotency_key=payment.id)Порог строят на размеченной истории, а не на слове confidence. Для денег и юридических статусов нужен preview и детерминированная проверка инвариантов. Каждый run имеет лимит шагов, времени и стоимости. Возобновление идёт из сохранённого состояния, поэтому retry не повторяет уже проведённую операцию.
Failure modes
Документ приходит дважды, внешний API отвечает после таймаута, справочник обновляется посреди run, агент зацикливается между двумя инструментами. Ещё хуже тихое изменение политики: вчера расхождение в рубль допускалось, сегодня нет. Версионируйте policy вместе с run и ведите append-only audit log.
Порог автоматического решения строится на истории
Число 0.98 в коде выше — не константа из книги, а результат разметки. Порядок работы всегда одинаковый:
- Возьмите несколько сотен исторических операций, где известен правильный ответ.
- Прогоните на них своё ранжирование и постройте кривую: доля автоматических решений против доли ошибок среди них.
- Выберите точку по цене ошибки, а не по красоте цифры. Для дебиторки одна ошибка стоит двух дней сверки, значит, доля ошибок должна быть близка к нулю, даже ценой маленькой автоматизации.
- Пересчитывайте порог после каждого изменения данных, справочников или модели.
Второе условие в примере — margin, разрыв между лучшим и вторым кандидатом. Оно важнее абсолютного score: два одинаково подходящих счёта означают не уверенность, а неоднозначность, и это ровно тот случай, когда решение принимает человек.
Правило главы
Чем дороже действие, тем меньше свободы остаётся модели и тем больше решения принадлежит workflow.
Чеклист главы
Практикум
Задача 51.1 — сверка платежей с автоматом состояний. ⭑⭑⭑
Смоделируйте сверку 200 платежей, включая дубли, частичные суммы и неоднозначных контрагентов.
Критерии приёмки:
Подсказки:
- Частичные суммы и платежи «одной строкой за три счёта» дают больше половины ручной очереди: заложите их в набор сразу.
- Убийство воркера удобно делать прямо в тесте: это самый быстрый способ найти операции без идемпотентности.
Задача 51.2 — порог по цене ошибки. ⭑⭑
Постройте кривую «доля автоматизации против доли ошибок» и выберите порог осознанно.
Критерии приёмки:
Подсказки:
- Цену ошибки спросите у бухгалтерии в часах разбора: это единственная цифра, которая закончит спор о пороге.
- Если при приемлемой доле ошибок автоматизируется меньше трети операций — это нормальный результат первого запуска, а не провал.