naas-abi 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- naas_abi/__init__.py +35 -0
- naas_abi/agents/AbiAgent.py +442 -0
- naas_abi/agents/AbiAgent_test.py +157 -0
- naas_abi/agents/EntitytoSPARQLAgent.py +952 -0
- naas_abi/agents/EntitytoSPARQLAgent_test.py +66 -0
- naas_abi/agents/KnowledgeGraphBuilderAgent.py +321 -0
- naas_abi/agents/KnowledgeGraphBuilderAgent_test.py +86 -0
- naas_abi/agents/OntologyEngineerAgent.py +115 -0
- naas_abi/agents/OntologyEngineerAgent_test.py +42 -0
- naas_abi/apps/oxigraph_admin/main.py +392 -0
- naas_abi/apps/oxigraph_admin/terminal_style.py +151 -0
- naas_abi/apps/sparql_terminal/main.py +68 -0
- naas_abi/apps/sparql_terminal/terminal_style.py +236 -0
- naas_abi/apps/terminal_agent/main.py +553 -0
- naas_abi/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi/cli.py +714 -0
- naas_abi/mappings.py +83 -0
- naas_abi/models/airgap_gemma.py +220 -0
- naas_abi/models/airgap_qwen.py +24 -0
- naas_abi/models/default.py +23 -0
- naas_abi/models/gpt_4_1.py +25 -0
- naas_abi/pipelines/AIAgentOntologyGenerationPipeline.py +635 -0
- naas_abi/pipelines/AIAgentOntologyGenerationPipeline_test.py +133 -0
- naas_abi/pipelines/AddIndividualPipeline.py +215 -0
- naas_abi/pipelines/AddIndividualPipeline_test.py +66 -0
- naas_abi/pipelines/InsertDataSPARQLPipeline.py +197 -0
- naas_abi/pipelines/InsertDataSPARQLPipeline_test.py +96 -0
- naas_abi/pipelines/MergeIndividualsPipeline.py +245 -0
- naas_abi/pipelines/MergeIndividualsPipeline_test.py +98 -0
- naas_abi/pipelines/RemoveIndividualPipeline.py +166 -0
- naas_abi/pipelines/RemoveIndividualPipeline_test.py +58 -0
- naas_abi/pipelines/UpdateCommercialOrganizationPipeline.py +198 -0
- naas_abi/pipelines/UpdateDataPropertyPipeline.py +175 -0
- naas_abi/pipelines/UpdateLegalNamePipeline.py +107 -0
- naas_abi/pipelines/UpdateLinkedInPagePipeline.py +179 -0
- naas_abi/pipelines/UpdatePersonPipeline.py +184 -0
- naas_abi/pipelines/UpdateSkillPipeline.py +118 -0
- naas_abi/pipelines/UpdateTickerPipeline.py +104 -0
- naas_abi/pipelines/UpdateWebsitePipeline.py +106 -0
- naas_abi/triggers.py +131 -0
- naas_abi/workflows/AgentRecommendationWorkflow.py +321 -0
- naas_abi/workflows/AgentRecommendationWorkflow_test.py +160 -0
- naas_abi/workflows/ArtificialAnalysisWorkflow.py +337 -0
- naas_abi/workflows/ArtificialAnalysisWorkflow_test.py +57 -0
- naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow.py +210 -0
- naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow_test.py +78 -0
- naas_abi/workflows/CreateClassOntologyYamlWorkflow.py +208 -0
- naas_abi/workflows/CreateClassOntologyYamlWorkflow_test.py +65 -0
- naas_abi/workflows/CreateIndividualOntologyYamlWorkflow.py +183 -0
- naas_abi/workflows/CreateIndividualOntologyYamlWorkflow_test.py +86 -0
- naas_abi/workflows/ExportGraphInstancesToExcelWorkflow.py +450 -0
- naas_abi/workflows/ExportGraphInstancesToExcelWorkflow_test.py +33 -0
- naas_abi/workflows/GetObjectPropertiesFromClassWorkflow.py +385 -0
- naas_abi/workflows/GetObjectPropertiesFromClassWorkflow_test.py +57 -0
- naas_abi/workflows/GetSubjectGraphWorkflow.py +84 -0
- naas_abi/workflows/GetSubjectGraphWorkflow_test.py +71 -0
- naas_abi/workflows/SearchIndividualWorkflow.py +190 -0
- naas_abi/workflows/SearchIndividualWorkflow_test.py +98 -0
- naas_abi-1.0.0.dist-info/METADATA +9 -0
- naas_abi-1.0.0.dist-info/RECORD +62 -0
- naas_abi-1.0.0.dist-info/WHEEL +5 -0
- naas_abi-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from typing import Annotated, Any, Optional
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from fastapi import APIRouter
|
|
11
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
12
|
+
from naas_abi import config, logger
|
|
13
|
+
from naas_abi_core.services.triple_store.TripleStorePorts import ITripleStoreService
|
|
14
|
+
from naas_abi_core.workflow import Workflow, WorkflowConfiguration, WorkflowParameters
|
|
15
|
+
from naas_abi_marketplace.applications.naas.integrations.NaasIntegration import (
|
|
16
|
+
NaasIntegration,
|
|
17
|
+
NaasIntegrationConfiguration,
|
|
18
|
+
)
|
|
19
|
+
from pydantic import Field
|
|
20
|
+
from rdflib import RDF, Graph, URIRef, query
|
|
21
|
+
|
|
22
|
+
prefixes = {
|
|
23
|
+
"http://www.w3.org/2000/01/rdf-schema#": "rdfs",
|
|
24
|
+
"http://www.w3.org/2002/07/owl#": "owl",
|
|
25
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
|
|
26
|
+
"http://ontology.naas.ai/abi/": "abi",
|
|
27
|
+
"http://purl.obolibrary.org/obo/": "bfo",
|
|
28
|
+
"https://www.commoncoreontologies.org/": "cco",
|
|
29
|
+
"http://www.w3.org/2001/XMLSchema#": "xsd",
|
|
30
|
+
"http://www.w3.org/2004/02/skos/core#": "skos",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ExportGraphInstancesToExcelWorkflowConfiguration(WorkflowConfiguration):
|
|
36
|
+
"""Configuration for ExportGraphInstancesToExcel workflow."""
|
|
37
|
+
|
|
38
|
+
triple_store: ITripleStoreService
|
|
39
|
+
naas_integration_config: NaasIntegrationConfiguration
|
|
40
|
+
data_store_path: str = "datastore/triplestore/export/excel"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ExportGraphInstancesToExcelWorkflowParameters(WorkflowParameters):
|
|
44
|
+
"""Parameters for ExportGraphInstancesToExcel workflow."""
|
|
45
|
+
|
|
46
|
+
excel_file_name: Annotated[
|
|
47
|
+
str,
|
|
48
|
+
Field(
|
|
49
|
+
description="Name of the Excel file to export.",
|
|
50
|
+
),
|
|
51
|
+
] = "graph_instances_export.xlsx"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ExportGraphInstancesToExcelWorkflow(Workflow):
|
|
55
|
+
"""Workflow for exporting graph instances to Excel."""
|
|
56
|
+
|
|
57
|
+
__configuration: ExportGraphInstancesToExcelWorkflowConfiguration
|
|
58
|
+
|
|
59
|
+
def __init__(self, configuration: ExportGraphInstancesToExcelWorkflowConfiguration):
|
|
60
|
+
super().__init__(configuration)
|
|
61
|
+
self.__configuration = configuration
|
|
62
|
+
self.__naas_integration = NaasIntegration(configuration.naas_integration_config)
|
|
63
|
+
|
|
64
|
+
def create_sheet_name(self, label: str) -> str:
|
|
65
|
+
"""Create sheet name: rdfs:label"""
|
|
66
|
+
# Clean the label for Excel sheet name (remove invalid characters)
|
|
67
|
+
clean_label = re.sub(r'[\\/*?:"<>|]', "_", label)
|
|
68
|
+
sheet_name = f"{clean_label}"
|
|
69
|
+
|
|
70
|
+
# Excel sheet names are limited to 31 characters
|
|
71
|
+
if len(sheet_name) > 31:
|
|
72
|
+
sheet_name = sheet_name[:28] + "..."
|
|
73
|
+
|
|
74
|
+
return sheet_name
|
|
75
|
+
|
|
76
|
+
def autofit_columns(
|
|
77
|
+
self, writer: pd.ExcelWriter, sheet_name: str
|
|
78
|
+
) -> pd.ExcelWriter:
|
|
79
|
+
"""Autofit columns in an Excel worksheet"""
|
|
80
|
+
worksheet = writer.sheets[sheet_name]
|
|
81
|
+
label_width = 0
|
|
82
|
+
|
|
83
|
+
# First pass to get Label column width
|
|
84
|
+
for column in worksheet.columns:
|
|
85
|
+
column = [cell for cell in column]
|
|
86
|
+
if column[0].value == "Label":
|
|
87
|
+
for cell in column:
|
|
88
|
+
try:
|
|
89
|
+
if len(str(cell.value)) > label_width:
|
|
90
|
+
label_width = len(str(cell.value))
|
|
91
|
+
except Exception as _:
|
|
92
|
+
pass
|
|
93
|
+
label_width += 2
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
# Second pass to set all column widths
|
|
97
|
+
for column in worksheet.columns:
|
|
98
|
+
max_length = 0
|
|
99
|
+
column = [cell for cell in column]
|
|
100
|
+
|
|
101
|
+
# If this is Sheet Name column, use Label width
|
|
102
|
+
if column[0].value == "Sheet Name":
|
|
103
|
+
worksheet.column_dimensions[column[0].column_letter].width = label_width
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
# Otherwise autofit based on content
|
|
107
|
+
for cell in column:
|
|
108
|
+
try:
|
|
109
|
+
if len(str(cell.value)) > max_length:
|
|
110
|
+
max_length = len(str(cell.value))
|
|
111
|
+
except Exception as _:
|
|
112
|
+
pass
|
|
113
|
+
adjusted_width = max_length + 2
|
|
114
|
+
worksheet.column_dimensions[column[0].column_letter].width = adjusted_width
|
|
115
|
+
|
|
116
|
+
return writer
|
|
117
|
+
|
|
118
|
+
def get_all_triples_by_class(self, graph: Graph) -> query.Result:
|
|
119
|
+
query_sparql = """
|
|
120
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
121
|
+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
122
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
123
|
+
SELECT DISTINCT ?class (COALESCE(?class_label, STR(?class)) as ?class_label)
|
|
124
|
+
WHERE {
|
|
125
|
+
?individual rdf:type ?class .
|
|
126
|
+
?individual rdf:type owl:NamedIndividual .
|
|
127
|
+
OPTIONAL { ?class rdfs:label ?class_label }
|
|
128
|
+
FILTER(STRSTARTS(STR(?individual), "http://ontology.naas.ai/abi/"))
|
|
129
|
+
FILTER(?class != owl:NamedIndividual)
|
|
130
|
+
}
|
|
131
|
+
ORDER BY ?class_label
|
|
132
|
+
"""
|
|
133
|
+
results = graph.query(query_sparql)
|
|
134
|
+
logger.info(f"Found {len(results)} classes.")
|
|
135
|
+
return results
|
|
136
|
+
|
|
137
|
+
def get_all_object_property_labels(self, graph: Graph) -> dict[str, str]:
|
|
138
|
+
"""Get object property label from URI.
|
|
139
|
+
If the object property URI is not in the prefixes, return the last part of the URI."""
|
|
140
|
+
|
|
141
|
+
sparql_query = """
|
|
142
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
143
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
144
|
+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
145
|
+
SELECT ?uri ?label
|
|
146
|
+
WHERE {
|
|
147
|
+
?uri rdf:type owl:ObjectProperty ;
|
|
148
|
+
rdfs:label ?label .
|
|
149
|
+
}
|
|
150
|
+
"""
|
|
151
|
+
results = graph.query(sparql_query)
|
|
152
|
+
object_property_labels: dict = {}
|
|
153
|
+
for row in results:
|
|
154
|
+
assert isinstance(row, query.ResultRow)
|
|
155
|
+
if row[0] is not None and row[1] is not None:
|
|
156
|
+
object_property_labels[str(row[0])] = str(row[1])
|
|
157
|
+
logger.info(f"Found {len(object_property_labels)} object properties.")
|
|
158
|
+
return object_property_labels
|
|
159
|
+
|
|
160
|
+
def export_to_excel(
|
|
161
|
+
self, parameters: ExportGraphInstancesToExcelWorkflowParameters
|
|
162
|
+
) -> Optional[str]:
|
|
163
|
+
"""Export graph instances to Excel and return asset URL to download it."""
|
|
164
|
+
graph = self.__configuration.triple_store.get()
|
|
165
|
+
all_triples = self.get_all_triples_by_class(graph)
|
|
166
|
+
object_property_labels = self.get_all_object_property_labels(graph)
|
|
167
|
+
|
|
168
|
+
# First collect all class info
|
|
169
|
+
summary_data = []
|
|
170
|
+
for row in all_triples:
|
|
171
|
+
assert isinstance(row, query.ResultRow)
|
|
172
|
+
class_uri = str(row["class"])
|
|
173
|
+
class_label = (
|
|
174
|
+
str(row["class_label"]).split("/")[-1]
|
|
175
|
+
if row["class_label"]
|
|
176
|
+
else class_uri.split("/")[-1]
|
|
177
|
+
)
|
|
178
|
+
sheet_name = self.create_sheet_name(class_label)
|
|
179
|
+
summary_data.append(
|
|
180
|
+
{
|
|
181
|
+
"URI": class_uri,
|
|
182
|
+
"Type": "Class",
|
|
183
|
+
"Label": class_label,
|
|
184
|
+
"Sheet Name": sheet_name,
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
summary_df = pd.DataFrame(summary_data)
|
|
189
|
+
summary_df = summary_df.sort_values("Label").drop_duplicates()
|
|
190
|
+
summary_df = summary_df.reset_index(drop=True)
|
|
191
|
+
logger.info(f"Found {len(summary_df)} classes with individuals.")
|
|
192
|
+
|
|
193
|
+
# Then collect all data properties and object properties by class
|
|
194
|
+
summary_data = []
|
|
195
|
+
local_dir_path = "storage/" + self.__configuration.data_store_path
|
|
196
|
+
os.makedirs(local_dir_path, exist_ok=True)
|
|
197
|
+
excel_file_path = os.path.join(
|
|
198
|
+
local_dir_path,
|
|
199
|
+
f"{datetime.now().strftime('%Y%m%dT%H%M%S')}_{parameters.excel_file_name}",
|
|
200
|
+
)
|
|
201
|
+
with pd.ExcelWriter(excel_file_path, engine="openpyxl") as writer: # type: ignore
|
|
202
|
+
# Collect all data properties and object properties across all classes
|
|
203
|
+
all_data_properties: list[dict[str, Any]] = []
|
|
204
|
+
all_object_properties: dict[str, list[dict[str, Any]]] = {}
|
|
205
|
+
|
|
206
|
+
for _, row in summary_df.iterrows(): # type: ignore
|
|
207
|
+
class_uri = str(row.get("URI", "")) # type: ignore
|
|
208
|
+
class_label = str(row.get("Label", "")) # type: ignore
|
|
209
|
+
sheet_name = str(row.get("Sheet Name", "")) # type: ignore
|
|
210
|
+
|
|
211
|
+
# Get all individuals of this class
|
|
212
|
+
individuals: list[dict[str, Any]] = []
|
|
213
|
+
total_triples: int = 0 # Add counter for actual triples
|
|
214
|
+
|
|
215
|
+
for s in graph.subjects(RDF.type, URIRef(class_uri)):
|
|
216
|
+
individual_uri = str(s)
|
|
217
|
+
individual_data_properties: dict[str, Any] = {
|
|
218
|
+
"uri": individual_uri,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Count triples for this individual
|
|
222
|
+
individual_triples: int = 2
|
|
223
|
+
|
|
224
|
+
# Get triples for this individual
|
|
225
|
+
for s, p, o in graph.triples((URIRef(individual_uri), None, None)):
|
|
226
|
+
individual_triples += 1 # Count each triple
|
|
227
|
+
# Skip RDF/RDFS/OWL system properties and object properties
|
|
228
|
+
if str(p) == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
def add_to_dict(data: dict[str, Any], key: str, value: Any):
|
|
232
|
+
# If property already exists, convert to list
|
|
233
|
+
if key in data:
|
|
234
|
+
existing_value = data[key]
|
|
235
|
+
if isinstance(existing_value, list):
|
|
236
|
+
existing_value.append(str(o))
|
|
237
|
+
else:
|
|
238
|
+
# Convert existing value to list (handle None case)
|
|
239
|
+
data[key] = (
|
|
240
|
+
[existing_value, str(o)]
|
|
241
|
+
if existing_value is not None
|
|
242
|
+
else [str(o)]
|
|
243
|
+
) # type: ignore
|
|
244
|
+
else:
|
|
245
|
+
data[key] = str(o)
|
|
246
|
+
return data
|
|
247
|
+
|
|
248
|
+
if isinstance(o, URIRef):
|
|
249
|
+
prop_label = object_property_labels.get(str(p), None)
|
|
250
|
+
if prop_label is None:
|
|
251
|
+
prop_label = str(p).split("/")[-1]
|
|
252
|
+
|
|
253
|
+
# Add to all_object_properties for separate sheet
|
|
254
|
+
if prop_label not in all_object_properties:
|
|
255
|
+
all_object_properties[prop_label] = []
|
|
256
|
+
|
|
257
|
+
# Get rdfs:label for subject and object
|
|
258
|
+
subject_label = None
|
|
259
|
+
object_label = None
|
|
260
|
+
|
|
261
|
+
# Get subject label
|
|
262
|
+
for _, label_pred, label_obj in graph.triples(
|
|
263
|
+
(
|
|
264
|
+
URIRef(individual_uri),
|
|
265
|
+
URIRef(
|
|
266
|
+
"http://www.w3.org/2000/01/rdf-schema#label"
|
|
267
|
+
),
|
|
268
|
+
None,
|
|
269
|
+
)
|
|
270
|
+
):
|
|
271
|
+
subject_label = str(label_obj)
|
|
272
|
+
break
|
|
273
|
+
|
|
274
|
+
# Get object label
|
|
275
|
+
for _, label_pred, label_obj in graph.triples(
|
|
276
|
+
(
|
|
277
|
+
URIRef(str(o)),
|
|
278
|
+
URIRef(
|
|
279
|
+
"http://www.w3.org/2000/01/rdf-schema#label"
|
|
280
|
+
),
|
|
281
|
+
None,
|
|
282
|
+
)
|
|
283
|
+
):
|
|
284
|
+
object_label = str(label_obj)
|
|
285
|
+
break
|
|
286
|
+
|
|
287
|
+
all_object_properties[prop_label].append(
|
|
288
|
+
{
|
|
289
|
+
"domain_class": class_label,
|
|
290
|
+
"domain_uri": individual_uri,
|
|
291
|
+
"domain_label": subject_label
|
|
292
|
+
or individual_uri.split("/")[-1],
|
|
293
|
+
"range_uri": str(o),
|
|
294
|
+
"range_label": object_label
|
|
295
|
+
or str(o).split("/")[-1],
|
|
296
|
+
"object_property_uri": str(p),
|
|
297
|
+
"object_property_label": prop_label,
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
# Split on either # or /
|
|
302
|
+
for k, v in prefixes.items():
|
|
303
|
+
if k in str(p):
|
|
304
|
+
prop_suffix = str(p).split(k)[-1]
|
|
305
|
+
prop_label = f"{v}:{prop_suffix}"
|
|
306
|
+
break
|
|
307
|
+
else:
|
|
308
|
+
prop_label = str(p).split("/")[-1]
|
|
309
|
+
|
|
310
|
+
individual_data_properties = add_to_dict(
|
|
311
|
+
individual_data_properties, prop_label, str(o)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Add to all_data_properties for separate sheet
|
|
315
|
+
all_data_properties.append(
|
|
316
|
+
{
|
|
317
|
+
"uri": individual_uri,
|
|
318
|
+
"property": prop_label,
|
|
319
|
+
"value": str(o),
|
|
320
|
+
"class": class_label,
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
total_triples += individual_triples
|
|
325
|
+
|
|
326
|
+
# Join any list values
|
|
327
|
+
for key, value in individual_data_properties.items():
|
|
328
|
+
if isinstance(value, list):
|
|
329
|
+
individual_data_properties[key] = "; ".join(value)
|
|
330
|
+
|
|
331
|
+
individuals.append(individual_data_properties)
|
|
332
|
+
|
|
333
|
+
# Create dataframe and write to Excel
|
|
334
|
+
if individuals:
|
|
335
|
+
df = pd.DataFrame(individuals)
|
|
336
|
+
if "rdfs:label" in df.columns:
|
|
337
|
+
df = df.sort_values("rdfs:label", ascending=True)
|
|
338
|
+
else:
|
|
339
|
+
df = df.sort_values("uri", ascending=True)
|
|
340
|
+
df.to_excel(writer, sheet_name=sheet_name, index=False)
|
|
341
|
+
writer = self.autofit_columns(writer, sheet_name)
|
|
342
|
+
|
|
343
|
+
summary_data.append(
|
|
344
|
+
{
|
|
345
|
+
"URI": class_uri,
|
|
346
|
+
"Type": "Class",
|
|
347
|
+
"Label": class_label,
|
|
348
|
+
"Sheet Name": f'=HYPERLINK("#\'{sheet_name}\'!A1", "{class_label}")',
|
|
349
|
+
"Number of Individuals": str(len(individuals)),
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Create Classes summary dataframe
|
|
354
|
+
summary_df = pd.DataFrame(summary_data)
|
|
355
|
+
summary_df = summary_df.sort_values("Label").drop_duplicates()
|
|
356
|
+
summary_df = summary_df.reset_index(drop=True)
|
|
357
|
+
summary_df = summary_df.astype({"Number of Individuals": int})
|
|
358
|
+
writer.book.create_sheet(
|
|
359
|
+
"Classes", 0
|
|
360
|
+
) # Create Summary sheet in first position
|
|
361
|
+
summary_df.to_excel(writer, sheet_name="Classes", index=False)
|
|
362
|
+
sheet = writer.sheets["Classes"]
|
|
363
|
+
sheet.sheet_properties.tabColor = "000000"
|
|
364
|
+
writer = self.autofit_columns(writer, "Classes")
|
|
365
|
+
|
|
366
|
+
# Create Object Properties
|
|
367
|
+
object_properties_summary = []
|
|
368
|
+
for prop_label, prop_data in sorted(all_object_properties.items()):
|
|
369
|
+
if prop_data:
|
|
370
|
+
sheet_name = self.create_sheet_name(prop_label)
|
|
371
|
+
prop_uri = str(prop_data[0]["object_property_uri"])
|
|
372
|
+
prop_df = pd.DataFrame(prop_data)
|
|
373
|
+
prop_df = prop_df.sort_values(
|
|
374
|
+
["domain_class", "domain_label", "range_label"]
|
|
375
|
+
)
|
|
376
|
+
prop_df.to_excel(writer, sheet_name=sheet_name, index=False)
|
|
377
|
+
writer = self.autofit_columns(writer, sheet_name)
|
|
378
|
+
|
|
379
|
+
object_properties_summary.append(
|
|
380
|
+
{
|
|
381
|
+
"URI": prop_uri,
|
|
382
|
+
"Type": "Object Property",
|
|
383
|
+
"Label": prop_label,
|
|
384
|
+
"Sheet Name": f'=HYPERLINK("#\'{sheet_name}\'!A1", "{prop_label}")',
|
|
385
|
+
"Number of Relations": str(len(prop_data)),
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
writer.book.create_sheet("Object Properties", 1)
|
|
389
|
+
object_properties_summary_df = pd.DataFrame(object_properties_summary)
|
|
390
|
+
object_properties_summary_df = object_properties_summary_df.sort_values(
|
|
391
|
+
"URI"
|
|
392
|
+
).drop_duplicates()
|
|
393
|
+
object_properties_summary_df = object_properties_summary_df.astype(
|
|
394
|
+
{"Number of Relations": int}
|
|
395
|
+
)
|
|
396
|
+
object_properties_summary_df.to_excel(
|
|
397
|
+
writer, sheet_name="Object Properties", index=False
|
|
398
|
+
)
|
|
399
|
+
sheet = writer.sheets["Object Properties"]
|
|
400
|
+
sheet.sheet_properties.tabColor = "000000"
|
|
401
|
+
writer = self.autofit_columns(writer, "Object Properties")
|
|
402
|
+
|
|
403
|
+
# Save data to storage and create download URL
|
|
404
|
+
buffer = BytesIO()
|
|
405
|
+
writer.book.save(buffer)
|
|
406
|
+
excel_data = buffer.getvalue()
|
|
407
|
+
buffer.close()
|
|
408
|
+
asset = self.__naas_integration.upload_asset(
|
|
409
|
+
data=excel_data,
|
|
410
|
+
workspace_id=config.workspace_id,
|
|
411
|
+
storage_name=config.storage_name,
|
|
412
|
+
prefix=local_dir_path,
|
|
413
|
+
object_name=str(parameters.excel_file_name),
|
|
414
|
+
visibility="public",
|
|
415
|
+
return_url=True,
|
|
416
|
+
)
|
|
417
|
+
if asset is not None:
|
|
418
|
+
asset_url = asset.get("asset_url")
|
|
419
|
+
logger.info(
|
|
420
|
+
f"💾 Graph exported to {excel_file_path} (download URL: {asset_url})"
|
|
421
|
+
)
|
|
422
|
+
return asset_url
|
|
423
|
+
else:
|
|
424
|
+
logger.error("❌ Failed to upload asset to Naas")
|
|
425
|
+
return None
|
|
426
|
+
|
|
427
|
+
def as_tools(self) -> list[BaseTool]:
|
|
428
|
+
return [
|
|
429
|
+
StructuredTool(
|
|
430
|
+
name="export_graph_instances_to_excel",
|
|
431
|
+
description="Export graph instances to Excel and return asset URL to download it.",
|
|
432
|
+
func=lambda **kwargs: self.export_to_excel(
|
|
433
|
+
ExportGraphInstancesToExcelWorkflowParameters(**kwargs)
|
|
434
|
+
),
|
|
435
|
+
args_schema=ExportGraphInstancesToExcelWorkflowParameters,
|
|
436
|
+
)
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
def as_api(
|
|
440
|
+
self,
|
|
441
|
+
router: APIRouter,
|
|
442
|
+
route_name: str = "",
|
|
443
|
+
name: str = "",
|
|
444
|
+
description: str = "",
|
|
445
|
+
description_stream: str = "",
|
|
446
|
+
tags: list[str | Enum] | None = None,
|
|
447
|
+
) -> None:
|
|
448
|
+
if tags is None:
|
|
449
|
+
tags = []
|
|
450
|
+
return None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from naas_abi import secret, services
|
|
3
|
+
from naas_abi.workflows.ExportGraphInstancesToExcelWorkflow import (
|
|
4
|
+
ExportGraphInstancesToExcelWorkflow,
|
|
5
|
+
ExportGraphInstancesToExcelWorkflowConfiguration,
|
|
6
|
+
ExportGraphInstancesToExcelWorkflowParameters,
|
|
7
|
+
)
|
|
8
|
+
from naas_abi_marketplace.applications.naas.integrations.NaasIntegration import (
|
|
9
|
+
NaasIntegrationConfiguration,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def export_graph_to_excel() -> ExportGraphInstancesToExcelWorkflow:
|
|
15
|
+
configuration = ExportGraphInstancesToExcelWorkflowConfiguration(
|
|
16
|
+
triple_store=services.triple_store_service,
|
|
17
|
+
naas_integration_config=NaasIntegrationConfiguration(
|
|
18
|
+
api_key=secret.get("NAAS_API_KEY")
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
return ExportGraphInstancesToExcelWorkflow(configuration)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_export_graph_instances_to_excel_workflow(
|
|
25
|
+
export_graph_to_excel: ExportGraphInstancesToExcelWorkflow,
|
|
26
|
+
):
|
|
27
|
+
result = export_graph_to_excel.export_to_excel(
|
|
28
|
+
ExportGraphInstancesToExcelWorkflowParameters()
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert result is not None, result
|
|
32
|
+
assert isinstance(result, str), result
|
|
33
|
+
assert result.startswith("https://api.naas.ai/workspace/"), result
|