from fastapi import FastAPI from fastapi.responses import JSONResponse, HTMLResponse from fastapi.middleware.cors import CORSMiddleware import pandas as pd import networkx as nx import plotly.graph_objects as go import re import os # ---------- CONFIG ---------- DATA_FILE = "База философских концептов - База философских концептов.csv" # ---------- APP SETUP ---------- app = FastAPI( title="Philosophy Knowledge Graph", description="Онтологическая база философских концептов — Knowledge GraphRAG", version="3.3.0", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ---------- LOAD DATA ---------- if not os.path.exists(DATA_FILE): raise FileNotFoundError(f"Файл {DATA_FILE} не найден в корне проекта.") df = pd.read_csv(DATA_FILE) df = df.fillna("") # ---------- BUILD GRAPH ---------- G = nx.DiGraph() for _, row in df.iterrows(): G.add_node( row["concept"], definition=row["definition"], needs=row["needs_level"], tech=row["tech_problem"] ) # регулярка для формата: «A» → «B» for _, row in df.iterrows(): relations = re.findall(r'«([^»]+)»\s*[↔→]\s*«([^»]+)»', str(row["relations"])) for a, b in relations: if a in G.nodes and b in G.nodes: G.add_edge(a, b, label=row["tech_problem"]) # Цвета Маслоу color_map = { "Физиологические": "#f4a261", "Безопасность": "#2a9d8f", "Принадлежность": "#e9c46a", "Самоуважение": "#264653", "Самореализация": "#e76f51" } # ---------- PRECOMPUTE LAYOUT ---------- # БЫСТРЫЙ, ЛЁГКИЙ, НЕ ТРЕБУЕТ SCIPY POS = nx.spring_layout(G, k=0.7, iterations=30, seed=42) # ---------- API ---------- @app.get("/") def root(): return {"message": "Philosophy Knowledge Graph API — работает!"} @app.get("/concepts") def get_concepts(): return JSONResponse([ {"concept": n, **G.nodes[n]} for n in G.nodes() ]) @app.get("/relations") def get_relations(): return JSONResponse([ {"source": u, "target": v, "label": d.get("label", "")} for u, v, d in G.edges(data=True) ]) @app.get("/jsonld") def get_jsonld(): jsonld = { "@context": { "concept": "http://example.org/concept", "definition": "http://example.org/definition", "relation": "http://example.org/relation" }, "@graph": [] } for node, data in G.nodes(data=True): jsonld["@graph"].append({ "@id": f"http://example.org/{node.replace(' ', '_')}", "concept": node, "definition": data.get("definition", ""), "tech_problem": data.get("tech", ""), "needs_level": data.get("needs", "") }) return JSONResponse(jsonld) # ---------- INTERACTIVE GRAPH ---------- @app.get("/graph", response_class=HTMLResponse) def get_graph(): pos = POS # --- Рёбра --- 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]) edge_trace = go.Scatter( x=edge_x, y=edge_y, mode="lines", line=dict(width=0.5, color="#888"), hoverinfo="none", ) # --- Узлы --- node_x, node_y, node_color, node_text = [], [], [], [] for node, data in G.nodes(data=True): x, y = pos[node] node_x.append(x) node_y.append(y) node_color.append(color_map.get(data["needs"], "#8d99ae")) node_text.append( f"{node}
{data['definition']}
{data['tech']}" ) node_trace = go.Scatter( x=node_x, y=node_y, mode="markers", hoverinfo="text", text=node_text, marker=dict( size=12, color=node_color, line=dict(width=1) ), ) fig = go.Figure( data=[edge_trace, node_trace], layout=go.Layout( title="Онтологическая база философских концептов", titlefont=dict(size=20), showlegend=False, hovermode="closest", margin=dict(b=0, l=0, r=0, t=40), xaxis=dict(showgrid=False, zeroline=False, visible=False), yaxis=dict(showgrid=False, zeroline=False, visible=False), ) ) return HTMLResponse(fig.to_html(full_html=False)) # ---------- RUN ---------- if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)