Примечание: Статья посвящена формату XBRL-CSV2 , (тэг "@context":"www.cbr.ru/xbrl_csv2").
Это вторая часть, описывающая конвертацию формульного слоя.
Авторство формата принадлежит Банку России.
Автор статьи — архитектор, принимавший участие в разработке формата в качестве технического специалиста.
Всем привет! В первой части мы познакомились с форматом XBRL-CSV2, который позволяет упаковать сложность XBRL-отчетности в простые и удобные для обработки CSV-таблицы. Мы обсудили, как данные и их метаописания (маппинг) преобразуются из XML-представления в реляционное.
Но сбор данных — это только половина дела. Вторая, не менее важная часть — это их проверка на целостность, непротиворечивость и соответствие бизнес-правилам. В классическом XBRL за это отвечает формульный слой (Formula Layer). Сегодня мы поговорим о том, как мы превратили эти формулы в исполняемые SQL-скрипты, создав так называемый «слой отделяемых формул».
Зачем это нужно? Принцип «пиши правила один раз»
Основное преимущество нашего подхода заключается в том, что авторы таксономии продолжают разрабатывать информационную модель и правила ее проверки на мощном и стандартизированном языке XBRL Formula. Они используют все его возможности: value assertions, existence assertions, фильтрацию по аспектам, переменные и т.д.
Затем, в процессе автоматической конвертации таксономии, эти формулы так же автоматически преобразуются в SQL-выражения. Это работает для любой СУБД, совместимой со стандартным SQL, будь то PostgreSQL, Oracle или, что особенно актуально для больших данных, Hadoop SparkSQL.
Принцип эквивалентности, заложенный в спецификации, гарантирует: результат проверки одного и того же отчета средствами классического XBRL-процессора и с помощью нашего слоя SQL-формул будет идентичным. Совпадение гарантировано при условии, что состав показателей и их аспектов в обоих форматах одинаков.
Как это работает? Архитектура конвертации

Процесс преобразования XBRL-формулы в готовый SQL-запрос — это конвейер, состоящий из нескольких четких этапов. Взглянем на него схематически.
Анализ исходной XBRL-формулы. Система «детектора формул» считывает формулу из таксономии и анализирует ее структуру.
-
Типизация и метрики. На основе автоматического анализа вычисляется набор метрик. Например:
CONCEPT.IS_EXISTS— наличие фильтров по концептам.FACT_VARIABLE.BIND_AS_SEQUENCE.FALSE— количество скалярных переменных.TAXIS.IS_EXISTS— наличие открытых осей.FORMULA.IS_EXISTENCE_ASSERTION— является ли формула проверкой существования. Эти метрики позволяют отнести формулу к одному из предопределенных типов:TYPE_I,TYPE_IIи т.д.
Формирование комплексного JSON-запроса. На основе типа формулы генерируется структурированный JSON-документ, который описывает формулу на языке, понятном для системы маппинга. Этот документ контролируется строгой JSON-схемой
outerFormulas.def.json.Поиск в маппинге. «Исполнитель запросов» использует этот JSON для выполнения JPath-запросов к избыточному файлу маппинга (
mbt_mapping_full.json). Цель — найти все CSV-файлы и столбцы, которые участвуют в расчете формулы.Формирование комплексного JSON-ответа. Результатом поиска является еще один JSON-документ, который содержит всю информацию, необходимую для построения SQL: найденные таблицы (аспекты данных), колонки, их аспекты дат/периодов, открытые оси и условия фильтрации.
Подбор шаблона MyBatis и генерация SQL. Файл ответа анализируется, и на основе его состава (например, количества открытых осей, типа аспектов данных) выбирается соответствующий шаблон MyBatis. Данные из ответа «заливаются» в этот шаблон, порождая итоговый, корректный SQL-запрос (DML).
Этот запрос вычисляет значения контрольного соотношения для всего пакета отчетности с учетом конкретного аспекта даты или периода.
Пример изнутри: полный цикл преобразования формулы valueAssertion_R10_25
Рассмотрим реальный пример — формулу valueAssertion_R10_25 типа TYPE_I.
Исходная логика формулы в XBRL:
Предусловие:
string($Asst_Tp) = "71" or string($Asst_Tp) = "72"Тест: Проверить, что
$DFI_BgnDtне пусто.Переменные: Две скалярные переменные (
$Asst_Tpи$DFI_BgnDt), привязанные к концептам.Аспекты: 12 глобальных открытых осей.
Шаг 1: Генерация JSON-запроса.
После анализа формулы генерируется комплексный JSON-запрос. Это техническое задание для системы маппинга, в котором на языке JPath-выражений описано, какие таблицы и столбцы нужно найти. В запросе для каждой переменной указаны фильтры, которые должны выполниться (например, найти таблицу, где есть ось dim_int_Asst_IdTaxis, но нет оси dim_int_AA_PrdBgnTaxis), и указания, по какому концепту искать колонку с данными.
(Для краткости ниже показан сильно сокращенный фрагмент запроса, иллюстрирующий его структуру)
"complexRequest": {
"formulaDetails": {"formulaCode": "valueAssertion_R10_25", ...},
"test": {"expression": "CASE WHEN originalValue_$DFI_BgnDt ...", ...},
"precondition": {"expression": "($Asst_Tp) = '71' ...", ...},
"queries": [
{
"id": "valueAssertion_RR71_25.c",
"typeQuery": "formulaFactVariable",
"query": {
"name": "$Asst_Tp",
"jpathTableFilter": [
// Фильтр-исключение: таблица, где НЕТ оси dim_int_AA_PrdBgnTaxis
{"jpathTestQuery": "get(...).isEmpty()", ...},
// Фильтр-включение: таблица, где ЕСТЬ ось dim_int_Asst_IdTaxis
{"jpathTestQuery": "get(...).isNotEmpty()", ...}
],
"jpathColumnFilter": [
// Поиск колонки с данными по концепту purcb-dic_Asst_Tp
{"jpathQuery": "$[?(@['aspect']['xbrl:concept']==\"purcb-dic_Asst_Tp\")]...", ...}
]
}
}
]
}
Шаг 2: Получение JSON-ответа от системы маппинга.
Запрос исполняется, и система возвращает структурированный ответ, который является готовым ТЗ для SQL-движка.
"complexResponse": {
"formulaDetails": { ... },
"test": {
"expression": "CASE WHEN originalValue_$DFI_BgnDt is not null THEN 1 ELSE 0 END",
"varList": ["$DFI_BgnDt"]
},
"precondition": {
"expression": " ($Asst_Tp) = '71' or ($Asst_Tp) = '72' ",
"varList": ["$Asst_Tp"]
},
"dataAspectList": [
{
"dataAspectType": "dataAspectTypedAxis",
"dataAspectName": "DATA_ASPECT0",
"roleUri": "http://.../tab/sr_R10",
"typedAxisArray": ["dim_int_Asst_IdTaxis"],
"dataColumnsList": [
{
"name": "$Asst_Tp",
"columnId": "purcb_dic_Asst_Tp_dimGrp_1_periodGrp_1",
"fallBackValue": "0",
"aspectsQuery": { "periodInstant": "$par:refPeriodEnd" }
},
{
"name": "$DFI_BgnDt",
"columnId": "purcb_dic_DFI_BgnDt_dimGrp_1_periodGrp_1",
"fallBackValue": "0",
"aspectsQuery": { "periodInstant": "$par:refPeriodEnd" }
}
]
}
]
}
Этот документ — готовое техническое задание для SQL-движка. Он говорит:
«Возьми таблицу sr_R10.csv. Для расчета тебе нужны колонки purcb_dic_Asst_Tp_… и purcb_dic_DFI_BgnDt_…, которые имеют аспект даты $par:refPeriodEnd. Выполни проверку: если значение в колонке $Asst_Tp равно '71' или '72', то значение в колонке $DFI_BgnDt не должно быть пустым».
Документ содержит общее описание формулы - раздел "formulaDetails", есть описание теста и предусловия.
Самое интересное — это массив «dataAspectList». Каждый элемент этого массива это аспект источника данных, который является разделом из слоя определения XBRL и это же является CSV таблицей данных.
Если у формулы более одного раздела данных - значит такая формула является "межформенным контролем", осуществляющим анализ данных из разных разделов (форм).
Шаг 3: Генерация и исполнение итогового SQL-запроса.
На основе ответа и шаблона TYPE_I генерируется готовый к исполнению SQL. Вот что получается в нашем случае:
WITH FORMULA_DETAILS AS
(SELECT '20250704' AS TAXIDENTIFIER
, '20250731' AS DRAFTIDENTIFIER
, 'http://www.cbr.ru/xbrl/nso/purcb/rep/2025-07-04/tab/sr_R10' AS FORMULAURI
, 'valueAssertion_R10_25' AS FORMULACODE
, 'WARNING' AS FORMULASEVERITY
, 'Раздел 10. Отсутствие значений у элемента Дата заключения внебиржевого ПФИ purcb-dic:DFI_BgnDt не допускается при указании значения 71 внебиржевой ПФИ или 72 биржевой срочный контракт в элементе данных Вид актива инструмента, контракта purcb-dic:Asst_Tp.' AS FORMULAUNSATISFIEDMESSAGE)
, DATA_ASPECT0 AS
(SELECT DISTINCT dim_int_Asst_IdTaxis
, COALESCE(purcb_dic_DFI_BgnDt_dimGrp_1_periodGrp_1,
'0') AS DFI_BgnDt
, purcb_dic_DFI_BgnDt_dimGrp_1_periodGrp_1 AS originalValue_DFI_BgnDt
, COALESCE(purcb_dic_Asst_Tp_dimGrp_1_periodGrp_1,
'0') AS Asst_Tp
, purcb_dic_Asst_Tp_dimGrp_1_periodGrp_1 AS originalValue_Asst_Tp
, '20250704_20250731_sr_R10' as aspect_source
FROM FCT_CSVSTAGE_20250704_20250731_sr_R10
WHERE UUID = '&UUID&')
, periodAspect as (SELECT 'PERIODINSTANT=$par:refPeriodEnd' AS PERIOD_CODE)
INSERT INTO TABLE fct_xbrl_csv_scontrols PARTITION (uuid='&UUID&')
SELECT RR.*
FROM (SELECT FORMULA_DETAILS.TAXIDENTIFIER
, FORMULA_DETAILS.DRAFTIDENTIFIER
, FORMULA_DETAILS.FORMULAURI
, FORMULA_DETAILS.FORMULACODE
, FORMULA_DETAILS.FORMULASEVERITY
, FORMULA_DETAILS.FORMULAUNSATISFIEDMESSAGE
, CONCAT('dsrs0->20250704_20250731_sr_R10', ';',
'dim_int_Asst_IdTaxis->', dim_int_Asst_IdTaxis, ';'
) AS ROW_ASPECT
, CONCAT('periodAspect->', periodAspect.PERIOD_CODE) AS PERIOD_ASPECT
, CONCAT(
'Asst_Tp->', COALESCE(Asst_Tp, 'NULL'), ';'
) AS PRECONDITION_ASPECT
, CONCAT(
'DFI_BgnDt->', COALESCE(DFI_BgnDt, 'NULL'), ';'
) AS TEST_ASPECT
, CASE WHEN originalValue_DFI_BgnDt is not null THEN 1 ELSE 0 END AS RESULT
FROM DATA_ASPECT0,
FORMULA_DETAILS,
periodAspect
WHERE (
originalValue_DFI_BgnDt IS NOT NULL
OR
originalValue_Asst_Tp IS NOT NULL
) AND (1 = 1
)
AND (Asst_Tp) = '71'
OR (Asst_Tp) = '72'
) RR
Этот запрос делает следующее:
FORMULA_DETAILS: Задает метаинформацию о формуле.DATA_ASPECT0: Выбирает данные из целевой CSV-таблицы, подставляя значения по умолчанию (fallBackValue) и сохраняя оригинальные значения для проверки.-
Основной
INSERT ... SELECT:Рассчитывает аспекты срабатывания формулы (
ROW_ASPECT,PERIOD_ASPECT) для детализации ошибки.Вычисляет результат проверки (
RESULT):1, если условие выполнено (дата не пуста), и0в противном случае.WHERE-клаузула реализует предусловие формулы, фильтруя только те строки, где вид актива равен '71' или '72'.
Результаты запроса (все строки, где RESULT=0) записываются в таблицу нарушений, формируя итоговый протокол проверки контрольного соотношения.
Гибкость и расширяемость: ваш собственный движок проверок
Одна из самых мощных возможностей спецификации — это ее открытость. Если стандартный набор шаблонов MyBatis не покрывает вашу уникальную потребность, вы можете разработать свой собственный шаблон.
Для этого вам на вход будет предоставлен универсальный complexResponse — JSON-документ ответа. Ваша задача — написать шаблон на MyBatis, который преобразует структуру этого документа в целевой SQL-запрос.
Это открывает дорогу для:
Оптимизации запросов под конкретную СУБД.
Реализации сложных, специфичных для бизнеса логик проверки, которые сложно выразить в XBRL.
Создания собственных «формул» напрямую на основе маппинга, минуя стадию XBRL.
Заключение
Спецификация отделяемых формул завершает картину XBRL-CSV2 как целостного и самодостаточного формата. Мы не только превратили данные в удобные таблицы, но и научились автоматически преобразовывать сложные бизнес-правила XBRL в эффективные SQL-скрипты.
Этот подход обеспечивает беспрецедентную гибкость и производительность. Регулятор и участники рынка получают мощный, стандартизированный и при этом невероятно практичный инструмент для сбора и, что важно, качественной проверки больших объемов отчетных данных.
Таким образом, мы сохраняем все преимущества экосистемы XBRL для авторов таксономий, но при этом даем обработчикам данных возможность использовать весь арсенал современных SQL-инструментов.
Полезные ссылки:
Спецификация XBRL-CSV на сайте ЦБ РФ: https://cbr.ru/projects_xbrl/taxonomy_xbrl/xbrl-csv
Первая часть статьи: ссылка
Что думаете о таком подходе к валидации данных? Сталкивались ли вы с подобными задачами в своих проектах?