RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Business Automation with Local AI
  6. /Ch. 12
Business Automation with Local AI

12. BI Tool Integration

Chapter 12 of 18 · 25 min
KEY INSIGHT

BI tools expect standardized data formats. Build transformation layers that convert AI-generated insights into dimensions and measures compatible with existing dashboards.

Business Intelligence tools aggregate data for visualization. Integrating local AI with BI platforms requires understanding their data models, query interfaces, and extension points.

Direct Database Integration

Many BI tools connect directly to databases. Configure connections with appropriate read permissions.

-- Create dedicated read-only user for BI integration
CREATE USER bi_reader WITH PASSWORD 'secure_password_here';
GRANT CONNECT ON DATABASE production TO bi_reader;
GRANT USAGE ON SCHEMA analytics TO bi_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO bi_reader;

-- For tables with sensitive data, create views with limited columns
CREATE VIEW bi_reader.customer_summary AS
SELECT 
    customer_id,
    signup_date,
    tier,
    lifetime_value
FROM customers
WHERE deleted_at IS NULL;

REST API Integration

For BI tools supporting custom data sources, implement API endpoints.

# bi_data_server.py
from flask import Flask, jsonify, request
import ollama
import json
from datetime import datetime
from pathlib import Path

app = Flask(__name__)

# Cache for expensive queries
query_cache = {}
CACHE_TTL_SECONDS = 300

@app.route("/api/v1/metrics/<metric_name>")
def get_metric(metric_name):
    """Return predefined metrics for BI tools."""
    cache_key = f"{metric_name}_{request.args.get('period', 'day')}"
    
    # Check cache
    if cache_key in query_cache:
        cached, timestamp = query_cache[cache_key]
        if datetime.now().timestamp() - timestamp < CACHE_TTL_SECONDS:
            return jsonify(cached)
    
    # Compute metric
    result = compute_metric(metric_name, request.args)
    
    # Cache result
    query_cache[cache_key] = (result, datetime.now().timestamp())
    
    return jsonify(result)

def compute_metric(metric_name: str, params: dict) -> dict:
    """Compute metrics using local AI for complex aggregations."""
    period = params.get("period", "day")
    
    if metric_name == "anomaly_score":
        return compute_anomaly_score(period)
    elif metric_name == "trend_forecast":
        return compute_forecast(period)
    elif metric_name == "segment_insight":
        return generate_segment_insight(params)
    else:
        return {"error": f"Unknown metric: {metric_name}"}

def compute_anomaly_score(period: str) -> dict:
    """Use local LLM to detect anomalies in data."""
    # Load recent data
    data = load_recent_data(period)
    
    prompt = f"""Analyze this data for anomalies:
{data}

Return JSON with:
- "anomalies": list of anomaly records with timestamp, metric, value, expected_range
- "summary": one-sentence description of overall health
- "risk_score": 0-100 rating
"""
    
    response = ollama.chat(
        model="llama3",
        messages=[{"role": "user", "content": prompt}],
        options={"temperature": 0.1}
    )
    
    # Parse and return structured result
    try:
        return json.loads(response["message"]["content"])
    except json.JSONDecodeError:
        return {"error": "Failed to parse AI response", "raw": response}

@app.route("/api/v1/query", methods=["POST"])
def custom_query():
    """Execute custom analytical queries."""
    query_request = request.json
    
    if not query_request.get("query"):
        return jsonify({"error": "Query required"}), 400
    
    # Validate query safety
    if any(unsafe in query_request["query"].lower() for unsafe in 
           ["drop ", "delete ", "truncate ", "alter ", "insert ", "update "]):
        return jsonify({"error": "Only SELECT queries allowed"}), 403
    
    result = execute_read_query(query_request["query"])
    return jsonify(result)

def execute_read_query(query: str) -> dict:
    """Execute a read-only query against the data warehouse."""
    # Implementation depends on your database
    pass

@app.route("/health")
def health():
    """Health check endpoint for monitoring."""
    return jsonify({
        "status": "healthy",
        "cache_size": len(query_cache),
        "model_available": check_ollama_connection()
    })

def check_ollama_connection() -> bool:
    """Verify Ollama service is available."""
    try:
        ollama.list()
        return True
    except Exception:
        return False

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Tableau and Power BI Integration

For Tableau Extract API or Power BI REST API, use SDKs:

# tableau_integration.py
from tableauscraper import TableauScraper
import json

class TableauConnector:
    def __init__(self, server_url: str, site: str = ""):
        self.server_url = server_url
        self.site = site
        self.ts = None
    
    def authenticate(self, api_token: str):
        """Authenticate using personal access token."""
        self.ts = TableauScraper()
        self.ts.authenticate(
            server=self.server_url,
            token_name="automation_user",
            token_value=api_token,
            site=self.site
        )
    
    def get_workbook_data(self, workbook_name: str, view_name: str) -> list[dict]:
        """Extract data from a specific view."""
        url = f"{self.server_url}/views/{workbook_name}/{view_name}"
        self.ts.loads(url)
        
        worksheet = self.ts.getWorksheet(view_name)
        return worksheet.data

# Power BI integration using REST API
# power_bi_integration.py
import requests

class PowerBIClient:
    def __init__(self, tenant_id: str, client_id: str, client_secret: str):
        self.tenant_id = tenant_id
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token = self._get_access_token()
    
    def _get_access_token(self) -> str:
        """Obtain OAuth access token."""
        url = f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token"
        data = {
            "grant_type": "client_credentials",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "scope": "https://analysis.windows.net/powerbi/api/.default"
        }
        response = requests.post(url, data=data)
        return response.json()["access_token"]
    
    def get_datasets(self, workspace_id: str) -> list[dict]:
        """List datasets in a workspace."""
        url = f"https://api.powerbi.com/v1.0/myorg/groups/{workspace_id}/datasets"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        response = requests.get(url, headers=headers)
        return response.json()["value"]
    
    def refresh_dataset(self, workspace_id: str, dataset_id: str):
        """Trigger dataset refresh."""
        url = f"https://api.powerbi.com/v1.0/myorg/groups/{workspace_id}/datasets/{dataset_id}/refreshes"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        requests.post(url, headers=headers)
EXERCISE

Create an API endpoint that serves AI-generated forecasts as time-series data compatible with Grafana's JSON API plugin.

← Chapter 11
Scheduling Reports
Chapter 13 →
Dashboard Updates