type: standard
aspect: structure
title: "ДРЕВОВИДНОЕ ПРЕДСТАВЛЕНИЕ ИЕРАРХИИ"
version: 1.0.0
date: 2026-02-19
status: active
Версия: 1.0.0
Дата: 2025-12-22
Уровень: L2 (Стандарт)
Статус: CANONICAL
Представление всей системной иерархии в виде дерева:
- Простое дерево (single parent)
- Граф зависимостей (multiple parents)
- Бинарное дерево (для алгоритмов)
NODE {
id: UUID,
name: STRING,
type: NODE_TYPE,
parent: UUID | null,
children: [UUID],
level: INTEGER,
path: STRING, // Полный путь от корня
metadata: MAP
}
1. У каждого узла ровно один родитель (кроме корня)
2. Корень не имеет родителя (parent = null)
3. Листья не имеют детей (children = [])
4. Нет циклов
5. Уровень ребенка = уровень родителя + 1
UNIVERSE (∞)
│
└─── WORKSPACE (L0)
│ id: root
│ path: /
│
├─── architect/ (L1, Информационная)
│ │ id: arch-001
│ │ path: /architect
│ │ type: knowledge_base
│ │ role: INFORMATIONAL
│ │
│ ├─── theory/ (документы)
│ │ ├─── MERKABA.md
│ │ ├─── QUESTIONS.md
│ │ └─── SYSTEMS.md
│ │
│ ├─── concept/ (документы)
│ │ ├─── PLATFORM.md
│ │ └─── ROLES.md
│ │
│ └─── standards/ (документы)
│ ├─── PRINCIPLES.md
│ ├─── structure/
│ │ ├─── ENTITY_HIERARCHY.md
│ │ ├─── ENTITY_CATALOG.md
│ │ └─── UNIVERSAL_SCHEMA.md
│ └─── 7-typology/
│
├─── infra/ (L1, Обеспечивающая)
│ │ id: infra-001
│ │ path: /infra
│ │ role: SUPPORTING
│ │
│ ├─── @beget-kondurov.server/ (L3, Модуль)
│ │ │ id: server-001
│ │ │ type: server
│ │ │ status: production
│ │ │
│ │ └─── solution/
│ │ └─── configs/
│ │
│ ├─── @dev-pro.server/ (L3)
│ └─── @router.network/ (L3)
│
├─── system/ (L1, Обеспечивающая)
│ │ id: sys-001
│ │ path: /system
│ │ role: SUPPORTING
│ │
│ ├─── @hub.service/ (L3, Сервис)
│ │ │ type: storage_service
│ │ │ provides: S3-compatible storage
│ │ │
│ │ └─── solution/
│ │ └─── minio/
│ │
│ ├─── @orchestrator.service/ (L3)
│ └─── @monitor.service/ (L3)
│
└─── projects/ (контейнер)
│
├─── pirotehnika/ (L1, Целевая)
│ │ id: piro-001
│ │ path: /projects/pirotehnika
│ │ type: business
│ │ role: TARGET
│ │ questions: 9
│ │
│ ├─── [ФАЙЛЫ]
│ │ ├─── CLAUDE.md
│ │ ├─── index.yaml
│ │ └─── PROJECT.md (9 вопросов)
│ │
│ ├─── design/
│ │ ├─── STRATEGY.md
│ │ └─── BUSINESS_MODEL.md
│ │
│ ├─── management/
│ │ ├─── STATUS.md
│ │ └─── TODO.md
│ │
│ ├─── _shared/ (данные)
│ │ └─── data/
│ │
│ ├─── retail/ (L2, Направление, Целевая)
│ │ │ id: piro-retail-001
│ │ │ path: /projects/pirotehnika/retail
│ │ │ role: main
│ │ │ questions: 9
│ │ │
│ │ ├─── [ФАЙЛЫ]
│ │ │ ├─── CLAUDE.md
│ │ │ └─── index.yaml
│ │ │
│ │ ├─── @pirotehnika.spb.ru/ (L3, Модуль, Целевая)
│ │ │ │ id: piro-site-001
│ │ │ │ path: /projects/pirotehnika/retail/@pirotehnika.spb.ru
│ │ │ │ type: site
│ │ │ │ status: production
│ │ │ │
│ │ │ ├─── [ФАЙЛЫ]
│ │ │ │ ├─── CLAUDE.md
│ │ │ │ └─── index.yaml
│ │ │ │
│ │ │ ├─── solution/ (КОД)
│ │ │ │ ├─── src/
│ │ │ │ ├─── config/
│ │ │ │ └─── README.md
│ │ │ │
│ │ │ └─── management/
│ │ │ └─── TODO.md
│ │ │ │
│ │ │ ├─── ЗАДАЧА #1 (L4)
│ │ │ │ id: task-001
│ │ │ │ title: "Добавить фильтр"
│ │ │ │ status: in_progress
│ │ │ │
│ │ │ ├─── ЗАДАЧА #2 (L4)
│ │ │ └─── ЗАДАЧА #3 (L4)
│ │ │
│ │ ├─── @admin.app/ (L3)
│ │ └─── _data/ (данные направления)
│ │
│ ├─── ozon/ (L2, Направление, Целевая)
│ │ │ role: satellite
│ │ │ parent: retail
│ │ │
│ │ └─── @ozon.api/ (L3, Модуль, Обеспечивающая)
│ │ │ type: api
│ │ │ questions: 7
│ │ │
│ │ └─── solution/
│ │
│ ├─── data/ (L2, Направление, Информационная)
│ │ │ role: service
│ │ │ questions: 5
│ │ │
│ │ └─── nocodb/
│ │ ├─── schema.sql
│ │ └─── scripts/
│ │
│ └─── services/ (L2, Направление, Обеспечивающая)
│ │ role: service
│ │ questions: 7
│ │
│ └─── @pim.service/ (L3, Модуль, Обеспечивающая)
│ │ type: service
│ │ status: production
│ │
│ ├─── solution/
│ │ ├─── app/
│ │ ├─── scripts/
│ │ │ ├─── sync_all_fields_to_1c.py
│ │ │ └─── scrape_producer_sites.py
│ │ └─── config/
│ │ └─── pim_1c_mapping.yaml
│ │
│ └─── management/
│
├─── lideravto/ (L1, Целевая)
│ └─── ...
│
└─── content-factory/ (L1, Целевая)
└─── ...
tree = {
"root": {
"id": "root",
"name": "WORKSPACE",
"type": "workspace",
"level": 0,
"parent": None,
"children": ["arch-001", "infra-001", "sys-001", "projects"],
"path": "/",
"metadata": {}
},
"arch-001": {
"id": "arch-001",
"name": "architect",
"type": "knowledge_base",
"level": 1,
"parent": "root",
"children": ["arch-theory", "arch-concept", "arch-standards"],
"path": "/architect",
"metadata": {
"role": "INFORMATIONAL",
"questions": 5
}
},
"piro-001": {
"id": "piro-001",
"name": "pirotehnika",
"type": "business",
"level": 1,
"parent": "projects",
"children": ["piro-retail-001", "piro-ozon-001", "piro-data", "piro-services"],
"path": "/projects/pirotehnika",
"metadata": {
"role": "TARGET",
"questions": 9,
"status": "active"
}
},
"piro-retail-001": {
"id": "piro-retail-001",
"name": "retail",
"type": "direction",
"level": 2,
"parent": "piro-001",
"children": ["piro-site-001", "piro-admin-001"],
"path": "/projects/pirotehnika/retail",
"metadata": {
"role": "TARGET",
"questions": 9,
"direction_role": "main"
}
},
"piro-site-001": {
"id": "piro-site-001",
"name": "@pirotehnika.spb.ru",
"type": "module",
"level": 3,
"parent": "piro-retail-001",
"children": ["task-001", "task-002", "task-003"],
"path": "/projects/pirotehnika/retail/@pirotehnika.spb.ru",
"metadata": {
"role": "TARGET",
"questions": 9,
"module_type": "site",
"status": "production"
}
},
"task-001": {
"id": "task-001",
"name": "Добавить фильтр по цене",
"type": "task",
"level": 4,
"parent": "piro-site-001",
"children": [],
"path": "/projects/pirotehnika/retail/@pirotehnika.spb.ru/TODO.md#task-001",
"metadata": {
"status": "in_progress",
"priority": "P0",
"assignee": "owner"
}
}
}
# Получить родителя
def get_parent(node_id: str) -> Node:
node = tree[node_id]
return tree[node.parent] if node.parent else None
# Получить детей
def get_children(node_id: str) -> List[Node]:
node = tree[node_id]
return [tree[child_id] for child_id in node.children]
# Получить путь до корня
def get_path_to_root(node_id: str) -> List[Node]:
path = []
current = node_id
while current:
node = tree[current]
path.append(node)
current = node.parent
return list(reversed(path))
# Получить всех потомков (рекурсивно)
def get_descendants(node_id: str) -> List[Node]:
descendants = []
node = tree[node_id]
for child_id in node.children:
descendants.append(tree[child_id])
descendants.extend(get_descendants(child_id))
return descendants
# Получить глубину поддерева
def get_depth(node_id: str) -> int:
node = tree[node_id]
if not node.children:
return 0
return 1 + max(get_depth(child_id) for child_id in node.children)
# Получить все узлы уровня L
def get_nodes_at_level(level: int) -> List[Node]:
return [node for node in tree.values() if node.level == level]
# Поиск по имени
def find_by_name(name: str) -> List[Node]:
return [node for node in tree.values() if name in node.name]
# Поиск по типу
def find_by_type(node_type: str) -> List[Node]:
return [node for node in tree.values() if node.type == node_type]
# Поиск по пути
def find_by_path(path: str) -> Node:
for node in tree.values():
if node.path == path:
return node
return None
# Поиск по метаданным
def find_by_metadata(key: str, value: Any) -> List[Node]:
return [node for node in tree.values()
if node.metadata.get(key) == value]
# DFS (Depth-First Search) - обход в глубину
def dfs(node_id: str, visit_fn: Callable):
node = tree[node_id]
visit_fn(node)
for child_id in node.children:
dfs(child_id, visit_fn)
# BFS (Breadth-First Search) - обход в ширину
def bfs(node_id: str, visit_fn: Callable):
queue = [node_id]
while queue:
current_id = queue.pop(0)
node = tree[current_id]
visit_fn(node)
queue.extend(node.children)
# Пример: вывести все узлы
def print_tree(node_id: str, indent: int = 0):
node = tree[node_id]
print(" " * indent + f"└─ {node.name} ({node.type}, L{node.level})")
for child_id in node.children:
print_tree(child_id, indent + 1)
Для алгоритмов требующих бинарное дерево (2 ребенка max).
Метод: Left-Child Right-Sibling (LCRS)
N-арное дерево:
A
/ | \
B C D
/ \ |
E F G
Бинарное дерево (LCRS):
A
/
B
/ \
E C
\ \
F D
/
G
Правило:
- Левый ребенок (left) = первый ребенок узла
- Правый ребенок (right) = следующий сосед (sibling)
class BinaryNode:
def __init__(self, value, left=None, right=None):
self.value = value # Данные узла
self.left = left # Первый ребенок
self.right = right # Следующий сосед
def __repr__(self):
return f"Node({self.value})"
def to_binary_tree(node_id: str) -> BinaryNode:
"""Конвертировать N-арное дерево в бинарное (LCRS)"""
node = tree[node_id]
binary = BinaryNode(node)
if not node.children:
return binary
# Первый ребенок → left
binary.left = to_binary_tree(node.children[0])
# Остальные дети → цепочка через right
current = binary.left
for child_id in node.children[1:]:
current.right = to_binary_tree(child_id)
current = current.right
return binary
Исходное дерево:
WORKSPACE
├── architect
│ ├── theory
│ ├── concept
│ └── standards
├── infra
└── projects
└── pirotehnika
├── retail
└── ozon
Бинарное дерево (LCRS):
WORKSPACE (left=architect, right=null)
├── left: architect (left=theory, right=infra)
│ ├── left: theory (left=null, right=concept)
│ │ └── right: concept (left=null, right=standards)
│ │ └── right: standards (left=null, right=null)
│ └── right: infra (left=null, right=projects)
│ └── right: projects (left=pirotehnika, right=null)
│ └── left: pirotehnika (left=retail, right=null)
│ └── left: retail (left=null, right=ozon)
│ └── right: ozon (left=null, right=null)
В реальности узлы могут зависеть друг от друга, создавая DAG (Directed Acyclic Graph).
node = {
"id": "piro-site-001",
"name": "@pirotehnika.spb.ru",
# Иерархическая связь (один родитель)
"parent": "piro-retail-001",
"children": [],
# Функциональные зависимости (много родителей)
"depends_on": [
"piro-pim-001", # PIM сервис
"infra-beget-001", # Сервер
"sys-hub-001" # Hub для медиа
],
# Кому предоставляет сервисы
"provides_to": [],
# Что использует (ресурсы)
"uses": [
"piro-shared-data", # Общие данные
"piro-assets" # Ассеты
]
}
Иерархия (родитель-ребенок):
pirotehnika
└── retail
└── @site
Функциональные зависимости:
@site →depends_on→ @pim.service
@site →depends_on→ @beget.server
@site →uses→ _shared/
@pim.service →provides_to→ @site
@pim.service →provides_to→ @ozon.api
@ozon.api →depends_on→ @pim.service
Граф (все связи):
pirotehnika (L1)
│
├─[contains]─→ retail (L2)
│ │
│ ├─[contains]─→ @site (L3)
│ │ │
│ │ ├─[depends_on]─→ @pim.service (L3)
│ │ ├─[depends_on]─→ @beget.server (L3)
│ │ └─[uses]────────→ _shared/
│ │
│ └─[contains]─→ @ozon.api (L3)
│ └─[depends_on]─→ @pim.service
│
└─[contains]─→ services (L2)
└─[contains]─→ @pim.service (L3)
├─[provides_to]─→ @site
└─[provides_to]─→ @ozon.api
Для определения порядка сборки/деплоя по зависимостям:
def topological_sort(graph: Dict) -> List[str]:
"""Топологическая сортировка DAG"""
# Подсчёт входящих рёбер
in_degree = {node: 0 for node in graph}
for node in graph:
for dep in graph[node].get('depends_on', []):
in_degree[dep] += 1
# Очередь узлов без зависимостей
queue = [node for node in graph if in_degree[node] == 0]
result = []
while queue:
node = queue.pop(0)
result.append(node)
# Уменьшить счётчик для зависимых
for dep in graph[node].get('depends_on', []):
in_degree[dep] -= 1
if in_degree[dep] == 0:
queue.append(dep)
# Проверка на циклы
if len(result) != len(graph):
raise Exception("Граф содержит циклы!")
return result
# Результат: порядок деплоя
# [@beget.server, @pim.service, @site, @ozon.api]
{
"tree_version": "1.0",
"root_id": "root",
"nodes": {
"root": {
"id": "root",
"name": "WORKSPACE",
"type": "workspace",
"level": 0,
"parent": null,
"children": ["arch-001", "infra-001", "projects"],
"path": "/",
"metadata": {}
},
"piro-001": {
"id": "piro-001",
"name": "pirotehnika",
"type": "business",
"level": 1,
"parent": "projects",
"children": ["piro-retail-001", "piro-services"],
"path": "/projects/pirotehnika",
"metadata": {
"role": "TARGET",
"questions": 9,
"status": "active"
}
}
},
"edges": [
{
"type": "contains",
"source": "root",
"target": "arch-001"
},
{
"type": "depends_on",
"source": "piro-site-001",
"target": "piro-pim-001"
}
]
}
CREATE TABLE nodes (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
level INTEGER NOT NULL,
parent_id UUID REFERENCES nodes(id),
path TEXT NOT NULL,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE edges (
id UUID PRIMARY KEY,
type VARCHAR(50) NOT NULL,
source_id UUID REFERENCES nodes(id),
target_id UUID REFERENCES nodes(id),
properties JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Индексы
CREATE INDEX idx_nodes_parent ON nodes(parent_id);
CREATE INDEX idx_nodes_level ON nodes(level);
CREATE INDEX idx_nodes_type ON nodes(type);
CREATE INDEX idx_nodes_path ON nodes(path);
CREATE INDEX idx_edges_source ON edges(source_id);
CREATE INDEX idx_edges_target ON edges(target_id);
CREATE INDEX idx_edges_type ON edges(type);
WORKSPACE
│
├─┬ architect (L1)
│ ├── theory/
│ ├── concept/
│ └── standards/
│
├─┬ infra (L1)
│ ├── @beget.server (L3)
│ └── @dev-pro.server (L3)
│
└─┬ projects
│
└─┬ pirotehnika (L1)
│
├─┬ retail (L2)
│ ├─┬ @site (L3)
│ │ ├── #task-1 (L4)
│ │ └── #task-2 (L4)
│ └── @admin (L3)
│
└─┬ services (L2)
└─┬ @pim.service (L3)
└── #task-5 (L4)
digraph system_tree {
rankdir=TB;
node [shape=box];
// Nodes
workspace [label="WORKSPACE\n(L0)"];
architect [label="architect\n(L1, INFO)"];
pirotehnika [label="pirotehnika\n(L1, TARGET)"];
retail [label="retail\n(L2, main)"];
site [label="@site\n(L3, site)"];
pim [label="@pim.service\n(L3, service)"];
// Hierarchy edges
workspace -> architect [label="contains"];
workspace -> pirotehnika [label="contains"];
pirotehnika -> retail [label="contains"];
retail -> site [label="contains"];
pirotehnika -> pim [label="contains"];
// Dependency edges
site -> pim [label="depends_on", style=dashed, color=blue];
}
Версия: 1.0.0
Создано: 2025-12-22
Автор: Claude Sonnet 4.5
Статус: CANONICAL