-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathqe_knowledge_graph.py
More file actions
193 lines (160 loc) · 6.26 KB
/
qe_knowledge_graph.py
File metadata and controls
193 lines (160 loc) · 6.26 KB
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
from fastapi import FastAPI, Request, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import json
import logging
# Configure logging first so it's available everywhere
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = FastAPI()
# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
# Setup templates
templates = Jinja2Templates(directory="templates")
# Add configuration class
class Settings:
JSON_FILE_PATH = 'eng/assets/xqe_univ_kg_load_v1.json'
DEBUG = True
settings = Settings()
# Improve JSON loading with better error handling
def load_json_data():
try:
with open(settings.JSON_FILE_PATH, 'r', encoding='utf-8') as f:
data = json.load(f)
if not isinstance(data, list):
logger.error("JSON data must be a list")
return []
return data
except FileNotFoundError:
logger.error(f"JSON file not found at {settings.JSON_FILE_PATH}")
return []
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON format: {str(e)}")
return []
except Exception as e:
logger.error(f"Unexpected error loading JSON: {str(e)}")
return []
# Load json data
json_data = load_json_data()
@app.get("/")
async def root(request: Request):
return templates.TemplateResponse(
"index.html",
{"request": request}
)
@app.get("/api/data")
async def get_data():
if not json_data:
raise HTTPException(
status_code=503,
detail="Service temporarily unavailable - Data failed to load"
)
try:
nodes = []
edges = []
seen_nodes = set()
# First pass: collect all valid nodes
for item in json_data:
if not isinstance(item, dict):
continue
node_id = item.get("Parameter_Name") or item.get("Card_Name")
if not node_id or not isinstance(node_id, str):
continue
if node_id in seen_nodes:
logger.warning(f"Duplicate node found: {node_id}")
continue
seen_nodes.add(node_id)
nodes.append({
"id": node_id,
"label": node_id,
"group": item.get("Namelist", "") or item.get("Card_Name", ""),
"title": (f"{item.get('Description', 'No description')}\n"
f"Type: {item.get('Value_Type', 'Not specified')}")
})
# Second pass: add edges only between existing nodes
for item in json_data:
if not isinstance(item, dict):
continue
source_id = item.get("Parameter_Name") or item.get("Card_Name")
if not source_id or source_id not in seen_nodes:
continue
relationships = item.get("Relationships_Conditions_to_Other_Parameters_Cards", {})
if not isinstance(relationships, dict):
continue
for target_id, description in relationships.items():
# Only create edge if both source and target nodes exist
if target_id and isinstance(target_id, str) and target_id in seen_nodes:
edges.append({
"id": f"edge-{source_id}-{target_id}", # Add unique edge ID
"source": source_id, # Use 'source' instead of 'from'
"target": target_id, # Use 'target' instead of 'to'
"title": str(description) if description else "Related"
})
logger.info(f"Successfully processed {len(nodes)} nodes and {len(edges)} edges")
return {"nodes": nodes, "edges": edges}
except Exception as e:
logger.error(f"Error processing data: {str(e)}")
raise HTTPException(status_code=500, detail="Error processing data")
@app.get("/api/node/{node_id}")
async def get_node_details(node_id: str):
if not node_id or not isinstance(node_id, str):
raise HTTPException(status_code=400, detail="Valid node ID string is required")
# First check for exact matches
for item in json_data:
if not isinstance(item, dict):
continue
if (item.get("Parameter_Name") == node_id or
item.get("Card_Name") == node_id):
return item
# Then check for case-insensitive matches
node_id_lower = node_id.lower()
for item in json_data:
if not isinstance(item, dict):
continue
if (str(item.get("Parameter_Name", "")).lower() == node_id_lower or
str(item.get("Card_Name", "")).lower() == node_id_lower):
return item
# Handle namelist groups
if node_id.startswith('&'):
return {
"Parameter_Name": node_id,
"Type": "Namelist",
"Description": "A namelist group that contains related parameters.",
"Group": "Namelist"
}
raise HTTPException(
status_code=404,
detail=f"Node '{node_id}' not found"
)
def main():
import uvicorn
import socket
# Log startup information
logger.info("Starting FastAPI application")
logger.info(f"Loaded {len(json_data)} items from JSON file")
# Try ports starting from 8001 until we find an available one
port = 8001
while port < 8020: # Limit port search to reasonable range
try:
# Test if port is available
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', port))
break
except OSError:
logger.warning(f"Port {port} is in use, trying next port")
port += 1
else:
logger.error("No available ports found in range 8001-8019")
raise SystemExit(1)
logger.info(f"Starting server on port {port}")
uvicorn.run(
"qe_knowledge_graph:app",
host="127.0.0.1",
port=port,
reload=True
)
if __name__ == "__main__":
main()