Шаг 3100 ячеек · 0 расхождений
Спецификация расчёта отчёта ПУНЦ
«Расчёт предельного уровня нерегулируемых цен на электрическую энергию (мощность)». Документ — оракул для шага «перенос логики в конструктор» и для построчной сверки. Дата: 12.08.2026.
0. Статус источников — читать первым
| Эталон из ТЗ | Есть в прогоне | Где |
|---|---|---|
Пример выгрузки готового отчета.xlsx | да | reference/etalon-punc-avgust-2024.xlsx (август + сентябрь 2024) |
Исходный шаблон отчета.xlsx | да, как макет | reference/punc-template.xlsx — снят с эталонной выгрузки на шаге 2 |
Код к отчету.txt (C#-класс) | НЕТ | файла нет ни в рабочей копии, ни во временных каталогах контура (find / -iname '*Код к отчету*' → пусто) |
Что это значит для спецификации: каждая строка ниже помечена источником.
[КОД]— снято с выжимок эталонного C#-кода, сохранённых в предыдущем прогоне
(data-dictionary.md §4 со ссылкой на строки 693–694 кода). Это единственное, что от кода осталось в контуре.
[ЭТАЛОН]— снято непосредственно с ячеекreference/etalon-punc-avgust-2024.xlsx.[ДАННЫЕ]— подтверждено запросом кdata.db(наборы 00101/00102/00211/01115).[ВОПРОС]— правило по имеющимся эталонам определяется неоднозначно; вынесено в §9.
Такие места не решены молча и не должны реализовываться «на глаз».
Правило приоритета: там, где [ЭТАЛОН] и [ДАННЫЕ] дают одно и то же число во всех проверенных ячейках (100 из 100, §8), правило считается установленным. Там, где эталон допускает два объяснения, — это [ВОПРОС], а не решение.
1. Отбор исходных данных
[КОД] [ДАННЫЕ] Единственный набор-источник строк отчёта — 00102 (начисления), компоненты цены — 00101.
-- строки отчёта
SELECT R_Id, R_SortInx, R_Nomenclature, R_Name, R_Volume, R_Price
FROM accrual_00102
WHERE D_ContractId = :contract -- договор из 00211.C_Id
AND D_Year = :year AND D_Month = :month
AND D_Type = 'АПП' -- ЖЁСТКО. Не КАПП, не СЧФ, не КСЧФ, не УПД, не Счет
AND R_CalcName = 'Текущий расчет' -- сценарий расчёта
AND R_Nomenclature NOT LIKE 'Средневзвешенный%' -- итоговые строки, не первичные
ORDER BY R_SortInx;
-- компоненты цены строки
SELECT Name, ConObjId, Volume, Price, PriceWithoutDiscont
FROM detail_00101
WHERE RowId = :R_Id -- связь ПОСТРОЧНАЯ: 00101.RowId = 00102.R_Id
AND RowCalcName = 'Текущий расчет';
[ДАННЫЕ] На договоре 20 за 2024-08 фильтр даёт 12 строк отчёта (из 17 строк АПП; 5 отброшены как «Средневзвешенный тариф …»). Если взять D_Type шире, начисление удвоится: АПП и СЧФ описывают одно и то же, КАПП/КСЧФ — корректировки.
[ДАННЫЕ] Соединять 00101.AccrualDocumentId = 00102.D_Id нельзя — это связь на документ, она размножает строки (1396 → 80 174 пары) и завышает суммы.
Использование остальных наборов
| Набор | Где используется | Проверка |
|---|---|---|
| 00211 (договоры) | шапка: C_Number, C_BeginDt; ключ C_EnergyRegionId | [ДАННЫЕ] |
| 01115 (субъекты РФ) | V_EnergyRegionName → регион в заголовке A7 | [ДАННЫЕ] |
| 01112 (подразделения), 01113 (контрагенты), 01114 (подписанты) | следов в эталонной выгрузке нет: ни дирекции, ни контрагента, ни ФИО подписанта в форме не выводится | [ЭТАЛОН] [ВОПРОС 6] |
2. Разделы и порядок строк
[ЭТАЛОН] Строки листа группируются по R_Nomenclature в 4 раздела, в фиксированном порядке; внутри раздела — по возрастанию R_SortInx.
| № | Раздел (колонка A листа) | R_Nomenclature | Есть в эталоне |
|---|---|---|---|
| 1 | Электрическая энергия | Электроэнергия | да, 6 строк (авг) / 5 (сен) |
| 2 | Электроэнергия (потери) | Электроэнергия (потери) | да, 2 строки |
| 3 | Мощность АТС | Мощность АТС | да, 4 строки |
| 4 | Мощность СО | Мощность СО | строк нет — раздел присутствует в перечне видов потребления, но данных по нему в обоих периодах нет [ВОПРОС 7] |
[ДАННЫЕ] Проверено на августе: сортировка внутри раздела по R_SortInx даёт ровно тот порядок строк, что в эталоне (1ЦК → 3ЦК ВН 670–10 → 3ЦК СН2 670–10 → 3ЦК ВН >10 → 3ЦК СН2 >10 → строка с нулевым объёмом). В сентябре порядок другой — он тоже объясняется R_SortInx того периода.
Колонка B — наименование позиции: [ЭТАЛОН] [ДАННЫЕ] содержимое скобок R_Name. Электроэнергия (3ЦК, ВН, более 10 МВт) → 3ЦК, ВН, более 10 МВт; Электроэнергия (Компенсация потерь) → Компенсация потерь. Эквивалентно склейке R_Category, R_VoltageLevelName, R_PowerRangeName, но для потерь такой склейки нет — поэтому берётся текст из скобок.
3. Разметка листа периода
[ЭТАЛОН] Один лист на период, имя листа = «август 2024» (название периода без « г.»). Строки 1–14 — шапка и заголовки (в шаблоне уже есть), данные начинаются со строки 15, по одной строке отчёта на строку листа, без пустых разделителей и без строк «Итого».
Шапка:
| Ячейка | Содержимое | Источник |
|---|---|---|
| A2/B2 | Рынок / ОРЭМ/РРЭМ | константа [ЭТАЛОН] |
| A3/B3 | Ценовая категория / пусто | [ЭТАЛОН] (в эталоне не заполняется) |
| A4/B4 | Договор / № Import_test 170/011-P/138Д-05 от 01.01.2024 г. | 00211.C_Number + C_BeginDt, формат № {C_Number} от {дд.мм.гггг} г. (с завершающим пробелом) [ДАННЫЕ] |
| A5/B5 | Регион / пусто | [ЭТАЛОН] — регион известен, но в этой ячейке эталона его нет [ВОПРОС 8] |
| A6/B6 | Отчетный период / август 2024 г., формат ячейки @ (текст) | [ЭТАЛОН] |
| A7 | Расчет предельного уровня нерегулируемых цен на электрическую энергию (мощность) в границах зоны деятельности регионального гарантирующего поставщика, по договору купли-продажи электрической энергии (мощности) от 01.01.2024 г. № Import_test 170/011-P/138Д-05 в границах Смоленской области | шаблон + C_BeginDt, C_Number, 01115.V_EnergyRegionName в родительном падеже [ВОПРОС 9] |
| A9 | за август 2024 г. | [ЭТАЛОН] |
| N9 | СТОИМОСТЬ (шапка правого блока) | константа |
| A14…K14 | номера колонок 1,2,3,4,5,6,7,7,8 и 3+4+5+6+7-8 в K14 | [ЭТАЛОН]; номер «7» стоит и над H, и над I |
Обратите внимание: в B4 порядок «№ … от …», в A7 — «от … № …». Это не опечатка спецификации, так в эталоне.
4. Колонки листа периода: источник → формула → формат
Обозначения: V = 00102.R_Volume строки; цена слота считается по компонентам 00101 этой строки (RowId = R_Id), сгруппированным по Name:
цена(слот) = Σ(Price · Volume) / Σ(Volume) если Σ(Volume) > 0
= единственное значение Price если Σ(Volume) = 0
[ДАННЫЕ] Взвешивание нужно потому, что 00101 размножена по объектам учёта ConObjId (у строки R_Id=10649 их 4). На имеющихся данных цены по объектам одинаковы, поэтому взвешивание и «взять первую цену» дают одно и то же — см. [ВОПРОС 4].
Соответствие слотов и компонентов 00101.Name [КОД] [ДАННЫЕ]:
| Слот | 00101.Name | Применяется к |
|---|---|---|
ore | СВНЦ ГП для 1ЦК | ЭЭ 1ЦК и все строки «Электроэнергия (потери)» |
ore | Цена ЭЭ на ОРЭ | ЭЭ 3ЦК (и прочие не-1ЦК) |
ore | Цена мощность на ОРЭ | «Мощность АТС», «Мощность СО» |
transfer | Услуги по ЭЭ 1ст | ЭЭ (у потерь и мощности компонента нет) |
markup | Сбытовая надбавка ГП | ЭЭ и потери |
infra | Тариф на инфраструктуру | ЭЭ и потери |
uirp | Услуги УИРП | только потери |
| — | ПУНЦ ЭЭ, ПУНЦ мощность АТС, ПУНЦ мощность СО | готовый итог источника; в колонки не пишется, используется как контроль (см. K) |
| — | Услуги ЭЭ 2ст: Ставка за потери, Ставка за содержание сетей | в эталонной выгрузке следов нет [ВОПРОС 10] |
Колонки:
| Кол. | Заголовок | Формула | Ноль/прочерк | Формат |
|---|---|---|---|---|
| A | раздел | см. §2 | — | текст |
| B | вид потребления, уровень напряжения | скобки R_Name | — | текст |
| C | фактический объём | R_Volume как есть, без пересчёта единиц | 0 печатается как 0 | General |
| D | средневзвешенная цена на ОРЭ | цена(ore) | — | General |
| E | единые (котловые) тарифы на передачу | цена(transfer) | компонента нет → - | General |
| F | сбытовая надбавка ГП | PriceWithoutDiscont компонента Сбытовая надбавка ГП, если он заполнен, иначе Price — т.е. цена ДО скидки | нет надбавки → - | General |
| G | плата за иные услуги | цена(infra) | нет → - | General |
| H | «Предельный уровень нерегулируемых цен ГП» | всегда - во всех 23 строках данных обоих периодов | всегда - | General |
| I | плата за услуги УИРП | цена(uirp) | у ЭЭ компонента нет → печатается 0; у мощности → - [ВОПРОС 2] | General |
| J | скидка | PriceWithoutDiscont − Price компонента Сбытовая надбавка ГП | PriceWithoutDiscont пуст (потери) или надбавки нет (мощность) → - | General |
| K | ПУНЦ, «3+4+5+6+7-8» | D + E + F + G + I − J, прочерк = 0 | — | General |
| L | без заголовка | −J (скидка со знаком минус); заполняется только у строк «Электрическая энергия», у потерь и мощности пусто | [ВОПРОС 3] | General |
| M | — | не используется | ||
| N | стоимость: ОРЭ | цена(ore) · V | нет → 0 | General |
| O | стоимость: передача | цена(transfer) · V | нет → 0 | General |
| P | стоимость: сбытовая надбавка | Price · V, т.е. по цене СО скидкой (не по F!) | нет → 0 | General |
| Q | стоимость: иные услуги | цена(infra) · V | нет → 0 | General |
| R | стоимость: УИРП | цена(uirp) · V | нет → 0 | General |
[ЭТАЛОН] Числа в колонках C…R выводятся без округления, с полной двоичной точностью (в эталоне лежит, например, 59921.393000000004 и 4.3172822696). Форматирование — только числовой формат ячейки, значение не округляется.
[ЭТАЛОН] [ДАННЫЕ] Контроль: посчитанное K совпадает и с 00102.R_Price строки, и с компонентом ПУНЦ ЭЭ/ПУНЦ мощность АТС из 00101 — во всех 23 строках. Это даёт независимую проверку формулы, но в отчёт пишется именно расчёт 3+4+5+6+7-8.
Матрица прочерков по разделам [ЭТАЛОН] (что печатается при отсутствии компонента):
| Раздел | D | E | F | G | H | I | J | K | L |
|---|---|---|---|---|---|---|---|---|---|
| Электрическая энергия | число | число | число | число | - | 0 | число | число | −J |
| Электроэнергия (потери) | число | - | число | число | - | число | - | число | пусто |
| Мощность АТС | число | - | - | - | - | - | - | число | пусто |
5. Формирование листов по периодам
[ЭТАЛОН] В примере выгрузки пять листов: август 2024, сентябрь 2024, август 2024_1, сентябрь 2024_1, Сравнение.
[ЭТАЛОН] Листы август 2024 и август 2024_1 совпадают побайтно по значениям (проверено поячеечно: 0 различий на 26×18 ячеек), то же для сентября. То есть в файле два комплекта одних и тех же периодов. Что представляет второй комплект (второй сценарий расчёта? второй набор договоров? «до/после»?) — из имеющихся эталонов не определяется, см. [ВОПРОС 1].
Правило именования листа: название периода без « г.» (август 2024 г. → август 2024); при повторе имени добавляется суффикс _1, _2, … [ЭТАЛОН]
Лист «Сравнение» — последний.
6. Лист «Сравнение»
[ЭТАЛОН] Размер A1:M11. Шапка: A1 Составляющая, объединённые B1:D1 и E1:G1 — подпись периодов (в эталоне обе август - сентябрь 2024), H1:J1 ИЗМЕНЕНИЕ СТОИМОСТИ, K1:M1 ИЗМЕНЕНИЕ В ПРОЦЕНТАХ. Строка 3 — номера колонок и словесные формулы (8=6-3, 9=(5-2)*7, 10=(7-4)*2, 11=8/3, 12=9/3, 13=10/3).
Строки 4–11 — фиксированные, менять нельзя:
| Строка | Составляющая | B (объём) | C (стоимость) |
|---|---|---|---|
| 4 | ОРЭМ ЭЭ+ мощность | формула =B6 | формула =SUM(C5:C6) |
| 5 | в т.ч. покупка мощности | число: Σ R_Volume разделов «Мощность АТС» + «Мощность СО» / 1000 | число: Σ колонки N по строкам мощности |
| 6 | в т.ч. покупка ЭЭ | число: Σ R_Volume разделов «Электрическая энергия» + «Электроэнергия (потери)» | число: Σ N по ЭЭ и потерям |
| 7 | Услуга по передаче | число: Σ R_Volume строк с заполненным котловым тарифом (на данных = ЭЭ без потерь) | число: Σ O по всем строкам |
| 8 | Сбытовая надбавка | формула =B6 | число: Σ P по всем строкам |
| 9 | Инфраструктура | формула =B6 | число: Σ Q по всем строкам |
| 10 | УИРП | число: Σ R_Volume строк с ненулевой ценой УИРП (на данных = потери) | число: Σ R по всем строкам |
| 11 | Итого | формула =B9 | формула =SUM(C5:C10) |
Формульные колонки (не заполнять значениями, переносить формулами): D=C/B, G=F/E, H=F−C, I=(E−B)·G, J=(G−D)·B, K=H/C, L=I/C, M=J/C. Итоговая строка 11: H11=SUM(H5:H9), I11=SUM(I5:I9), J11=SUM(J5:J9) — суммируются строки 5–9, УИРП (строка 10) в итог изменения стоимости не входит [ВОПРОС 11].
Форматы: B/C/E/F/H/I/J — #,##0; B5 — #,##0.000; D/G — #,##0.000000; D5/G5 — денежный _-* #,##0.00\ _₽_-;…; K/L/M — 0.00%.
Наполнение: [ЭТАЛОН] левый блок (B–D) и правый (E–G) содержат агрегаты по всем листам своего комплекта (август + сентябрь вместе), а не по одному периоду. В примере левый и правый блоки численно совпадают, потому что совпадают комплекты листов.
Единицы: объём мощности делится на 1000, объём ЭЭ — нет, хотя в шапке B2 единица одна (тыс.кВт*ч, МВт). Переносить как есть [ВОПРОС 12].
7. Чего в эталоне НЕТ — запрет на додумывание
Ни в эталонной выгрузке, ни в сохранившихся выжимках кода этого нет. Реализовывать нельзя, пока заказчик не даст отдельного письменного правила:
- КАПП и КСЧФ. Отбор жёстко
D_Type='АПП'. Корректировки не подмешиваются, не замещают АПП, не суммируются с ним. (В данных КАПП за 2024-08 есть — 72 строки — и он переписывает объём 5928 → 7000 кВт·ч. Отчёт этого не видит и видеть не должен.) - Строки «Средневзвешенный …» (5 штук в августе) — исключены целиком, ни в строки листа, ни в агрегаты «Сравнения» не входят.
- Альтернативные сценарии
R_CalcName = 'Альтернативный: …'— не используются. - Округление значений — нигде не применяется.
- Строки «Итого» на листе периода — их нет.
- Пересчёт единиц (кВт·ч → МВт·ч) на листе периода — не выполняется,
R_Volumeкладётся как есть, несмотря на подпись «МВт∙ч» в C12. - НДС — ни в одной колонке отчёта не фигурирует (
R_CostVAT,R_VAT_Amountне используются). - Фильтр по
D_State(Prepared/Approved) — в эталоне не применяется. - Подписанты (01114), дирекция (01112), контрагент (01113) — в форму не выводятся.
- Компоненты
Услуги ЭЭ 2ст: Ставка за потерииСтавка за содержание сетей— в колонки отчёта не попадают (двухставочный тариф в этой форме не раскрывается).
8. Контрольные строки: как значение получается по спецификации
Скрипт spec_selfcheck.py считает контрольные ячейки прямо из data.db по формулам §1–§6 (без вызова расчётного модуля) и сравнивает с эталонной выгрузкой. Полный протокол — evidence/spec-selfcheck.txt.
Итог: 100 ячеек, расхождений 0.
| # | Строка эталона | Проверяемое правило | Ключевые числа |
|---|---|---|---|
| 1 | август 2024, стр. 15 — ЭЭ 1ЦК СН2 <670 кВт (R_Id=10649) | 1ЦК берёт СВНЦ ГП для 1ЦК; 4 объекта учёта схлопываются; F — цена до скидки, P — по цене со скидкой | V=5928; D=3.12194; F=0.87046 (PriceWithoutDiscont), Price=0.783414; J=0.87046−0.783414=0.087046; K=3.12194+4.55684+0.87046+0.00481+0−0.087046=8.467004; P=5928·0.783414=4644.078192 |
| 2 | стр. 16 — ЭЭ 3ЦК ВН 670–10 МВт (10652) | не-1ЦК берёт Цена ЭЭ на ОРЭ; значение не округляется | D=1.6947282696; K=1.6947282696+2.34526+0.30276+0.00481−0.030276=4.3172822696 = 00102.R_Price |
| 3 | стр. 18 — ЭЭ 3ЦК ВН >10 МВт (10663) | все пять стоимостей N…R | N=3398869·1.6947245076=5760146.592421904; O=7971231.51094; P=3398869·0.261135=887563.656315; Q=16348.55989; R=0 |
| 4 | стр. 20 — ЭЭ 3ЦК ВН 670–10 МВт, объём 0 (10676) | при ΣVolume=0 цена берётся как единственное Price, а не 0/0 | D=0 (в 00101 цена ОРЭ действительно 0.0), E=2.34526, F=0.30276, G=0.00481, J=0.030276 → K=2.622554; все стоимости 0 |
| 5 | стр. 21 — потери «Компенсация потерь» (10654) | у потерь ОРЭ = СВНЦ ГП для 1ЦК; передачи нет → -; УИРП есть; скидки нет → - | D=3.12194; E=-; F=0.65099 (PriceWithoutDiscont пуст → берётся Price); I=0.00074; J=-; K=3.12194+0.65099+0.00481+0.00074=3.77848; R=1500·0.00074=1.11 |
| 6 | стр. 25 — Мощность АТС 3ЦК ВН >10 МВт (10664) | у мощности только ОРЭ, остальное - | D=K=861.13871; N=4568·861.13871=3933681.62728; O…R=0 |
| 7 | лист Сравнение, B5…C10 (10 ячеек) | агрегаты по обоим периодам | B5=9629/1000=9.629; C5=8368852.07187; B6=7130126; C6=12896242.430418974; B7=7118689; C7=16872905.76768; C8=1905244.755036; C9=34295.90606; B10=5401+6036=11437; C10=6.65258 |
Дополнительно: сплошная сверка уже реализованного расчёта с эталоном (reconcile_punc.py) — 156 показателей, расхождений 0, допуски 1e-6 руб/МВт·ч и 1e-4 руб.
9. Открытые вопросы — решать не самостоятельно
- Второй комплект листов (
август 2024_1,сентябрь 2024_1). Значения в нём идентичны первому. Что это за срез — второй сценарий расчёта, другой договор, «план/факт»? От ответа зависит, откуда берётся правый блок (E–G) листа «Сравнение». - Колонка I у электроэнергии:
0или-? КомпонентаУслуги УИРПу строк ЭЭ в 00101 нет, но в эталоне стоит0, тогда как у мощности при отсутствии компонента — прочерк, и у ЭЭ отсутствующая передача тоже даёт прочерк. Правило «нет компонента → прочерк» здесь нарушается. Сейчас зафиксировано по факту эталона (матрица §4). - Колонка L (скидка со знаком минус, без заголовка, только у ЭЭ) — служебная пометка или значащая колонка? В шаблоне заголовка нет.
- Цены, различающиеся по объектам учёта. Спецификация предписывает взвешивание по объёму, но на имеющихся данных все цены внутри строки одинаковы, поэтому правило эталоном не проверено. Если в продуктивных данных цены по
ConObjIdразойдутся, результат зависит от выбора (взвешенная / первая / максимальная). - Нулевой объём при нескольких разных ценах. Взвешивание невозможно; сейчас правило «единственная цена», а если их несколько — не определено.
- Наборы 01112/01113/01114. В ТЗ они перечислены как источники отчёта, но в эталонной выгрузке их полей нет. Нужны ли они в форме вообще?
- Раздел «Мощность СО». Данных нет ни в одном периоде эталона. Печатать ли раздел при отсутствии строк и какой компонент 00101 (
Цена мощность на ОРЭили иной) — не проверено. - B5 «Регион» в шапке пуст, хотя регион известен и используется в заголовке A7. Так задумано или дефект эталонной выгрузки?
- Склонение региона в A7: «в границах Смоленской области» против значения справочника «Смоленская область». Нужен либо словарь склонений, либо поле в 01115.
- Двухставочный тариф. Компоненты
Услуги ЭЭ 2ст: Ставка за потерииСтавка за содержание сетейесть в 00101 (46 строк каждый), но в форму не попадают. Это осознанно или форма покрывает только одноставочные строки? H11=SUM(H5:H9)на листе «Сравнение» не включает строку 10 (УИРП), хотяC11=SUM(C5:C10)включает. Формула эталона переносится как есть — подтвердить, что это так и задумано.- Единицы в «Сравнении»: объём мощности делится на 1000, объём ЭЭ — нет, при общей подписи «тыс.кВт*ч, МВт».
- Сентябрь 2024 в исходных данных отсутствует (
00102содержит толькоD_Year=2024, D_Month=8). Второй период эталона на имеющихся данных пересчитать нечем — построчная сверка сентября будет возможна только после выгрузки данных.
10. Что должно быть исправлено в текущей реализации
По итогам сверки с эталоном (шаг 4 — перенос логики):
app.py:187кладёт в шапку00211.C_Name(«РУСЭНЕРГОСБЫТ» (Смоленск) Import_test),
а эталон в B4 требует № {C_Number} от {C_BeginDt} г. → № Import_test 170/011-P/138Д-05 от 01.01.2024 г. . Аналогично A7.
- В шаблоне
punc_template.pyрегион подставляется без склонения → «в границах Смоленская
область» вместо «в границах Смоленской области» [ВОПРОС 9].
- B5 «Регион» реализация заполняет, эталон — оставляет пустым
[ВОПРОС 8]. _sum_volume(price_key='transfer')для строки 7 «Сравнения» и правило «объём ЭЭ без
потерь» на имеющихся данных дают одно и то же число (7 118 689); какое из них верно в общем случае — упирается в [ВОПРОС 10].
Проверяемые артефакты в репозитории: SPEC-PUNC.md (этот документ),
spec_selfcheck.py (расчёт контрольных ячеек из data.db),
evidence/spec-selfcheck.txt (протокол), reference/etalon-punc-avgust-2024.xlsx (эталон заказчика).