test(consolidation): точные значения и проверки сопоставления в сквозном тесте РБ, уточнение spec
Точные числа свода и набор пробелов, кандидаты, MANUAL/alias_created, EXTERNAL до сопоставления; формулировка сценария «Сопоставление из плана среза» и design #80 уточнены; docstring effective_cumulative. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
0bfe9eea5f
commit
a5cde2adb7
@ -64,6 +64,9 @@ def effective_cumulative(is_cumulative: bool | None, period_quarter: PeriodQuart
|
||||
квартал `Q1`; парсер помечает такие ОСВ нарастающими): здесь он считается квартальным при любом флаге — подходит
|
||||
любой версии года, а нарастающий и квартальный `Q1` одной пары — один слот привязки. Прочие —
|
||||
`counts_as_cumulative`.
|
||||
|
||||
Не зависит от типа документа: ограничение «только ОСВ» (R-14a) живёт у вызывающего —
|
||||
`consolidation_documents._cumulative`; для ББ/ОФР эту функцию вызывать нельзя.
|
||||
"""
|
||||
return period_quarter != PeriodQuarter.Q1 and counts_as_cumulative(is_cumulative)
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"""Сквозной сценарий Среза 4 на реальных данных группы «Русский бисквит» (tasks 5.1; spec consolidated-osv «Признаки на
|
||||
фикстурах РБ», «Сопоставление из плана среза», «Рекомендуемая Счастливочная»; documents-api «Фикстуры, подтверждённые
|
||||
до консолидации»).
|
||||
фикстурах РБ», «Сопоставление из плана среза»; documents-api «Фикстуры, подтверждённые до консолидации»).
|
||||
|
||||
Golden-снимки парсера `rb/2024-Q1..Q3` и `schastl/2024-Q1..Q3` → боевой `map_file` → подтверждение через
|
||||
`POST /documents/{id}/confirm` → создание консолидации 2024 Q3 (подхват подтверждённых) → блокировка → сводная ОСВ,
|
||||
@ -49,6 +48,8 @@ SCHASTL_FILE_NAME = "ПЕКАРНЯ СЧАСТЛИВОЧНАЯ" # в файла
|
||||
RB_NAME_IN_SCHASTL = "Русский бисквит"
|
||||
TIME_BUDGET_SECONDS = 60.0
|
||||
Q1, Q2, Q3 = "Q1", "Q2", "Q3"
|
||||
ROWS_TOTAL, UNMAPPED_TOTAL = 1385, 1381 # свод РБ 2024 Q3
|
||||
CHANGED_ROWS, PROPAGATED = 4, 17 # строк свода, получивших контрагента; записей osv_entry, кроме выбранной (18 всего)
|
||||
SUMS = ("opening_debit", "opening_credit", "turnover_debit", "turnover_credit", "closing_debit", "closing_credit")
|
||||
|
||||
|
||||
@ -119,6 +120,7 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0
|
||||
for path in _golden_files(fixtures_dir, folder):
|
||||
documents.extend(await map_golden_snapshot(seeded, entity=entity, path=path, uploaded_by=user.id))
|
||||
assert len(documents) == 11 + 11 # РБ: Q1 2, Q2 2, Q3 7; Счастливочная: Q1 3, Q2 3, Q3 5
|
||||
assert all(d.status is not DocumentStatus.NEEDS_CHECK for d in documents) # подтверждаются без правок
|
||||
|
||||
client = await auth_client(KA_RF)
|
||||
for document in documents:
|
||||
@ -155,7 +157,9 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0
|
||||
print(
|
||||
f"\nSVOD: rows={len(rows)} summary={ {k: v for k, v in summary.items() if k != 'gaps'} } gaps={summary['gaps']}"
|
||||
) # noqa: T201
|
||||
assert summary["rows_total"] == len(rows)
|
||||
# Числа сверены независимым подсчётом по сырым golden-JSON (склейка Q1–Q3, пробелы по наличию файлов).
|
||||
assert summary["rows_total"] == len(rows) == ROWS_TOTAL
|
||||
assert summary["unmapped_total"] == UNMAPPED_TOTAL
|
||||
|
||||
# строки свода: 60 и 62 у обеих, 76 у Счастливочной
|
||||
present = {(row["legal_entity_id"], row["account_root"]) for row in rows}
|
||||
@ -164,12 +168,13 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0
|
||||
assert (str(schastl.id), account) in present
|
||||
assert (str(schastl.id), "76") in present
|
||||
|
||||
# пробелы: у РБ нет квартальных ОСВ 66/67/76 за Q1–Q2, у Счастливочной — 01/02
|
||||
# пробелы (точный набор по файлам golden): у РБ за Q1–Q2 нет ОСВ 01, 58.03, 66, 67, 76, у Счастливочной — 01, 02
|
||||
gaps = {(g["legal_entity_id"], g["account_code"]): g["missing_quarters"] for g in summary["gaps"]}
|
||||
for account in ("66", "67", "76"):
|
||||
assert set(gaps[(str(rb.id), account)]) >= {Q1, Q2}
|
||||
for account in ("01", "02"):
|
||||
assert set(gaps[(str(schastl.id), account)]) >= {Q1, Q2}
|
||||
assert len(summary["gaps"]) == len(gaps)
|
||||
assert gaps == {
|
||||
**{(str(rb.id), account): [Q1, Q2] for account in ("01", "58.03", "66", "67", "76")},
|
||||
**{(str(schastl.id), account): [Q1, Q2] for account in ("01", "02")},
|
||||
}
|
||||
|
||||
# чек-лист: ОСВ 58 РБ не подтверждена (есть только 58.03)
|
||||
checklist = await _get(client, f"{version_url}/checklist")
|
||||
@ -189,28 +194,40 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0
|
||||
target = [row for row in unmapped if _is_schastl(row["counterparty_name"])]
|
||||
assert target
|
||||
first = target[0]
|
||||
# записи склеенной строки идут по кварталам; сопоставляем запись последнего квартала: в строке свода показывается
|
||||
# способ сопоставления записи самого позднего квартала (D-3, решение №8), поэтому только она даёт `MANUAL`
|
||||
entry_id = first["entry_ids"][-1]
|
||||
unmapped_before = summary["unmapped_total"]
|
||||
assert (first["counterparty_id"], first["counterparty_type"]) == (None, "EXTERNAL")
|
||||
|
||||
# кандидаты: ООО «Пекарня Счастливочная» есть в списке с признаком GSK. `recommended` на реальном имени «Юг-Финанс /
|
||||
# ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» не ожидается: рекомендация — точное совпадение нормализованного имени/алиаса (сценарий
|
||||
# «Рекомендуемая Счастливочная» покрыт в test_api_counterparty_mapping на имени «Пекарня Счастливочная ООО»)
|
||||
candidates = await _get(client, f"{version_url}/counterparty-candidates", entry_id=entry_id)
|
||||
schastl_candidate = next(item for item in candidates if item["id"] == str(schastl.id))
|
||||
assert schastl_candidate["counterparty_type"] == "GSK"
|
||||
|
||||
mapped = await client.post(
|
||||
f"{version_url}/osv/entries/{first['entry_ids'][0]}/counterparty", json={"legal_entity_id": str(schastl.id)}
|
||||
f"{version_url}/osv/entries/{entry_id}/counterparty", json={"legal_entity_id": str(schastl.id)}
|
||||
)
|
||||
assert mapped.status_code == HTTP_OK, mapped.text
|
||||
body = mapped.json()
|
||||
assert body["row"]["counterparty_type"] == "GSK"
|
||||
assert body["propagated"] >= 1
|
||||
assert (body["row"]["match_kind"], body["alias_created"]) == ("MANUAL", True)
|
||||
assert body["propagated"] == PROPAGATED
|
||||
|
||||
after = await _get(client, f"{version_url}/osv/summary")
|
||||
rows_after = await _all_rows(client, version_url)
|
||||
# `propagated` считает записи `osv_entry` (в том числе в парах-пробелах 66/76, строк в своде не дающих), а
|
||||
# `unmapped_total` — строки свода: квартальные записи одного контрагента склеены (D-2). Поэтому `unmapped_total`
|
||||
# падает на число склеенных строк свода, получивших контрагента, а «1 + propagated» сверяется с записями в БД
|
||||
# (design #79).
|
||||
before_by_entries = {entry_id: row for row in rows for entry_id in row["entry_ids"]}
|
||||
# (design #80).
|
||||
before_by_entries = {eid: row for row in rows for eid in row["entry_ids"]}
|
||||
changed = {
|
||||
tuple(row["entry_ids"]) for row in rows_after
|
||||
if row["counterparty_id"] is not None and before_by_entries[row["entry_ids"][0]]["counterparty_id"] is None
|
||||
} # fmt: skip
|
||||
assert len(changed) >= 2 # noqa: PLR2004 - счета 60 и 62
|
||||
assert len(changed) == CHANGED_ROWS
|
||||
stored = (
|
||||
await seeded.execute(
|
||||
select(OsvEntry).where(OsvEntry.owner_legal_entity_id == rb.id, OsvEntry.counterparty_name.is_not(None))
|
||||
@ -218,7 +235,7 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0
|
||||
)
|
||||
).scalars().all() # fmt: skip
|
||||
named = [entry for entry in stored if _is_schastl(entry.counterparty_name)]
|
||||
assert len(named) == 1 + body["propagated"]
|
||||
assert len(named) == 1 + PROPAGATED == 1 + body["propagated"]
|
||||
assert {entry.counterparty_id for entry in named} == {schastl.id}
|
||||
assert {entry.counterparty_match_kind for entry in named} == {
|
||||
CounterpartyMatchKind.MANUAL,
|
||||
@ -284,4 +301,3 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0
|
||||
elapsed = time.perf_counter() - started
|
||||
logger.info("сквозной сценарий РБ: {:.1f} с, строк свода {}", elapsed, len(rows_after))
|
||||
assert elapsed <= TIME_BUDGET_SECONDS
|
||||
assert all(d.status is not DocumentStatus.NEEDS_CHECK for d in documents)
|
||||
|
||||
@ -254,7 +254,7 @@
|
||||
|
||||
- [x] **Step 1: Тест** по сценарию (сначала падает на отсутствии хелпера или данных).
|
||||
- [x] **Step 2–4:** хелпер → `pytest -m "integration and fixtures" tests/test_consolidation_fixtures.py -q` → PASS; `make check-full` (без `e2e`, если compose с парсером не поднят — отметить в ledger).
|
||||
- [x] **Step 5: Commit** `test(consolidation): сквозной сценарий Среза 4 на фикстурах группы «Русский бисквит»` (тест под `xfail(strict)`: найден дефект привязки Q1, см. `task-13-report.md`; Step 2–4 закрыть после исправления; закрыты исправлением R-14 — документ за Q1 равнозначен нарастающему и квартальному, xfail снят).
|
||||
- [x] **Step 5: Commit** `test(consolidation): сквозной сценарий Среза 4 на фикстурах группы «Русский бисквит»` (первый коммит теста шёл под `xfail(strict)`: найден дефект привязки Q1, см. `task-13-report.md`; Step 2–4 закрыты после исправления R-14 — документ за Q1 равнозначен нарастающему и квартальному, xfail снят; `make check-full` целиком выполняется в Task 14).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -264,5 +264,5 @@ legal_entity_name, account_code, missing_quarters[]}}`. Фильтры `GET …/
|
||||
| 77 | 2026-10-06 | R-12: все текстовые ячейки экспорта (компания, ИНН, счёт, период, контрагент, ИНН контрагента, «Сопоставлено с», тип, способ, лист «Пробелы») проходят `_put_text`: запрещённые XML-символы (`ILLEGAL_CHARACTERS_RE`) вырезаются, тип ячейки принудительно `"s"`, поэтому `=`, `+`, `-`, `@` в начале не становятся формулой, а текст читается обратно без изменений | файлы внешние, аналитики открывают выгрузку в Excel: без этого `=HYPERLINK(...)` из чужого файла выполнился бы в их книге, а управляющий символ давал `IllegalCharacterError` → 500 | текст ячейки с ведущим `=` больше не вычисляется — это желаемо; вырезанный управляющий символ из выгрузки пропадает |
|
||||
| 78 | 2026-10-06 | R-13: параметр `entity_names` убран из `build_workbook` — сигнатура `build_workbook(c, rows) -> bytes`; сборка книги выполняется в `run_in_threadpool` (линейная по строкам, явный счётчик строк вместо `sheet.max_row`) | строки и пробелы уже несут имена ЮЛ, параметр не использовался (YAGNI); план предписывал параметр — Interfaces Task 12 исправлены | если фронту понадобится словарь переопределения имён — вернуть параметр |
|
||||
| 79 | 2026-10-06 | Сквозной тест на фикстурах (Task 13): ГК «Русский бисквит» сидится в Ростовском филиале, а не в «СПб РФ», как в плане | dev-пользователь `ka_rf` (seed) — сотрудник Ростовского филиала; видимость ГК и документов у РФ ограничена своим филиалом (policy), у `ka_rf` нет СПб; смысл сценария (КА РФ ведёт свою ГК) не меняется | тест не проверяет конкретное название филиала; при появлении dev-пользователя СПб РФ — заменить константу `ROSTOV` |
|
||||
| 80 | 2026-10-06 | Сверка сопоставления на реальных данных: `unmapped_total` падает на число СКЛЕЕННЫХ строк свода, получивших контрагента, а `1 + propagated` равно числу записей `osv_entry` с этим именем в ОСВ РБ, привязанных к версии (включая пары-пробелы 66/76, строк в своде не дающие); равенство «`unmapped_total` падает на `1 + propagated`» из spec consolidated-osv «Сопоставление из плана среза» верно только для нарастающих документов (одна запись = одна строка свода) | на фикстурах квартальные записи одного контрагента склеиваются (D-2): в РБ «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» — 18 записей, 4 строки свода по счетам 60/62; поведение продукта (propagated считает записи хранилища, summary — строки свода) согласовано с D-2/D-3, неточна формулировка spec | формулировка сценария в spec вводит в заблуждение для квартальных документов; уточнить при архивации (не меняет код) |
|
||||
| 80 | 2026-10-06 | Сверка сопоставления на реальных данных: `unmapped_total` падает на число СКЛЕЕННЫХ строк свода, получивших контрагента, а `1 + propagated` равно числу записей `osv_entry` с этим именем в ОСВ РБ, привязанных к версии (включая пары-пробелы 66/76, строк в своде не дающие); равенство «`unmapped_total` падает на `1 + propagated`» из прежней формулировки spec consolidated-osv «Сопоставление из плана среза» нарушается дважды: квартальные записи склеиваются в одну строку свода, а записи в парах-пробелах (счета 66/76 РБ) дают `propagated`, но строк свода не дают; верно оно только когда записи не склеены и не лежат в пробелах (нарастающие документы без пробелов) | на фикстурах квартальные записи одного контрагента склеиваются (D-2): в РБ «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» — 18 записей, 4 строки свода по счетам 60/62; поведение продукта (propagated считает записи хранилища, summary — строки свода) согласовано с D-2/D-3, неточна формулировка spec | формулировка сценария в spec исправлена (fix round 1, M1); код не менялся |
|
||||
| 81 | 2026-10-06 | R-14 + R-14a: ОСВ за `Q1` (`period_quarter = Q1`) равнозначна и нарастающей, и квартальной. `consolidation_periods.effective_cumulative(is_cumulative, period_quarter)` (ложна для `Q1`, иначе `counts_as_cumulative`) используется в `select_sources` (для версии `Q2+` ОСВ за `Q1` — квартальный источник `Q1`; нарастающая за квартал версии по-прежнему побеждает; для версии `Q1` — прежнее поведение: нарастающая «как есть», квартальная — склейка, №50), в адаптере чек-листа и — только для `document_type = OSV` — в `consolidation_documents._cumulative`, то есть в `fits` (ОСВ за `Q1` подходит любой версии года при любом флаге) и в ключе замены `_key` (нарастающая и квартальная ОСВ за `Q1` одной пары — один слот, побеждает поздний `confirmed_at`, при равенстве — больший `id`). R-14a: ББ/ОФР за `Q1` — отчёт на конец квартала, не источник версий `Q2`–`Q4`: для них прежнее правило D-5 (NULL → нарастающий → только квартал версии), ключ замены не меняется | парсер помечает ОСВ за первый квартал нарастающей (период с начала года = Q1); без этого сквозной тест на фикстурах РБ давал 17 привязок из 22, пары 60/62(/76) уходили в пробел `[Q1]`, свод пуст | нарастающая и квартальная ОСВ за `Q1` одной пары вытесняют друг друга при привязке; если и ББ/ОФР за `Q1` должны подхватываться версиями `Q2`–`Q4` — снять условие `document_type = OSV` в `_cumulative` |
|
||||
|
||||
@ -116,7 +116,9 @@ propagated_entry_ids, alias_created, alias_moved_from}`. Уже связанну
|
||||
- **WHEN** в версии РБ 2024 Q3 строка РБ «Пекарня Счастливочная ООО» (счёт 60) связана с ООО «Пекарня Счастливочная»
|
||||
- **THEN** 200; у строки `counterparty_type = GSK`, `match_kind = MANUAL`; создан `legal_entity_alias` «Пекарня
|
||||
Счастливочная ООО»; все остальные строки РБ с тем же именем во всех привязанных ОСВ (60, 62, 66 …) получили тот же
|
||||
`counterparty_id` и `PROPAGATED`; `propagated` ≥ 1; `summary.unmapped_total` уменьшился на `1 + propagated`
|
||||
`counterparty_id` и `PROPAGATED`; `propagated` ≥ 1; `summary.unmapped_total` уменьшился на число СТРОК сводной ОСВ,
|
||||
получивших контрагента (квартальные записи одного контрагента склеены в одну строку), а `1 + propagated` равно числу
|
||||
записей `osv_entry` с этим именем во всех привязанных ОСВ, включая пары-пробелы и записи, склеенные в одну строку
|
||||
|
||||
#### Scenario: Алиас занят
|
||||
- **WHEN** `normalized` имени строки уже есть алиасом у другого ЮЛ, `move_alias = false`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user