Spaces:
Running
Running
File size: 17,509 Bytes
8e24fe8 9535a23 8e24fe8 0a9d8d9 53bc32d f0f9975 5a85822 f0f9975 ad42f66 53bc32d ab26382 87838aa 74c6c6f 53bc32d 9535a23 ad42f66 ab26382 5da6a2a 53bc32d 9535a23 bb993b1 0219c0a 74c6c6f 87838aa 0219c0a 87838aa 0219c0a 87838aa 74c6c6f 87838aa 1145297 87838aa c3e59a0 87838aa bb993b1 9535a23 53bc32d 0219c0a ad42f66 87838aa 9535a23 ad42f66 87838aa 9535a23 87838aa 0219c0a d14128d c3e59a0 ad42f66 c3e59a0 53bc32d 1145297 53bc32d 7040320 53bc32d d14128d 53bc32d 1145297 8265570 a92ac02 57d40ff 9535a23 53bc32d 0219c0a c3e59a0 9535a23 5bf3b43 9535a23 5bf3b43 9535a23 1fc9f1e 9535a23 b515232 53bc32d 5bf3b43 1fc9f1e c3e59a0 8b5def0 b515232 9535a23 5bf3b43 1fc9f1e 53bc32d 05a1b24 ad42f66 8b5def0 9535a23 b515232 8b5def0 1fc9f1e b515232 9535a23 b515232 9535a23 b515232 9535a23 57d40ff 0219c0a 1145297 ad42f66 1145297 ad42f66 0219c0a 9205b89 bb993b1 74c6c6f bb993b1 53bc32d ad42f66 c9719e2 74c6c6f 1145297 ad42f66 0219c0a ad42f66 0219c0a ad42f66 0219c0a 74c6c6f 0219c0a ad42f66 0219c0a ad42f66 0219c0a 0a9d8d9 87838aa ab26382 ad42f66 87838aa ad42f66 87838aa b515232 eb192f5 87838aa ad42f66 87838aa eb192f5 ad42f66 ab26382 be98dcc ab26382 97ac75d ab26382 97ac75d 86cffdc ea70db6 be98dcc 86cffdc ea70db6 9535a23 86cffdc be98dcc 86cffdc be98dcc 97ac75d 87838aa 947f419 87838aa 947f419 53bc32d ad42f66 b7a66df ad42f66 87838aa 9535a23 87838aa ad42f66 87838aa b7a66df 0219c0a 53bc32d ad42f66 f600486 87838aa ad42f66 bb993b1 ad42f66 74c6c6f ad42f66 b515232 74c6c6f b515232 ad42f66 87838aa ad42f66 87838aa 82250f4 87838aa 82250f4 ad42f66 87838aa ad42f66 f600486 87838aa 53bc32d 74c6c6f ad42f66 0219c0a 1145297 ad42f66 1145297 74c6c6f 53bc32d ad42f66 634a5fa 74c6c6f ad42f66 74c6c6f | 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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | """
MODULE ONTOLOGY GRAPH - V FINAL (FIXED INGESTION & DISPLAY)
===========================================================
Mise à jour : Ingestion stricte par feuille & Affichage Chain of Thought.
"""
import streamlit as st
import pandas as pd
import networkx as nx
from pyvis.network import Network
import tempfile
import streamlit.components.v1 as components
import json
import sys
import os
import time
# --- GESTION DES IMPORTS ---
try:
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
from src.core.ontology_manager import OntologyManager
from src.core.validation_engine import ValidationEngine
from src.core.schema_extractor import SchemaExtractor
from src.rag.ontology_blocks_builder import OntologyBlocksBuilder
from src.Algorithms.vector_search import SemanticIndex
from src.Algorithms.rdf_manager import RDFStore
from src.modules.jasmine_agent import JasmineAgent
from src.core.inference_engine import InferenceEngine # R-BOX ACTIVÉE
except ImportError:
pass
# ==============================================================================
# 1. DISPATCHER D'OUTILS (VOTRE VERSION CONSERVÉE)
# ==============================================================================
def execute_agent_tool(tool_name, args, rdf_store, vector_engine):
"""Exécute l'outil et retourne le résultat + un log pour le debugger"""
result_data = ""
visual_update = None
debug_info = f"🔧 Tool Call: {tool_name}({args})"
try:
if tool_name == "search_semantic":
query = args.get("query", "")
hits = vector_engine.search(query, top_k=5)
if not hits:
result_data = f"❌ Aucun résultat sémantique pour '{query}'."
else:
result_data = f"### 🧠 Résultats Vectoriels (OG-RAG)\n"
for h in hits:
uri_clean = str(h['uri']).split('#')[-1]
meta_info = h.get('meta', {'type': 'Unknown'})
result_data += f"- **{uri_clean}** ({meta_info.get('type', 'Entity')})\n"
preview = str(h['text'])[:250].replace("\n", " ")
result_data += f" > *{preview}...*\n"
elif tool_name == "execute_sparql":
query = args.get("query", "").replace("```sparql", "").replace("```", "").strip()
debug_info += f"\n\n🔍 SPARQL Query:\n{query}"
result_data = rdf_store.execute_sparql(query)
result_data = f"### ⚡ Résultat SPARQL (Calculé sur T-Box Typée)\n\n{result_data}"
elif tool_name == "highlight_node":
raw_id = args.get("node_id", "")
target_id = raw_id.replace("http://vortex.ai/ontology#", "").replace("vortex:", "")
visual_update = {"action": "highlight_node", "target_id": target_id}
result_data = f"✅ Zoom visuel activé sur : {target_id}"
except Exception as e:
result_data = f"❌ Erreur Exécution Outil : {str(e)}"
return result_data, visual_update, debug_info
# ==============================================================================
# 2. UTILS & STYLE (VOTRE THÈME GOTHAM CONSERVÉ)
# ==============================================================================
def apply_gotham_theme():
st.markdown("""
<style>
.stApp { background-color: #0d1117 !important; color: #c9d1d9; }
.ontology-title { font-family: 'Space Grotesk', sans-serif; font-size: 1.5rem; font-weight: 700; color: #ffffff; }
/* Chat Styling - Palantir Style */
.chat-container { border-right: 1px solid #30363d; padding-right: 15px; height: 75vh; overflow-y: auto; }
.user-msg {
background: rgba(30, 136, 229, 0.1);
border-left: 3px solid #1E88E5;
padding: 12px; margin: 10px 0;
border-radius: 0 4px 4px 0;
font-family: 'Inter', sans-serif;
}
.bot-msg {
background: rgba(48, 54, 61, 0.3);
border-left: 3px solid #00E676;
padding: 12px; margin: 10px 0;
border-radius: 0 4px 4px 0;
font-family: 'Inter', sans-serif;
}
/* Debugger Log Style */
.debug-block {
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
color: #8b949e;
background: #0d1117;
border: 1px solid #30363d;
padding: 10px;
margin-top: 5px;
border-radius: 4px;
}
</style>
""", unsafe_allow_html=True)
def get_node_style(entity_type):
colors = {"Client": "#1E88E5", "Garant": "#8E44AD", "Pret": "#F39C12", "Transaction": "#00BCD4"}
return colors.get(str(entity_type).strip(), "#6C757D"), "dot"
def safe_open_sheet(client, name):
for _ in range(5):
try: return client.open(name)
except: time.sleep(1)
return None
def safe_get_records(sh, w_name):
for _ in range(3):
try: return pd.DataFrame(sh.worksheet(w_name).get_all_records())
except: time.sleep(1)
return pd.DataFrame()
# --- MODIFICATION CRITIQUE : INGESTION STRICTE PAR FEUILLE ---
def extract_triplets(ontology_df, client, sheet_name):
triplets = []
sh = safe_open_sheet(client, sheet_name)
if not sh: return pd.DataFrame(), {}
# Pré-chargement des feuilles pour performance
cache = {s: safe_get_records(sh, s) for s in ontology_df['Sheet'].unique()}
sheets_columns = {s: list(df.columns) for s, df in cache.items()}
# Itération sur chaque règle de l'ontologie
for _, rule in ontology_df.iterrows():
target_sheet = rule['Sheet']
df = cache.get(target_sheet)
# Sécurité : Si la feuille est vide ou colonne manquante, on passe
if df is None or df.empty or rule['SubjectCol'] not in df.columns:
continue
for _, row in df.iterrows():
s_val = str(row.get(rule['SubjectCol'], '')).strip().upper()
if not s_val: continue
s_id = f"{rule['SubjectClass']}:{s_val}"
# --- ISOLATION : On ne prend que les colonnes de CETTE ligne dans CETTE feuille ---
# (Cela empêche le mélange des données entre Clients et Remboursements)
clean_props = {k: v for k, v in row.to_dict().items() if v and str(v).strip()}
if rule['ObjectType'] == 'relation':
ocol = str(rule['ObjectColOrConcept'])
oval = str(row.get(ocol, '')).strip()
if oval:
o_cls = ocol.replace("ID_", "") if "ID_" in ocol else ocol
triplets.append({
"subject": s_id,
"predicate": rule['Predicate'],
"object": f"{o_cls}:{oval.upper()}",
"object_type": "entity",
"subject_props": clean_props
})
elif rule['ObjectType'] == 'data_property':
target_col = rule['ObjectColOrConcept']
# On vérifie que la propriété est bien dans la feuille courante
if target_col in df.columns:
oval = str(row.get(target_col, '')).strip()
if oval:
triplets.append({
"subject": s_id,
"predicate": rule['Predicate'],
"object": oval,
"object_type": "literal",
"subject_props": clean_props
})
return pd.DataFrame(triplets), sheets_columns
def apply_visual_actions(G, action_data):
if not action_data: return G
action = action_data.get("action")
target = action_data.get("target_id")
if action == "highlight_node":
for n in G.nodes:
if n != target:
G.nodes[n]['color'] = 'rgba(50,50,50,0.1)'
else: G.nodes[n]['size'] = 50
return G
# ==============================================================================
# 3. MAIN ORCHESTRATOR
# ==============================================================================
def show_ontology_graph(client, sheet_name):
apply_gotham_theme()
# --- STATE INIT ---
if "rdf_store" not in st.session_state: st.session_state["rdf_store"] = RDFStore()
if "vector_engine" not in st.session_state: st.session_state["vector_engine"] = SemanticIndex()
if "ontology_manager" not in st.session_state: st.session_state["ontology_manager"] = OntologyManager()
if "validation_metrics" not in st.session_state: st.session_state["validation_metrics"] = None
if "chat_history" not in st.session_state: st.session_state["chat_history"] = []
if "current_visual_action" not in st.session_state: st.session_state["current_visual_action"] = None
if "jasmine_active" not in st.session_state: st.session_state["jasmine_active"] = False
# --- HEADER ---
c1, c2 = st.columns([5, 1])
with c1: st.markdown('<h1 class="ontology-title">VORTEX FLUX - KG OPERATIONS</h1>', unsafe_allow_html=True)
with c2: st.session_state["jasmine_active"] = st.toggle("TERMINAL", value=st.session_state["jasmine_active"])
# --- LOAD DATA ---
ontology_df = pd.DataFrame(safe_get_records(safe_open_sheet(client, sheet_name), "Ontology"))
if ontology_df.empty: st.caption("Chargement..."); return
triplets_df, _ = extract_triplets(ontology_df, client, sheet_name)
if triplets_df.empty: return
# --- NETWORKX BUILD ---
G = nx.DiGraph()
for _, r in triplets_df.iterrows():
s, p, o = r['subject'], r['predicate'], r['object']
if s not in G.nodes:
c, sh = get_node_style(s.split(':')[0])
G.add_node(s, label=s.split(':')[1] if ':' in s else s, group=s.split(':')[0], color=c, shape=sh, **r.get('subject_props', {}))
if r['object_type'] == 'entity':
if o not in G.nodes:
c, sh = get_node_style(o.split(':')[0])
G.add_node(o, label=o.split(':')[1] if ':' in o else o, group=o.split(':')[0], color=c, shape=sh)
G.add_edge(s, o, label=p)
else:
pid = f"Prop:{hash(o)%10000}"
G.add_node(pid, label=str(o)[:20], group="Prop", color="#6C757D", shape="text", size=10)
G.add_edge(s, pid, label=p, dashes=True)
# --- PIPELINE PHASE 2 (OG-RAG + PIPELINE + INFERENCE) ---
st.session_state["ontology_manager"].load_from_dataframe(ontology_df)
if "pipeline_done" not in st.session_state:
with st.status("🚀 Démarrage Système VORTEX (Palantir Architecture)...", expanded=True) as status:
# 1. Ingestion & Pipeline Cleaning
st.write("1️⃣ Ingestion & Pipeline de Nettoyage (T-Box Stricte)...")
st.session_state["rdf_store"] = RDFStore()
st.session_state["rdf_store"].ingest_networkx_graph(G, st.session_state["ontology_manager"])
# 2. R-Box Inference
st.write("2️⃣ R-BOX : Raisonnement & Déductions (Golden Record)...")
reasoner = InferenceEngine(st.session_state["rdf_store"])
reasoner.run_inference()
# 3. Validation
st.write("3️⃣ Audit Qualité & Extraction Schéma...")
validator = ValidationEngine(st.session_state["rdf_store"], st.session_state["ontology_manager"])
validator.run_validation()
st.session_state["validation_metrics"] = validator.generate_metrics_dashboard()
# 4. Vectorisation (Hyper-blocs enrichis)
st.write("4️⃣ Construction Hyper-blocs & Indexation...")
builder = OntologyBlocksBuilder(st.session_state["rdf_store"])
hyper_blocks = builder.build_all_blocks()
st.session_state["vector_engine"].build_from_ontology_blocks(hyper_blocks)
status.update(label="✅ Système Opérationnel", state="complete", expanded=False)
st.session_state["pipeline_done"] = True
# --- DASHBOARD METRICS (DIAGNOSTIC VISUEL) ---
metrics = st.session_state.get("validation_metrics")
pipeline_stats = st.session_state["rdf_store"].pipeline.get_health_report()
if metrics:
st.markdown("### 🏥 VORTEX HEALTH MONITORING")
c1, c2, c3, c4 = st.columns(4)
c1.metric("Triplets RDF", metrics["triples"])
c2.metric("Score Qualité", f"{metrics['coverage_score']}%")
c3.metric("Données Valides", pipeline_stats.get("valid_entries", 0))
c4.metric("Rejets Contrats", pipeline_stats.get("rejected_contracts", 0), delta_color="inverse")
# --- TABLEAU DE CORRECTION ---
if pipeline_stats.get("rejected_contracts", 0) > 0:
st.error(f"🚨 {pipeline_stats.get('rejected_contracts')} Erreurs détectées. Ouvrez le guide ci-dessous.")
with st.expander("🔍 GUIDE DE CORRECTION EXCEL (Où corriger ?)", expanded=True):
logs = st.session_state["rdf_store"].pipeline.logs
if logs:
df_err = pd.DataFrame(logs)
st.dataframe(df_err, use_container_width=True)
# --- LAYOUT PRINCIPAL ---
if st.session_state["jasmine_active"]:
col_chat, col_graph = st.columns([1, 2], gap="large")
else:
col_graph = st.container()
col_chat = None
# --- SECTION CHAT AVEC DEBUGGER LOG ---
if st.session_state["jasmine_active"] and col_chat:
with col_chat:
st.markdown("### 🤖 AIP COMMAND")
with st.container(height=600):
for msg in st.session_state["chat_history"]:
if msg["role"] == "user":
st.markdown(f'<div class="user-msg">{msg["content"]}</div>', unsafe_allow_html=True)
elif msg["role"] == "debugger":
# C'est ici que le Debugger Log s'affiche (Chain of Thought)
with st.expander("🕵️ AIP Debugger (Chain of Thought)", expanded=False):
st.markdown(f'<div class="debug-block">{msg["content"]}</div>', unsafe_allow_html=True)
else:
st.markdown(f'<div class="bot-msg">{msg["content"]}</div>', unsafe_allow_html=True)
if prompt := st.chat_input("Ordre..."):
st.session_state["chat_history"].append({"role": "user", "content": prompt})
st.rerun()
# --- LOGIQUE AGENT ---
if st.session_state["chat_history"] and st.session_state["chat_history"][-1]["role"] == "user":
last_msg = st.session_state["chat_history"][-1]["content"]
agent = JasmineAgent(st.session_state["rdf_store"], ontology_df.to_dict('records'))
with st.spinner("🧠 Analyse OAG..."):
resp_text, tool_action, thought_trace = agent.ask(last_msg, st.session_state["chat_history"][:-1])
# 1. Ajouter le Debugger Log (Chain of Thought)
if thought_trace:
st.session_state["chat_history"].append({"role": "debugger", "content": thought_trace})
# 2. Exécuter l'Outil et Ajouter le Log Technique
if tool_action:
with st.spinner("⚙️ Exécution Pipeline..."):
tool_args = tool_action.get('args', {})
res, vis, debug_info = execute_agent_tool(
tool_action.get('tool', 'none'),
tool_args,
st.session_state["rdf_store"],
st.session_state["vector_engine"]
)
st.session_state["chat_history"].append({"role": "debugger", "content": debug_info})
if vis: st.session_state["current_visual_action"] = vis
st.session_state["chat_history"].append({"role": "assistant", "content": res})
st.rerun()
elif resp_text:
st.session_state["chat_history"].append({"role": "assistant", "content": resp_text})
st.rerun()
# --- SECTION VISUALISATION ---
with col_graph:
if st.session_state["current_visual_action"]:
G = apply_visual_actions(G, st.session_state["current_visual_action"])
nt = Network(height="700px", width="100%", bgcolor="#0d1117", font_color="#c9d1d9")
nt.from_nx(G)
nt.set_options(json.dumps({
"physics": {"forceAtlas2Based": {"gravitationalConstant": -50, "springLength": 100}},
"nodes": {"font": {"size": 14, "color": "#ffffff"}, "borderWidth": 2},
"interaction": {"hover": True, "navigationButtons": True}
}))
path = tempfile.gettempdir() + "/ontology_viz.html"
nt.save_graph(path)
with open(path, 'r', encoding='utf-8') as f:
components.html(f.read(), height=700)
if st.session_state["current_visual_action"]:
if st.button("🔄 Reset Vue"):
st.session_state["current_visual_action"] = None
st.rerun() |