powerbi-ontology-extractor 0.1.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.
- cli/__init__.py +1 -0
- cli/pbi_ontology_cli.py +286 -0
- powerbi_ontology/__init__.py +38 -0
- powerbi_ontology/analyzer.py +420 -0
- powerbi_ontology/chat.py +303 -0
- powerbi_ontology/cli.py +530 -0
- powerbi_ontology/contract_builder.py +269 -0
- powerbi_ontology/dax_parser.py +305 -0
- powerbi_ontology/export/__init__.py +17 -0
- powerbi_ontology/export/contract_to_owl.py +408 -0
- powerbi_ontology/export/fabric_iq.py +243 -0
- powerbi_ontology/export/fabric_iq_to_owl.py +463 -0
- powerbi_ontology/export/json_schema.py +110 -0
- powerbi_ontology/export/ontoguard.py +177 -0
- powerbi_ontology/export/owl.py +522 -0
- powerbi_ontology/extractor.py +368 -0
- powerbi_ontology/mcp_config.py +237 -0
- powerbi_ontology/mcp_models.py +166 -0
- powerbi_ontology/mcp_server.py +1106 -0
- powerbi_ontology/ontology_diff.py +776 -0
- powerbi_ontology/ontology_generator.py +406 -0
- powerbi_ontology/review.py +556 -0
- powerbi_ontology/schema_mapper.py +369 -0
- powerbi_ontology/semantic_debt.py +584 -0
- powerbi_ontology/utils/__init__.py +13 -0
- powerbi_ontology/utils/pbix_reader.py +558 -0
- powerbi_ontology/utils/visualizer.py +332 -0
- powerbi_ontology_extractor-0.1.0.dist-info/METADATA +507 -0
- powerbi_ontology_extractor-0.1.0.dist-info/RECORD +33 -0
- powerbi_ontology_extractor-0.1.0.dist-info/WHEEL +5 -0
- powerbi_ontology_extractor-0.1.0.dist-info/entry_points.txt +4 -0
- powerbi_ontology_extractor-0.1.0.dist-info/licenses/LICENSE +21 -0
- powerbi_ontology_extractor-0.1.0.dist-info/top_level.txt +2 -0
cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI module for PowerBI Ontology Extractor."""
|
cli/pbi_ontology_cli.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PowerBI Ontology CLI
|
|
3
|
+
|
|
4
|
+
Command-line interface for PowerBI Ontology Extractor.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
from rich import print as rprint
|
|
18
|
+
except ImportError:
|
|
19
|
+
print("Error: Required packages not installed. Run: pip install click rich")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
from powerbi_ontology import PowerBIExtractor, OntologyGenerator, SemanticAnalyzer
|
|
23
|
+
from powerbi_ontology.export import FabricIQExporter, OntoGuardExporter, JSONSchemaExporter, OWLExporter
|
|
24
|
+
from powerbi_ontology import SchemaMapper
|
|
25
|
+
from powerbi_ontology.utils.visualizer import OntologyVisualizer
|
|
26
|
+
|
|
27
|
+
# Setup logging
|
|
28
|
+
logging.basicConfig(
|
|
29
|
+
level=logging.INFO,
|
|
30
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
console = Console()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@click.group()
|
|
37
|
+
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging')
|
|
38
|
+
def cli(verbose):
|
|
39
|
+
"""PowerBI Ontology Extractor - Extract semantic intelligence from Power BI dashboards."""
|
|
40
|
+
if verbose:
|
|
41
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cli.command()
|
|
45
|
+
@click.argument('pbix_file', type=click.Path(exists=True))
|
|
46
|
+
@click.option('--output', '-o', type=click.Path(), help='Output file path')
|
|
47
|
+
@click.option('--format', '-f', type=click.Choice(['json', 'fabric-iq', 'ontoguard']), default='json', help='Output format')
|
|
48
|
+
def extract(pbix_file, output, format):
|
|
49
|
+
"""Extract ontology from Power BI .pbix file."""
|
|
50
|
+
console.print(f"[bold green]Extracting ontology from[/bold green] {pbix_file}")
|
|
51
|
+
|
|
52
|
+
with Progress(
|
|
53
|
+
SpinnerColumn(),
|
|
54
|
+
TextColumn("[progress.description]{task.description}"),
|
|
55
|
+
console=console
|
|
56
|
+
) as progress:
|
|
57
|
+
task = progress.add_task("Extracting...", total=None)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
extractor = PowerBIExtractor(pbix_file)
|
|
61
|
+
semantic_model = extractor.extract()
|
|
62
|
+
|
|
63
|
+
progress.update(task, description="Generating ontology...")
|
|
64
|
+
generator = OntologyGenerator(semantic_model)
|
|
65
|
+
ontology = generator.generate()
|
|
66
|
+
|
|
67
|
+
# Determine output file
|
|
68
|
+
if not output:
|
|
69
|
+
output = Path(pbix_file).stem + "_ontology.json"
|
|
70
|
+
|
|
71
|
+
# Export based on format
|
|
72
|
+
if format == 'json':
|
|
73
|
+
ontology_dict = {
|
|
74
|
+
"name": ontology.name,
|
|
75
|
+
"version": ontology.version,
|
|
76
|
+
"source": ontology.source,
|
|
77
|
+
"entities": [
|
|
78
|
+
{
|
|
79
|
+
"name": e.name,
|
|
80
|
+
"description": e.description,
|
|
81
|
+
"properties": [
|
|
82
|
+
{
|
|
83
|
+
"name": p.name,
|
|
84
|
+
"type": p.data_type,
|
|
85
|
+
"required": p.required
|
|
86
|
+
}
|
|
87
|
+
for p in e.properties
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
for e in ontology.entities
|
|
91
|
+
],
|
|
92
|
+
"relationships": [
|
|
93
|
+
{
|
|
94
|
+
"from": r.from_entity,
|
|
95
|
+
"to": r.to_entity,
|
|
96
|
+
"type": r.relationship_type,
|
|
97
|
+
"cardinality": r.cardinality
|
|
98
|
+
}
|
|
99
|
+
for r in ontology.relationships
|
|
100
|
+
],
|
|
101
|
+
"businessRules": [
|
|
102
|
+
{
|
|
103
|
+
"name": r.name,
|
|
104
|
+
"entity": r.entity,
|
|
105
|
+
"condition": r.condition,
|
|
106
|
+
"action": r.action
|
|
107
|
+
}
|
|
108
|
+
for r in ontology.business_rules
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
with open(output, 'w') as f:
|
|
112
|
+
json.dump(ontology_dict, f, indent=2)
|
|
113
|
+
elif format == 'fabric-iq':
|
|
114
|
+
exporter = FabricIQExporter(ontology)
|
|
115
|
+
fabric_json = exporter.export()
|
|
116
|
+
with open(output, 'w') as f:
|
|
117
|
+
json.dump(fabric_json, f, indent=2)
|
|
118
|
+
elif format == 'ontoguard':
|
|
119
|
+
exporter = OntoGuardExporter(ontology)
|
|
120
|
+
ontoguard_json = exporter.export()
|
|
121
|
+
with open(output, 'w') as f:
|
|
122
|
+
json.dump(ontoguard_json, f, indent=2)
|
|
123
|
+
|
|
124
|
+
progress.update(task, completed=True)
|
|
125
|
+
console.print(f"[bold green]✓[/bold green] Ontology extracted to {output}")
|
|
126
|
+
console.print(f" Entities: {len(ontology.entities)}")
|
|
127
|
+
console.print(f" Relationships: {len(ontology.relationships)}")
|
|
128
|
+
console.print(f" Business Rules: {len(ontology.business_rules)}")
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
132
|
+
logging.exception("Extraction failed")
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@cli.command()
|
|
137
|
+
@click.argument('pbix_files', nargs=-1, type=click.Path(exists=True))
|
|
138
|
+
@click.option('--report', '-r', type=click.Path(), help='Output report file (HTML)')
|
|
139
|
+
@click.option('--output-dir', '-d', type=click.Path(), help='Output directory for individual ontologies')
|
|
140
|
+
def analyze(pbix_files, report, output_dir):
|
|
141
|
+
"""Analyze multiple Power BI files for conflicts and semantic debt."""
|
|
142
|
+
if not pbix_files:
|
|
143
|
+
console.print("[bold red]Error:[/bold red] No .pbix files provided")
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
console.print(f"[bold green]Analyzing[/bold green] {len(pbix_files)} Power BI files...")
|
|
147
|
+
|
|
148
|
+
semantic_models = []
|
|
149
|
+
|
|
150
|
+
with Progress(console=console) as progress:
|
|
151
|
+
task = progress.add_task("Loading files...", total=len(pbix_files))
|
|
152
|
+
|
|
153
|
+
for pbix_file in pbix_files:
|
|
154
|
+
try:
|
|
155
|
+
extractor = PowerBIExtractor(pbix_file)
|
|
156
|
+
model = extractor.extract()
|
|
157
|
+
semantic_models.append(model)
|
|
158
|
+
progress.advance(task)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Failed to extract {pbix_file}: {e}")
|
|
161
|
+
|
|
162
|
+
if not semantic_models:
|
|
163
|
+
console.print("[bold red]Error:[/bold red] No valid semantic models extracted")
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
# Analyze
|
|
167
|
+
analyzer = SemanticAnalyzer(semantic_models)
|
|
168
|
+
|
|
169
|
+
console.print("\n[bold]Analysis Results:[/bold]")
|
|
170
|
+
|
|
171
|
+
# Conflicts
|
|
172
|
+
conflicts = analyzer.detect_conflicts()
|
|
173
|
+
console.print(f"\n[bold yellow]Conflicts:[/bold yellow] {len(conflicts)}")
|
|
174
|
+
for conflict in conflicts[:5]: # Show first 5
|
|
175
|
+
console.print(f" • {conflict.concept}: {conflict.dashboard1} vs {conflict.dashboard2}")
|
|
176
|
+
if len(conflicts) > 5:
|
|
177
|
+
console.print(f" ... and {len(conflicts) - 5} more")
|
|
178
|
+
|
|
179
|
+
# Semantic debt
|
|
180
|
+
debt_report = analyzer.calculate_semantic_debt()
|
|
181
|
+
console.print(f"\n[bold red]Semantic Debt:[/bold red] ${debt_report.total_cost:,.0f}")
|
|
182
|
+
console.print(f" {debt_report.message}")
|
|
183
|
+
|
|
184
|
+
# Generate report
|
|
185
|
+
if report:
|
|
186
|
+
analyzer.generate_consolidation_report(report)
|
|
187
|
+
console.print(f"\n[bold green]✓[/bold green] Report saved to {report}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@cli.command()
|
|
191
|
+
@click.argument('ontology_file', type=click.Path(exists=True))
|
|
192
|
+
@click.option('--format', '-f', type=click.Choice(['fabric-iq', 'ontoguard', 'json-schema', 'owl']), required=True, help='Export format')
|
|
193
|
+
@click.option('--output', '-o', type=click.Path(), help='Output file path')
|
|
194
|
+
def export(ontology_file, format, output):
|
|
195
|
+
"""Export ontology to different formats."""
|
|
196
|
+
console.print(f"[bold green]Exporting[/bold green] {ontology_file} to {format} format...")
|
|
197
|
+
|
|
198
|
+
# Load ontology (simplified - would need proper loading)
|
|
199
|
+
console.print("[bold yellow]Note:[/bold yellow] Full export requires loading ontology from file")
|
|
200
|
+
console.print("This is a simplified version. Use 'extract' command with --format option instead.")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@cli.command()
|
|
204
|
+
@click.argument('ontology_file', type=click.Path(exists=True))
|
|
205
|
+
@click.option('--schema', '-s', type=click.Path(), help='Current database schema JSON')
|
|
206
|
+
def validate(ontology_file, schema):
|
|
207
|
+
"""Validate schema bindings and detect drift."""
|
|
208
|
+
console.print(f"[bold green]Validating[/bold green] {ontology_file}...")
|
|
209
|
+
|
|
210
|
+
if schema:
|
|
211
|
+
console.print(f" Comparing against schema: {schema}")
|
|
212
|
+
# Load and compare schemas
|
|
213
|
+
console.print("[bold yellow]Note:[/bold yellow] Full validation requires schema mapping setup")
|
|
214
|
+
else:
|
|
215
|
+
console.print("[bold yellow]Warning:[/bold yellow] No schema provided. Cannot detect drift.")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@cli.command()
|
|
219
|
+
@click.argument('ontology_file', type=click.Path(exists=True))
|
|
220
|
+
@click.option('--output', '-o', type=click.Path(), help='Output image file')
|
|
221
|
+
@click.option('--interactive', '-i', is_flag=True, help='Generate interactive HTML')
|
|
222
|
+
@click.option('--format', '-f', type=click.Choice(['png', 'svg', 'pdf']), default='png', help='Image format')
|
|
223
|
+
def visualize(ontology_file, output, interactive, format):
|
|
224
|
+
"""Visualize ontology as entity-relationship diagram."""
|
|
225
|
+
console.print(f"[bold green]Visualizing[/bold green] {ontology_file}...")
|
|
226
|
+
|
|
227
|
+
console.print("[bold yellow]Note:[/bold yellow] Full visualization requires loading ontology from file")
|
|
228
|
+
console.print("This is a simplified version. Use 'extract' command first.")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@cli.command()
|
|
232
|
+
@click.option('--input-dir', '-i', type=click.Path(exists=True), required=True, help='Input directory with .pbix files')
|
|
233
|
+
@click.option('--output-dir', '-o', type=click.Path(), required=True, help='Output directory for ontologies')
|
|
234
|
+
def batch(input_dir, output_dir):
|
|
235
|
+
"""Process multiple .pbix files in batch."""
|
|
236
|
+
input_path = Path(input_dir)
|
|
237
|
+
output_path = Path(output_dir)
|
|
238
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
|
|
240
|
+
pbix_files = list(input_path.glob("*.pbix"))
|
|
241
|
+
|
|
242
|
+
if not pbix_files:
|
|
243
|
+
console.print(f"[bold red]Error:[/bold red] No .pbix files found in {input_dir}")
|
|
244
|
+
sys.exit(1)
|
|
245
|
+
|
|
246
|
+
console.print(f"[bold green]Processing[/bold green] {len(pbix_files)} files...")
|
|
247
|
+
|
|
248
|
+
with Progress(console=console) as progress:
|
|
249
|
+
task = progress.add_task("Processing...", total=len(pbix_files))
|
|
250
|
+
|
|
251
|
+
for pbix_file in pbix_files:
|
|
252
|
+
try:
|
|
253
|
+
extractor = PowerBIExtractor(str(pbix_file))
|
|
254
|
+
semantic_model = extractor.extract()
|
|
255
|
+
|
|
256
|
+
generator = OntologyGenerator(semantic_model)
|
|
257
|
+
ontology = generator.generate()
|
|
258
|
+
|
|
259
|
+
output_file = output_path / f"{pbix_file.stem}_ontology.json"
|
|
260
|
+
ontology_dict = {
|
|
261
|
+
"name": ontology.name,
|
|
262
|
+
"version": ontology.version,
|
|
263
|
+
"source": ontology.source,
|
|
264
|
+
"entities": len(ontology.entities),
|
|
265
|
+
"relationships": len(ontology.relationships),
|
|
266
|
+
"businessRules": len(ontology.business_rules)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
with open(output_file, 'w') as f:
|
|
270
|
+
json.dump(ontology_dict, f, indent=2)
|
|
271
|
+
|
|
272
|
+
progress.advance(task)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Failed to process {pbix_file}: {e}")
|
|
275
|
+
progress.advance(task)
|
|
276
|
+
|
|
277
|
+
console.print(f"[bold green]✓[/bold green] Batch processing complete. Output: {output_dir}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def main():
|
|
281
|
+
"""Main entry point."""
|
|
282
|
+
cli()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == '__main__':
|
|
286
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PowerBI Ontology Extractor
|
|
3
|
+
|
|
4
|
+
Extract semantic intelligence from Power BI .pbix files and convert to formal ontologies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "PowerBI Ontology Extractor Contributors"
|
|
9
|
+
|
|
10
|
+
from powerbi_ontology.extractor import PowerBIExtractor, SemanticModel
|
|
11
|
+
from powerbi_ontology.ontology_generator import OntologyGenerator, Ontology
|
|
12
|
+
from powerbi_ontology.analyzer import SemanticAnalyzer
|
|
13
|
+
from powerbi_ontology.contract_builder import ContractBuilder
|
|
14
|
+
from powerbi_ontology.schema_mapper import SchemaMapper
|
|
15
|
+
from powerbi_ontology.semantic_debt import SemanticDebtAnalyzer, SemanticDebtReport, analyze_ontologies
|
|
16
|
+
from powerbi_ontology.ontology_diff import OntologyDiff, DiffReport, diff_ontologies, merge_ontologies
|
|
17
|
+
from powerbi_ontology.review import OntologyReview, ReviewWorkflow, ReviewStatus, create_review
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"PowerBIExtractor",
|
|
21
|
+
"SemanticModel",
|
|
22
|
+
"OntologyGenerator",
|
|
23
|
+
"Ontology",
|
|
24
|
+
"SemanticAnalyzer",
|
|
25
|
+
"ContractBuilder",
|
|
26
|
+
"SchemaMapper",
|
|
27
|
+
"SemanticDebtAnalyzer",
|
|
28
|
+
"SemanticDebtReport",
|
|
29
|
+
"analyze_ontologies",
|
|
30
|
+
"OntologyDiff",
|
|
31
|
+
"DiffReport",
|
|
32
|
+
"diff_ontologies",
|
|
33
|
+
"merge_ontologies",
|
|
34
|
+
"OntologyReview",
|
|
35
|
+
"ReviewWorkflow",
|
|
36
|
+
"ReviewStatus",
|
|
37
|
+
"create_review",
|
|
38
|
+
]
|