В юридической практике корпоративных юристов относительно недавно (несколько лет назад) появилась необходимость составлять и подавать уведомления о контролируемых иностранных компаниях (КИК) в рамках ст. 25.13 НК РФ. Суть этой обязанности — составить и подать документ, в котором будут отражены все связи общества в холдинге по цепочкам от текущего ООО (АО) в РФ до владельца- налогового резидента РФ КИК. Говоря проще, если офшором владеет россиянин (налоговый резидент РФ), а офшор российским ООО (даже через забор промежуточных ООО) более 25 % — уведомлению быть. Изюминка в том, что подавать необходимо всем ООО (АО) в которых эта ситуация наблюдается и подавать как сведения о владении более 25%, так и последующие изменения доли владения своевременно, иначе штрафы (100 000 рублей по каждой компании в цепочке — ст. 129.6 НК РФ). Так как холдинг (совокупность юр. лиц) организм живой и постоянные изменения долей владения неизбежны, за всем этим надо как-то следить, чтобы не насобирать штрафов. Как упростить работу в данном направлении, автоматизировать ее, посвящена данная статья. Статья также будет интересна с точки зрения графического представления связанных структур, например соц. сетей.

В данной статье не будем останавливаться на юридических аспектах подаваемых уве��омлений о КИК, об участии в КИК, рассмотрим техническую сторону вопроса.
Бесспорно, если холдинг, о котором идет речь представляет себя простые структуры вида ООО->КИК->россиянин, то, что-то строить здесь с привлечением машины нецелесообразно, другое дело, если структура ветвится, двоится и нет числа этим сплетениям.
Рассмотрим несколько существующих графических решений, которые упростят работу.
Для удобства визуализации будет использоваться среда jupyter notebook и python.
Данное решение самое древнее из представленных и не может похвастаться своей интерактивностью. О данном пакете есть такая же древняя статья на Хабре.
Однако старое не значит плохое, и данный вариант один из наиболее удачных как в плане рисования, так и в вычислительном.
Установим и импортируем модуль через jupyter:
Также импортируем иные доп. модули, которые помогут нарисовать фигуры:
Построим с помощью networkx первую сеть:
Вот, что получилось:

Как видно, Иванов владеет двумя КИКами, которые, в свою очередь, владеют российскими юр. лицами.
Разберем код выше.
Импортировали модуль и указали откуда будем считывать данные на диске:
В текущей директории считали 'example.edgelist':
*example.edgelist — это обычный текстовый файл вида:
Значения записаны кто-кем владеет с пробелом между ними.
Далее определили как будет выглядеть сеть:
Если поменять на pos = nx.spring_layout(G), то она примет вид:

И это расположение, как ни странно, наиболее подходящее для более масштабных структур.
Наконец, нарисовали сеть, обозначив отступы:
Сохранить картинку просто:
Здесь мы отступы не сделали, и названия «уехали»:

Networkx оперирует понятиями нод(nodes) и связей(edges) между ними. В нашей ситуации ноды — это Иванов, КИК1, КИК2, Ромашка_ООО, Ведро_АО. А связи — то, что находится в файле example.edgelist.
Посмотреть и то и другое можно просто, обратившись к методам G.nodes и G.edges:

Проясним немного построенную сеть, добавим стрелочки:
Небольшие изменения позволили нарисовать более внятную картину кто кем владеет:

В коде, как можно заметить, изменения минимальны.
Следующий этап — построение графика, где будут видны размеры пакетов владения.
Для этого надо познакомиться с понятием веса (weight) это третий основной параметр, с которым может работать networkx. Чтобы его включить в работу, в текстовый файл надо добавить эти самые веса, например так:
Теперь заново построим сеть, используя уже веса и обозначим их на графике:
*example2.edgelist — это файл, который сформирован выше с весами.
Получим вот такую картину:

Теперь нам как юристам-программистам, надо понять, в какой последовательности и в каком размере владеет Иванов, например компанией Ведро_АО и владеет ли вообще. Это требуется, чтобы определить в разветвленном холдинге факт владения и все цепочки до целевой ООО (АО), чтобы потом эти цепочки прописать в уведомление по КИК.
С помощью networkx сделать это можно следующим образом:
В качестве первого аргумента идет нода-владелец, вторым — нода, до которой мы будем строить пути.
Используя данный метод можно увидеть, что Ведром_АО Иванов владеет по следующим цепочкам:
Графически это подтверждается.
Узнать долю владения можно перемножив веса между соответствующими нодами: 1*0,5*1=0,5, 1*1=1. Доля более 25%, уведомление необходимо подавать.
В коде перемножение делается следующими костылями (более изящный метод пока не найден):
В первом случае выведет долю 0,5, во втором 1.
Какие еще есть доступные варианты визуализации? Например, Netwulf.
Документация находится здесь.
Сама сеть интерактивна, в этом ее основной плюс. После установки python пакета, построим сеть:
После запуска кода, jupyter подвисает, а в дополнительно открывшемся окне браузера виден результат:

Справа на панели видны опции, изменение которых влияет в режиме онлайн на построенную сеть.
Минус данного пакета — пока нельзя отобразить веса и стрелки между нодами, но авторы обещали это доработать.
*чтобы вернуться в jupyter понадобится нажать на опцию «post to python»:

Еще один неплохой вариант подобной визуализации для python — молодой проект webweb.
Документация здесь.
Строится сеть схожим образом:

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

Резюмируя, можно сказать, что развивающиеся потомки networkx — netwulf и webweb хороши для построения быстрой картинки структуры небольшого холдинга. У обоих модулей есть режим freeze, чтобы заморозить ноды, которые слипаются в одну кучу в силу интерактивности графика. Однако даже используя их непросто работать с масштабными структурами, где количество нод больше 200.
Все было бы совсем хорошо при построении подобных структур, если бы не одно но, которое портит всю картину. Это но заключается в том, что в холдингах общества владеют сами собой через другие юр. лица и это называется либо перекрестное либо кольцевое владение.
На картинках в письмах от Минфина (например от 02.07.2013 ОА-4-13/11912) это выглядит так.
Перекрестное владение:

Кольцевое:

Посмотрим, как определит связи networkx для схемы перекрестного владения участия D в B.
Создадим edgelist со связями:
Построив сеть с весами, можно увидеть, что обратная связь между A и B не отражена:

Ее можно увидеть, если построить сеть без весов, со стрелками:

Что с расчетами? Какова совокупная доля D в B?
Тут кажется все прозрачно, 45%
И networkx выдает при команде list(nx.all_simple_paths(G,'D', 'B')):
[['D', 'B']]
Но не все так просто.
Минфин говорит, совокупная доля D в B определяется по формуле:

И составит 57,69%.
Что делать? networkx бессильна?
Вовсе нет, networkx позволит выявить подобные ситуации, но вот формула расчета будет другой, согласно «букве Закона».
Частично проблему можно снять, добавив в edgelist записи
A A
B B
Далее командой list(nx.nodes_with_selfloops(G)) можно посмотреть ноды с участием в самих себе, но при определении путей из D в B это все равно не учитывается.
jupyter тетрадка скачать — здесь.

В данной статье не будем останавливаться на юридических аспектах подаваемых уве��омлений о КИК, об участии в КИК, рассмотрим техническую сторону вопроса.
Бесспорно, если холдинг, о котором идет речь представляет себя простые структуры вида ООО->КИК->россиянин, то, что-то строить здесь с привлечением машины нецелесообразно, другое дело, если структура ветвится, двоится и нет числа этим сплетениям.
Рассмотрим несколько существующих графических решений, которые упростят работу.
Для удобства визуализации будет использоваться среда jupyter notebook и python.
Networkx
Данное решение самое древнее из представленных и не может похвастаться своей интерактивностью. О данном пакете есть такая же древняя статья на Хабре.
Однако старое не значит плохое, и данный вариант один из наиболее удачных как в плане рисования, так и в вычислительном.
Установим и импортируем модуль через jupyter:
!pip install networkx import networkx as nx
Также импортируем иные доп. модули, которые помогут нарисовать фигуры:
from matplotlib import pyplot as plt %matplotlib inline plt.rcParams.update({ 'figure.figsize': (7.5, 7.5), 'axes.spines.right': False, 'axes.spines.left': False, 'axes.spines.top': False, 'axes.spines.bottom': False})
Построим с помощью networkx первую сеть:
from pathlib import Path data_dir = Path('.') / 'data' # Read edge list G = nx.read_edgelist('example.edgelist') # Draw network #pos = nx.spring_layout(G) pos = nx.spectral_layout(G) #pos = nx.planar_layout(G) nx.draw_networkx(G, pos) plt.gca().margins(0.15, 0.15)
Вот, что получилось:

Как видно, Иванов владеет двумя КИКами, которые, в свою очередь, владеют российскими юр. лицами.
Разберем код выше.
Импортировали модуль и указали откуда будем считывать данные на диске:
from pathlib import Path data_dir = Path('.') / 'data'
В текущей директории считали 'example.edgelist':
G = nx.read_edgelist('example.edgelist')
*example.edgelist — это обычный текстовый файл вида:
# source target Иванов КИК1 Иванов КИК2 КИК1 КИК2 КИК1 Ромашка_ООО КИК2 Ведро_АО
Значения записаны кто-кем владеет с пробелом между ними.
Далее определили как будет выглядеть сеть:
pos = nx.spectral_layout(G)
Если поменять на pos = nx.spring_layout(G), то она примет вид:

И это расположение, как ни странно, наиболее подходящее для более масштабных структур.
Наконец, нарисовали сеть, обозначив отступы:
nx.draw_networkx(G, pos) plt.gca().margins(0.15, 0.15)
Сохранить картинку просто:
plt.savefig('plot.png')
Как нарисовать сегмент в networkx
#подграфик H = G.subgraph(['Иванов', 'КИК1', 'Ромашка_ООО']) plt.subplot(212) print("Подграфик:") nx.draw_networkx(H)
Здесь мы отступы не сделали, и названия «уехали»:

Networkx оперирует понятиями нод(nodes) и связей(edges) между ними. В нашей ситуации ноды — это Иванов, КИК1, КИК2, Ромашка_ООО, Ведро_АО. А связи — то, что находится в файле example.edgelist.
Посмотреть и то и другое можно просто, обратившись к методам G.nodes и G.edges:

Направленный график в networkx (Directed edge list)
Проясним немного построенную сеть, добавим стрелочки:
# Read edge list G = nx.read_edgelist( str('example.edgelist'), create_using=nx.DiGraph) pos = nx.spectral_layout(G) # Draw network nx.draw_networkx(G, pos, arrowsize=20) plt.gca().margins(0.15, 0.15)
Небольшие изменения позволили нарисовать более внятную картину кто кем владеет:

В коде, как можно заметить, изменения минимальны.
Следующий этап — построение графика, где будут видны размеры пакетов владения.
Для этого надо познакомиться с понятием веса (weight) это третий основной параметр, с которым может работать networkx. Чтобы его включить в работу, в текстовый файл надо добавить эти самые веса, например так:
# source target Иванов КИК1 100 Иванов КИК2 100 КИК1 КИК2 50 КИК1 Ромашка_ООО 100 КИК2 Ведро_АО 100
Теперь заново построим сеть, используя уже веса и обозначим их на графике:
# Read edge list G = nx.read_weighted_edgelist( str('example2.edgelist')) # Extract weights weights = [d['weight'] for s, t, d in G.edges(data=True)] nx.draw_networkx(G,pos) labels = nx.get_edge_attributes(G,'weight') nx.draw_networkx_edge_labels(G,pos,edge_labels=labels) plt.gca().margins(0.15, 0.15)
*example2.edgelist — это файл, который сформирован выше с весами.
Получим вот такую картину:

Кто кем и как владеет, networkx
Теперь нам как юристам-программистам, надо понять, в какой последовательности и в каком размере владеет Иванов, например компанией Ведро_АО и владеет ли вообще. Это требуется, чтобы определить в разветвленном холдинге факт владения и все цепочки до целевой ООО (АО), чтобы потом эти цепочки прописать в уведомление по КИК.
С помощью networkx сделать это можно следующим образом:
list(nx.all_simple_paths(G,'Иванов', 'Ведро_АО'))
В качестве первого аргумента идет нода-владелец, вторым — нода, до которой мы будем строить пути.
Используя данный метод можно увидеть, что Ведром_АО Иванов владеет по следующим цепочкам:
[['Иванов', 'КИК1', 'КИК2', 'Ведро_АО'], ['Иванов', 'КИК2', 'Ведро_АО']]
Графически это подтверждается.
Узнать долю владения можно перемножив веса между соответствующими нодами: 1*0,5*1=0,5, 1*1=1. Доля более 25%, уведомление необходимо подавать.
В коде перемножение делается следующими костылями (более изящный метод пока не найден):
x=0 b=0 c=[] for i in list(nx.all_simple_paths(G,'Иванов', 'Ведро_АО')): for a in i: if x>len(i)-2: pass else: b=int(nx.bidirectional_dijkstra(G, i[x],i[x+1])[0])#доля владения x+=1 c.append(b/100) print(c) import numpy as np print(np.prod(c))
x=0 b=0 c=[] for i in list(nx.all_shortest_paths(G,'Иванов', 'Ведро_АО')): for a in i: if x>len(i)-2: pass else: b=int(nx.bidirectional_dijkstra(G, i[x],i[x+1])[0])#доля владения x+=1 c.append(b/100) print(c) import numpy as np print(np.prod(c))
В первом случае выведет долю 0,5, во втором 1.
Какие еще есть доступные варианты визуализации? Например, Netwulf.
Netwulf
Документация находится здесь.
Сама сеть интерактивна, в этом ее основной плюс. После установки python пакета, построим сеть:
import netwulf as nw plt.figure(figsize=(200,200)) G = nx.read_weighted_edgelist(str('example2.edgelist'),create_using=nx.DiGraph) pos = nx.spring_layout(G) nw.visualize(G)
После запуска кода, jupyter подвисает, а в дополнительно открывшемся окне браузера виден результат:

Справа на панели видны опции, изменение которых влияет в режиме онлайн на построенную сеть.
Минус данного пакета — пока нельзя отобразить веса и стрелки между нодами, но авторы обещали это доработать.
*чтобы вернуться в jupyter понадобится нажать на опцию «post to python»:

Еще один неплохой вариант подобной визуализации для python — молодой проект webweb.
Webweb
Документация здесь.
Строится сеть схожим образом:
from webweb import Web web = Web(title='kitchen_sink') web.display.networkName = 'tree' web.display.networkLayer = 2 web.display.colorBy = 'ring' web.display.sizeBy = 'degree' web.display.gravity = .3 web.display.charge = 30 web.display.linkLength = 15 web.display.colorPalette = 'Greens' web.display.scaleLinkOpacity = False web.display.scaleLinkWidth = True from pathlib import Path data_dir = Path('.') / 'data' # Read edge list G = nx.read_edgelist('example.edgelist',create_using=nx.DiGraph) plt.figure(figsize=(200,200)) # Draw network pos = nx.spring_layout(G) Web(list(G.edges)).show()

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

Резюмируя, можно сказать, что развивающиеся потомки networkx — netwulf и webweb хороши для построения быстрой картинки структуры небольшого холдинга. У обоих модулей есть режим freeze, чтобы заморозить ноды, которые слипаются в одну кучу в силу интерактивности графика. Однако даже используя их непросто работать с масштабными структурами, где количество нод больше 200.
«Подножка» от Минфина, перекрестное и кольцевое владение
Все было бы совсем хорошо при построении подобных структур, если бы не одно но, которое портит всю картину. Это но заключается в том, что в холдингах общества владеют сами собой через другие юр. лица и это называется либо перекрестное либо кольцевое владение.
На картинках в письмах от Минфина (например от 02.07.2013 ОА-4-13/11912) это выглядит так.
Перекрестное владение:

Кольцевое:

Посмотрим, как определит связи networkx для схемы перекрестного владения участия D в B.
Создадим edgelist со связями:
# source target D B 45 B A 40 A B 55 E A 60
Построив сеть с весами, можно увидеть, что обратная связь между A и B не отражена:

Ее можно увидеть, если построить сеть без весов, со стрелками:

Что с расчетами? Какова совокупная доля D в B?
Тут кажется все прозрачно, 45%
И networkx выдает при команде list(nx.all_simple_paths(G,'D', 'B')):
[['D', 'B']]
Но не все так просто.
Минфин говорит, совокупная доля D в B определяется по формуле:

И составит 57,69%.
Что делать? networkx бессильна?
Вовсе нет, networkx позволит выявить подобные ситуации, но вот формула расчета будет другой, согласно «букве Закона».
Частично проблему можно снять, добавив в edgelist записи
A A
B B
Далее командой list(nx.nodes_with_selfloops(G)) можно посмотреть ноды с участием в самих себе, но при определении путей из D в B это все равно не учитывается.
jupyter тетрадка скачать — здесь.
