Spaces:
Running
Running
File size: 6,892 Bytes
96aaef2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | import networkx as nx
import matplotlib.pyplot as plt
import plotly.graph_objects as go
from collections import defaultdict
import re
from utils.config import COLORS, MAX_NODES
def build_knowledge_graph(result):
"""Строит граф знаний на основе извлеченных сущностей (только реальные связи)"""
G = nx.Graph()
# Добавляем узлы
for material in result.get('materials_lemmas', []):
G.add_node(material, type='материал', color=COLORS['материал'])
for structure in result.get('structures_lemmas', []):
G.add_node(structure, type='конструкция', color=COLORS['конструкция'])
for param in result.get('parameters_lemmas', []):
G.add_node(param, type='параметр', color=COLORS['параметр'])
for standard in result.get('standards', []):
G.add_node(standard, type='норматив', color=COLORS['норматив'])
# Приводим к спискам
materials = list(result.get('materials_lemmas', []))
structures = list(result.get('structures_lemmas', []))
parameters = list(result.get('parameters_lemmas', []))
all_lemmas = materials + structures + parameters
# Реальные связи из текста (по предложениям)
co_occurrence = defaultdict(int)
text = result.get('full_text', '').lower()
sentences = re.split(r'[.!?]\s+', text)
for sent in sentences:
sent_lower = sent.lower()
lemmas_in_sent = [lemma for lemma in all_lemmas if lemma in sent_lower]
for i, t1 in enumerate(lemmas_in_sent):
for t2 in lemmas_in_sent[i+1:]:
co_occurrence[(t1, t2)] += 1
# Добавляем только реальные связи (без искусственных)
for (t1, t2), weight in co_occurrence.items():
if weight >= 1: # если хоть раз встретились в одном предложении
G.add_edge(t1, t2, weight=weight)
# Удаляем изолированные узлы (без связей)
isolated = list(nx.isolates(G))
G.remove_nodes_from(isolated)
print(f"📊 ИТОГО: узлов={G.number_of_nodes()}, связей={G.number_of_edges()}")
if isolated:
print(f"🗑️ Удалено изолированных узлов: {len(isolated)}")
return G
def get_graph_statistics(G):
"""Статистика графа"""
if len(G.nodes) == 0:
return {}
stats = {
'nodes': G.number_of_nodes(),
'edges': G.number_of_edges(),
'density': nx.density(G),
'components': nx.number_connected_components(G),
}
degrees = dict(G.degree())
top_nodes = sorted(degrees.items(), key=lambda x: x[1], reverse=True)[:10]
stats['top_nodes'] = [(node, degree, G.nodes[node].get('type', 'unknown'))
for node, degree in top_nodes]
type_counts = defaultdict(int)
for node, data in G.nodes(data=True):
type_counts[data.get('type', 'unknown')] += 1
stats['type_counts'] = dict(type_counts)
return stats
def visualize_matplotlib(G, max_nodes=MAX_NODES):
"""Визуализация графа с matplotlib (статический)"""
if len(G.nodes) == 0:
return None
# Ограничиваем количество узлов для читаемости
if len(G.nodes) > max_nodes:
degrees = dict(G.degree())
top = sorted(degrees.items(), key=lambda x: x[1], reverse=True)[:max_nodes]
G = G.subgraph([node for node, _ in top])
fig, ax = plt.subplots(figsize=(14, 12))
pos = nx.spring_layout(G, k=2, iterations=80, seed=42)
# Цвета и размеры
node_colors = [G.nodes[n].get('color', COLORS['unknown']) for n in G.nodes()]
node_sizes = [G.degree(n) * 100 + 500 for n in G.nodes()]
# Рисуем
nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=node_sizes, alpha=0.8, ax=ax)
nx.draw_networkx_edges(G, pos, alpha=0.4, edge_color='gray', ax=ax)
# Подписи
labels = {n: n[:20] + '..' if len(n) > 20 else n for n in G.nodes()}
nx.draw_networkx_labels(G, pos, labels, font_size=7, ax=ax)
# Легенда
from matplotlib.patches import Patch
legend = [
Patch(facecolor=COLORS['материал'], label='Материалы'),
Patch(facecolor=COLORS['конструкция'], label='Конструкции'),
Patch(facecolor=COLORS['параметр'], label='Параметры'),
Patch(facecolor=COLORS['норматив'], label='Нормативы')
]
ax.legend(handles=legend, loc='upper left', fontsize=10)
plt.title('Граф знаний строительной документации (реальные связи)', fontsize=14, fontweight='bold')
plt.axis('off')
plt.tight_layout()
return fig
def visualize_plotly(G, max_nodes=MAX_NODES):
"""Интерактивная визуализация графа с plotly"""
if len(G.nodes) == 0:
return None
if len(G.nodes) > max_nodes:
degrees = dict(G.degree())
top = sorted(degrees.items(), key=lambda x: x[1], reverse=True)[:max_nodes]
G = G.subgraph([node for node, _ in top])
pos = nx.spring_layout(G, k=2, iterations=80, seed=42)
# Рёбра
edge_x, edge_y = [], []
for u, v in G.edges():
x0, y0 = pos[u]
x1, y1 = pos[v]
edge_x.extend([x0, x1, None])
edge_y.extend([y0, y1, None])
# Узлы
node_x, node_y, node_text, node_color = [], [], [], []
for node in G.nodes():
x, y = pos[node]
node_x.append(x)
node_y.append(y)
node_text.append(f"{node}<br>Связей: {G.degree(node)}<br>Тип: {G.nodes[node].get('type', 'unknown')}")
node_color.append(G.nodes[node].get('color', COLORS['unknown']))
fig = go.Figure()
fig.add_trace(go.Scatter(x=edge_x, y=edge_y, mode='lines', line=dict(width=0.8, color='#888'), hoverinfo='none'))
fig.add_trace(go.Scatter(x=node_x, y=node_y, mode='markers+text', text=list(G.nodes()),
textposition="top center", hovertext=node_text,
marker=dict(size=[G.degree(node)*2+20 for node in G.nodes()],
color=node_color, line=dict(width=1, color='black'))))
fig.update_layout(title='Граф знаний строительной документации', showlegend=False,
hovermode='closest', margin=dict(b=20, l=5, r=5, t=40),
xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
return fig |