Andrey Epifancev 8940f32ae3 fix: пары ВГО — суммы со знаком, процент в упаковке, deviation_amount (ревью #175, AURORA-1536)
- суммы сторон со знаком, как в шахматке; строки с суммой < 0 в упаковку не идут, причина NEGATIVE_AMOUNT;
  при min(ΣA, ΣB) ≤ 0 процент null и элиминация 0, статус списка AUTO — только при элиминации > 0
- группы одного размера выбираются по проценту расхождения к меньшей сумме (точной дробью), ничья — по равному проценту
- elimination_amount группы и тройки переименован в deviation_amount; version_no тройки обязателен
- суммы и проценты пар в JSON — с двумя знаками
- elimination-pairs по правилу с measure ≠ closing — 422 INVALID_RULE
- дельта спеки, design.md (R-7, R-8), tasks, README

make check-full: exit 0 (1896 passed; integration 990 passed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:47:47 +04:00

36 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Решение
Итог тройки `(rule_id, A, B)` считается чистой функцией по строкам обеих сторон до упаковки: суммы сторон со знаком,
`deviation_amount = |ΣA − ΣB|`, процент к меньшей стороне, `eliminated_amount = min(ΣA, ΣB)` при расхождении ≤ 5 %,
иначе и для `manual` — 0. Упаковка итог не меняет. Каждое правило считается отдельно (каскад по остатку отменён
владельцем 21.10.2026): строка общего счёта входит в несколько правил, двойной учёт снимает AURORA-1751.
Сбор встречных строк тройки — одна функция `_collect_rule_sides` для итога, упаковки и списка версии: список версии
(`build_totals`) строит сводную ОСВ один раз и проходит правила ББ × упорядоченные пары периметра без упаковки.
Версия правил фиксируется при создании версии консолидации (`latest_effective_version_no` на московскую дату) и дальше
не меняется; ветки «правила не зафиксированы» в чтении правил, пресете шахматки и парах убраны.
Эталонный Excel используется только для сверки точных встречных строк: он списывает полную сумму каждой стороны,
поэтому его общий итог не равен системному правилу минимума (OQ-155).
## Решения владельца 23.10.2026 (ревью PR #175)
1. Суммы сторон — со знаком, как в шахматке; строки с отрицательной суммой в упаковку не идут (`NEGATIVE_AMOUNT`).
2. Группы одного размера сортируются по проценту расхождения, а не по сумме.
3. Кроме тройки — список пар всей версии одним запросом (делаем сами в ветке PR).
4. `elimination_rules_version` — NOT NULL с миграцией существующих версий (DBML v5.23).
## Decisions made without discussion
| Маркер | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| R-1 (design #1) | 23.10.2026 | При `min(ΣA, ΣB) ≤ 0` процент `null`, элиминация 0, тройка подсвечивается при любом расхождении | Минимум со знаком дал бы элиминацию отрицательной суммы или отрицательный процент, который не пробивает порог; аналитик должен разобрать такой случай | Поменять одну функцию `calculate_rule_totals` и §5 |
| R-2 (design #2) | 23.10.2026 | Список версии — `GET …/elimination-totals`, статусы `AUTO` / `DISCREPANCY` / `ONE_SIDED` / `MANUAL`; тройки без строк не возвращаются; только правила ББ с `measure = closing` | Повторяет три категории задачи («сопоставленные, с расхождением, без пары») плюс ручные правила; ОФР в §4 не формализован | Переименование маршрута и статусов — правка фронта до его разработки |
| R-3 (design #3) | 23.10.2026 | Создание версии без правил, действующих на дату, — 409 `ELIMINATION_RULES_MISSING` | Поле NOT NULL; подставлять «последние открытые» значило бы взять правила, ещё не вступившие в силу | При пустом справочнике правил консолидацию не создать до seed — на практике seed всегда заводит v1 |
| R-4 (design #4) | 23.10.2026 | Миграция: дата создания по Москве; пустой `created_at` — текущая дата; нет действующих на дату — последняя открытая; нет правил вовсе — отказ миграции | Совпадает с правилом создания; запасной вариант нужен только для данных, созданных до появления правил | Неверный номер у старых версий — исправляется UPDATE до верификации |
| R-5 (design #5) | 23.10.2026 | `is_version_fixed` в ответе `elimination-rules` оставлен и всегда `true` | Фронт шахматки уже читает поле (PR #141, #158) | Удалить поле позже вместе с фронтом |
| R-6 (design #6) | 23.10.2026 | Группа упаковки — `deviation_amount` вместо `elimination_amount`; отдельной суммы элиминации у группы нет | В КФО идёт итог тройки; упаковка его не меняет (§5), а сумма у группы читалась бы как вклад в элиминацию | Добавить поле у группы, если корректировкам понадобится |
| R-7 (design #7) | 23.10.2026 | Статус тройки `AUTO` только при `eliminated_amount > 0`; обе стороны отрицательные и равные — `DISCREPANCY` | Иначе тройка без элиминации выглядела бы сопоставленной автоматически; совпадает с гайдом фронта §5.7.2 | Одна строка в `_total_status` |
| R-8 (design #8) | 23.10.2026 | Процент расхождения при упаковке сравнивается точной дробью (`Fraction`), не округлённым `Decimal` | Ничья по проценту (100 и 110,25 против 105 — по 5 %) не должна зависеть от точности деления | Нет: на выход процент идёт `Decimal` с двумя знаками |