---
search:
  exclude: true
---

# Silver

В данном разделе приведены сведения о Silver, плагине для СУБД Picodata.

!!! tip "Picodata Enterprise"
    Функциональность плагина доступна только в
    коммерческой версии Picodata.

## Общие сведения {: #intro }

Плагин Silver используется для расчета себестоимости при работе с Picodata.

## Состав плагина {: #plugin_files }

Внутри архива с плагином находится структура вложенных директорий,
включающая имя и версию плагина, а также его файлы:

```
└── silver
    └── 0.6.2
        ├── libsilver.so
        ├── manifest.yaml
        ├── migrations
        │   ├── 0001_tables.db
        │   └── 0002_calc_settings.db
        └── silver-cli
```

Основная логика плагина обеспечивается разделяемой библиотекой
`libsilver.so`. Исходная конфигурация плагина задается в файле манифеста
(`manifest.yaml`). В директории `migrations` находятся миграции плагина:
они создают/удаляют тестовые таблицы для проведения [вычислений расчета
стоимости](#algorithm_description) при включении/отключении плагина
соответственно. Исполняемый файл `silver-cli` используется для
выполнения операций в плагине: загрузки данных, запуска расчета, очистки
таблиц с промежуточными результатами (при необходимости).

## Подключение плагина {: #plugin_enable }

Содержимое архива с плагином следует распаковать в любую удобную
директорию, которую после этого нужно будет указать как `PLUGIN_DIR` для
инстанса Picodata.

При запуске одного инстанса из [командной строки] директорию плагина
можно указать с помощью параметра:

```bash
picodata run --plugin-dir=<PLUGIN-DIR> ...
```

Однако, для полноценной использования плагина рекомендуется запустить кластер с помощью [роли Ansible].

[командной строки]: ../reference/cli.md
[роли Ansible]: ../admin/deploy_ansible.md

После запуска Picodata с поддержкой плагинов в заданной директории подключитесь к [административной
консоли] инстанса Picodata.

Установите плагин, добавьте его к тиру, запустите миграции и включите
его с помощью следующих SQL-команд:

```sql
CREATE PLUGIN silver 0.6.2;
ALTER PLUGIN silver 0.6.2 ADD SERVICE silver TO TIER default;
ALTER PLUGIN silver MIGRATE TO 0.6.2;
ALTER PLUGIN silver 0.6.2 ENABLE;
```

Для диагностики работы плагина обратитесь к [отладочному журналу] инстанса Picodata.

[отладочному журналу]: ../admin/local_monitoring.md#reading_log
[административной консоли]: ../tutorial/connecting.md#admin_console

## Конфигурация плагина {: #silver_config }

Исходная конфигурация плагина задается в файле `manifest.yaml`:

```yaml
description: Plugin Silver - costs allocation
name: silver
version: 0.6.2
services:
  - name: silver
    description: Costs allocation
    default_configuration:
      clear_intermediate: true
      complete_batch_size: 50
      complete_pool_size: 100
      difference_limit: 50
      iterations_limit: 150
      max_step: 150
      node_difference_limit: 0
      results_allocation_only: false
      step_batch_size: 50
      step_pool_size: 2000
migration:
  - migrations/0001_tables.db
  - migrations/0002_results_sum_type.db
```

Для изменения доступны следующие параметры в блоке `default_configuration`:

- `clear_intermediate` — признак удаления промежуточных результатов
- `complete_batch_size` — размер пакета для записи окончательных результатов расчета
- `complete_pool_size` — количество вершин (узлов расчета), одновременно сохраняющих окончательные результаты
- `difference_limit` — размер общей допустимой погрешности. Если сумма входящих затрат во всех вершинах в начале очередного шага будет меньше значения этого параметра, расчет завершится
- `iterations_limit` — ограничение на число записей, которые обработает отдельный узел расчета перед тем, как отдать управление другому узлу
- `max_step` — максимально число итераций расчета
- `node_difference_limit` — аналогично `difference_limit`, но для отдельной вершины (узла расчета)
- `results_allocation_only` — сохранять в результате только распределенные затраты, без учета плановых сумм (базы распределения)
- `step_batch_size` — размер пакета для записи промежуточных результатов
- `step_pool_size` — количество вершин (узлов расчета), выполняющих расчет одновременно

## Управление действиями плагина {: #silver_cli }

Для выполнения некоторых операций в плагине используйте консольное приложение
`silver-cli`, входящее в состав поставки плагина. Функциональность приложения:

- загрузка данных в таблицы плагина отдельными порциями (пакетами)
- запуск калькуляций
- очистка таблиц с промежуточными результатами калькуляций

## Описание алгоритма расчета и распределения затрат  {: #algorithm_description }

Расчет реализован в виде алгоритма над графом, где каждая вершина
представляет собой центр возникновения затрат (узел расчета), а грани —
отнесение части затрат на другой центр в соответствии с коэффициентом
распределения. Ввиду того, что чаще всего в таком графе существуют
циклические зависимости (когда затраты узла расчета 1 распределяются на
узел расчета 2, а затраты узла расчета 2, в свою очередь, распределяются
на узел расчета 1, напрямую либо через несколько промежуточных узлов
расчета), распределение затрат осуществляется в ходе последовательных
итераций. Критерием остановки расчета может служить:

- достижение заданного лимита итераций
- уменьшение общей суммы затрат к распределению на очередной итерации до
  суммы приемлемой общей погрешности
- уменьшение суммы входящих затрат для одного узла расчета до суммы
  приемлемой погрешности для узла

Так как данные для расчета распределены между отдельными инстансами СУБД
Picodata, то выполнение итераций производится параллельно, что позволяет
ускорить общее время расчета. При этом, результаты отдельной итерации
записываются в таблицу промежуточных результатов.

!!! note "Примечание"
    Все рассчитанные суммы округляются до копеек (2 знака после запятой).

### Начальные данные {: #incoming_data }

Начальные данные объединяются в наборы, что позволяет хранить (и
обрабатывать) данные для нескольких сценариев. Данные состоят из базы
распределения и начальных входящих затрат, которые хранятся в следующих
таблицах:

#### Наборы данных {: #datasets }

| N | Поле | Тип | Описание |
|---|------|-----|----------|
| 1 | set_id | uuid | идентификатор набора данных |
| 2 | name | string | наименование набора данных |
| 3 | year | unsigned int | год периода данных |
| 4 | month | unsigned int | месяц периода данных |

#### База распределения {: #distribution }

| N | Поле | Тип | Описание |
|---|------|-----|----------|
| 1 | id | string | идентификатор записи |
| 2 | set_id | uuid | идентификатор набора данных |
| 3 | sender_id | string | узел-отправитель |
| 4 | receiver_id | string | узел-получатель |
| 5 | prod_id | string | код продукции (услуги, работы) |
| 6 | base_value | decimal | значение для получателя |

#### Начальные затраты {: #initial_costs }

| N | Поле | Тип | Описание |
|---|------|-----|----------|
| 1 | id | string | идентификатор записи |
| 2 | set_id | uuid | идентификатор набора данных |
| 3 | receiver_id | string | узел-получатель затрат |
| 4 | cost_type | string | код затраты |
| 5 | cost_sum | decimal | сумма затраты |


### Этапы выполнения алгоритма {: #algorithm_stages }

#### Подготовка к расчету — создание узлов расчета {: #stage0 }

Перед запуском расчета для каждого узла собирается база
распределения из соответствующей таблицы. При этом считается общее
значение базы (**OCB**) и суммируется значение базы по каждому коду
продукции (услуге, работе) (формула 1):

$$ ОСВ = \sum_{i=1}^k СВ_i $$

где *i* — порядковый номер кода продукции (услуги, работы) (далее — код
выпуска),

$СВ_i$ — сумма значений базы распределения (выпуска) этого кода
продукции (работы, услуги), вычисляется по формуле 2:

$$ СВ = \sum_{j=1}^n В_j $$

где *j* — порядковый номер получателя кода выпуска,

$В_j$ — значение для получателя.

#### Итеративное распределение затрат {: #stage1 }

На каждой итерации расчета в каждом узле выполняются следующие
действия:

- выбираются входящие затраты (для первого шага — из таблицы начальных
  затрат, для следующих — из соответствующей текущей итерации таблицы
  промежуточных результатов)
    - если задана сумма приемлемой погрешности для узла и общая сумма
      входящих затрат меньше этого значения, то распределение затрат не
      выполняется и расчет для узла считается завершенным
    - иначе для каждого кода затраты:
        - задаются начальные значения для распределения по кодам
          выпуска:
        - $СРЗ$ (сумма распределяемой затраты), равная сумме затраты и
        - $БРЗ$ (база распределения затраты), равная $ОСВ$ из
          формулы 1;
    - для каждого кода выпуска вычисляется часть затраты к распределению
      на выпуск ($СЗВ$):

        если $БРЗ$ равно $СВ$ текущего кода выпуска из формулы 2, то
        (формула 3):

        $$ СЗВ = СРЗ $$

        иначе (формула 4):

        $$ СЗВ = { СРЗ \over БРЗ }  * СВ $$

        После этого (формула 5):

        $$ СРЗ = СРЗ - СЗВ $$

        и (формула 6):

        $$ БРЗ = БРЗ - СВ $$

    - полученное значение $СЗВ$ распределяется по получателям:
        - задаются начальные значения для распределения по получателям:
            - $СРЗП$ (сумма распределяемой затраты по получателям),
              равная $СЗВ$, и
            - $БРЗП$ (база распределения затраты по получателям),
              равная $В$ из формулы 2 для получателя;
        - для каждого получателя вычисляется часть затраты на получателя
          ($СЗП$):

            если $БРЗП$ равно $В$ текущего получателя, то (формула
            7):

            $$ СЗП = СРЗП $$

            иначе (формула 8):

            $$ СЗП = { СРЗП \over БРЗП } * В $$

        - полученное значение $СЗП$ увеличивает накопленную сумму
          затрат для сочетания “код выпуска” — “код затраты” —
          “получатель” и промежуточный результат для сочетания “код
          затраты” — “получатель”
- после окончания распределения затрат накопленные промежуточные
  результаты сохраняются в соответствующую следующей итерации таблицу.

По окончании итерации проверяется ее порядковый номер. Если задан лимит
на количество итераций и номер текущей итерации достиг его, то расчет
считается завершенным.

Также проверяется общая сумма входящих затрат для следующей итерации.
Если задана сумма приемлемой общей погрешности и общая сумма затрат
меньше этого значения, или нет затрат к распределению на следующей
итерации, то расчет считается завершенным.

В противном случае запускается следующая итерация расчета.

### Сохранение результатов {: #saving_results }

По окончании расчета каждый узел сохраняет накопленные суммы
затрат (для сочетаний “код выпуска” — “код затраты” — “получатель”) в
таблицу результатов:

| N | Поле | Тип | Описание |
|---|------|-----|----------|
| 1 | id | string | идентификатор записи |
| 2 | calc_id | uuid | идентификатор расчета |
| 3 | sender_id | string | узел-отправитель затрат |
| 4 | receiver_id | string | узел-получатель затрат |
| 4 | cost_type | string | код затраты |
| 6 | prod_id | string | код выпуска продукции (работы, услуги) |
| 5 | cost_sum | decimal | сумма затраты |
