Skip to content

Commit

Permalink
done app
Browse files Browse the repository at this point in the history
  • Loading branch information
sasax7 committed Dec 23, 2024
1 parent edb44cc commit 132bb33
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 63 deletions.
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@

__pycache__
*.pyc
.env
.vscode
.git
venv
keys.txt
__pycache__/
.venv

Binary file modified __pycache__/get_trend_data.cpython-311.pyc
Binary file not shown.
2 changes: 1 addition & 1 deletion api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CorrelationRequest(BaseModel):

class CorrelateChildrenRequest(BaseModel):
asset_id: int
lag: Optional[Dict[LagUnit, int]] = None
lags: Optional[List[Dict[LagUnit, int]]] = None
start_time: Optional[datetime] = None
end_time: Optional[datetime] = None

Expand Down
89 changes: 53 additions & 36 deletions api/openapi.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
from fastapi import FastAPI, HTTPException

from datetime import datetime
from api.models import (
CorrelationRequest,
CorrelateChildrenRequest,
)
import pytz
import yaml
from api.models import CorrelationRequest, CorrelateChildrenRequest, AssetAttribute
from api.correlation import get_data, compute_correlation
from api.plot_correlation import (
create_best_correlation_heatmap,
in_depth_plot_scatter,
plot_lag_correlations,
)
import pytz
from get_trend_data import get_all_asset_children


# Create the FastAPI app instance
app = FastAPI(
title="Correlation App API",
description="API to manage and query correlations between assets.",
version="1.0.0",
openapi_url="/v1/version/openapi.json",
openapi_version="3.1.0",
)

# Load custom OpenAPI schema
with open("openapi.yaml", "r") as f:
openapi_yaml = yaml.safe_load(f)


def custom_openapi():
app.openapi_schema = openapi_yaml
return app.openapi_schema

app = FastAPI()

app.openapi = custom_openapi

@app.post("/correlate")

# Define endpoints
@app.post("/v1/correlate")
def correlate_assets(request: CorrelationRequest):
end_time = request.end_time or datetime.now()
dataframes = get_data(request)
Expand All @@ -30,19 +51,30 @@ def correlate_assets(request: CorrelationRequest):
}


@app.post("/correlate-children")
@app.post("/v1/correlate-children")
def correlate_asset_children(request: CorrelateChildrenRequest):
end_time = request.end_time or datetime.now()
child_asset_ids = get_all_asset_children(request.asset_id)
print(f"Found {len(child_asset_ids)} children for asset {request.asset_id}")
correlation_request = CorrelationRequest(
assets=child_asset_ids,
lags=request.lags,
start_time=request.start_time,
end_time=request.end_time,
)

correlations = correlate_assets(correlation_request)

return {
"asset_id": request.asset_id,
"lag": request.lag,
"assets": child_asset_ids,
"lags": request.lags,
"start_time": request.start_time,
"end_time": end_time,
"correlation": "To be implemented",
"correlation": correlations,
}


@app.post("/in-depth-correlation")
@app.post("/v1/in-depth-correlation")
def in_depth_correlation(request: CorrelationRequest):
"""
1) Fetch data for exactly two assets/attributes.
Expand All @@ -61,29 +93,13 @@ def in_depth_correlation(request: CorrelationRequest):
detail="Could not retrieve data for both assets/attributes. Check logs.",
)

# 2) Compute correlation (including any lags) for these two DataFrameInfo
correlations = compute_correlation(df_infos, request)
# 'correlations' is a dict that includes "lag_details" for the single pair, e.g.:
# {
# "867_energy_costs and 867_Wirkleistung": {
# "best_correlation": 0.16,
# "best_lag": 3,
# "best_lag_unit": "hours",
# "lag_details": [
# {"lag_unit": "hours", "lag_step": -10, "correlation": 0.05}, ...
# ]
# }
# }

# 3) Plot the lag correlation lines for that single pair
# This will create line plots in "lag_plots/" (by default) for each lag unit
plot_lag_correlations(correlations, output_dir="lag_plots")

# 4) Also create a scatter plot of the raw data to see direct x-y relationship
# (We re-use 'df_infos' -> 2 DataFrameInfo objects)

lag_plots = plot_lag_correlations(correlations, output_dir="/tmp/lag_plots")

try:
scatter_result = in_depth_plot_scatter(
df_infos, output_file="in_depth_scatter.png"
df_infos, output_file="/tmp/in_depth_scatter.png"
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
Expand All @@ -92,10 +108,11 @@ def in_depth_correlation(request: CorrelationRequest):

return {
"assets": request.assets,
"lags": request.lags,
"start_time": request.start_time,
"end_time": end_time,
"best_correlation": scatter_result["correlation"], # correlation from scatter
"plot_base64_png": scatter_result["plot_base64_png"], # scatter in Base64
"lag_correlation_plots": "Saved in lag_plots/ directory",
"detailed_correlations": correlations, # full correlation details with lags
"correlation": correlations,
"scatter_plot": scatter_result["plot_base64_png"],
"columns": scatter_result["columns"],
"lag_plots": lag_plots,
}
47 changes: 23 additions & 24 deletions api/plot_correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import base64
import io
import os


def create_best_correlation_heatmap(correlations_dict, output_file="heatmap.png"):
def create_best_correlation_heatmap(correlations_dict, output_file="/tmp/heatmap.png"):
"""
Creates a heatmap from the 'best_correlation' values in correlations_dict.
In addition to correlation, each cell is annotated with the best_lag and lag_unit.
Saves the resulting figure to 'output_file' instead of showing it.
The input 'correlations_dict' is expected to look like:
Expand All @@ -28,7 +26,6 @@ def create_best_correlation_heatmap(correlations_dict, output_file="heatmap.png"
Only the best_correlation field is used for coloring the heatmap
(pairs with None or NaN are left blank).
The annotation in each cell will display correlation + lag info if available.
"""
# 1) Gather all columns (col1, col2) by splitting each key on " and "
all_cols = set()
Expand All @@ -48,51 +45,39 @@ def create_best_correlation_heatmap(correlations_dict, output_file="heatmap.png"
# Convert the set of columns to a sorted list (for consistent ordering)
all_cols = sorted(all_cols)

# 2) Initialize an NxN DataFrame for numeric correlation and a second for text annotations
# 2) Initialize an NxN DataFrame for numeric correlation
df_matrix = pd.DataFrame(np.nan, index=all_cols, columns=all_cols)
df_annot = pd.DataFrame("", index=all_cols, columns=all_cols)

# 3) Fill in the best correlations and annotations
# 3) Fill in the best correlations
for col1, col2, best_corr, best_lag, best_lag_unit in data_for_matrix:
if best_corr is not None and not np.isnan(best_corr):
df_matrix.loc[col1, col2] = best_corr
df_matrix.loc[col2, col1] = best_corr

corr_str = f"{best_corr:.2f}"
if best_lag_unit and best_lag != 0:
annotation = f"{corr_str}\n({best_lag} {best_lag_unit})"
else:
annotation = corr_str

df_annot.loc[col1, col2] = annotation
df_annot.loc[col2, col1] = annotation

# (Optional) Set diagonal to 1.0 correlation, with a simple annotation like "1.00"
# (Optional) Set diagonal to 1.0 correlation
for col in all_cols:
df_matrix.loc[col, col] = 1.0
df_annot.loc[col, col] = "1.00"

# 4) Plot the heatmap (no plt.show())
plt.figure(figsize=(10, 8))
sns.heatmap(
df_matrix,
annot=df_annot,
fmt="",
annot=False, # Disable text annotations
cmap="coolwarm",
square=True,
center=0.0,
vmin=-1,
vmax=1,
)
plt.title("Best Correlation Heatmap (with Lag Info)")
plt.title("Best Correlation Heatmap")
plt.tight_layout()

# Save the figure to disk
plt.savefig(output_file)
plt.close() # Close the figure to free resources


def in_depth_plot_scatter(df_info_list, output_file="in_depth_scatter.png"):
def in_depth_plot_scatter(df_info_list, output_file="/tmp/in_depth_scatter.png"):
"""
Accepts a list of TWO DataFrameInfo objects (each with one column),
merges them, computes correlation, and returns:
Expand Down Expand Up @@ -161,7 +146,7 @@ def in_depth_plot_scatter(df_info_list, output_file="in_depth_scatter.png"):
}


def plot_lag_correlations(correlations_dict, output_dir="lag_plots"):
def plot_lag_correlations(correlations_dict, output_dir="/tmp/lag_plots"):
"""
For each pair of columns in 'correlations_dict', we look at 'lag_details'
and group them by lag_unit (e.g., hours, days). Then we make a separate plot
Expand All @@ -174,10 +159,13 @@ def plot_lag_correlations(correlations_dict, output_dir="lag_plots"):
"""
import os
import matplotlib.pyplot as plt
import io
import base64

os.makedirs(output_dir, exist_ok=True)

seen_pairs = set() # Track pairs we've already plotted
plot_images = {} # Dictionary to store base64-encoded images

for pair_name, info in correlations_dict.items():
# Split on " and " to get the two column names.
Expand Down Expand Up @@ -231,4 +219,15 @@ def plot_lag_correlations(correlations_dict, output_dir="lag_plots"):

plt.tight_layout()
plt.savefig(filepath)
plt.close(fig)

# Save the figure to a buffer and encode it in base64
buf = io.BytesIO()
fig.savefig(buf, format="png")
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode("utf-8")
plt.close(fig) # Free resources

# Store the base64 image in the dictionary
plot_images[f"{pair_label}_{safe_unit}"] = img_base64

return plot_images
20 changes: 20 additions & 0 deletions dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.11.9

WORKDIR /app

RUN apt-get update && apt-get install -y \
git \
libpq-dev \
gcc \
&& apt-get clean

COPY requirements.txt .

RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 3000

CMD ["python", "main.py"]
20 changes: 20 additions & 0 deletions get_trend_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import eliona.api_client2
from eliona.api_client2.rest import ApiException
from eliona.api_client2.api.data_api import DataApi
from eliona.api_client2.api.assets_api import AssetsApi
import os
import logging
import pytz
from datetime import datetime
from api.models import AssetAttribute

# Initialize the logger
logger = logging.getLogger(__name__)
Expand All @@ -18,6 +20,24 @@
# Create an instance of the API client
api_client = eliona.api_client2.ApiClient(configuration)
data_api = DataApi(api_client)
assets_api = AssetsApi(api_client)


def get_all_asset_children(asset_id):
try:
logger.info(f"Fetching all assets to find children for asset {asset_id}")
assets = assets_api.get_assets()
child_ids = [asset_id] # Start with the parent asset_id

for asset in assets:
if asset_id in asset.locational_asset_id_path:
child_ids.append(asset.id)

logger.info(f"Found {len(child_ids) - 1} children for asset {asset_id}")
return [AssetAttribute(asset_id=child_id) for child_id in child_ids]
except ApiException as e:
logger.error(f"Exception when calling AssetsApi->get_assets: {e}")
return [AssetAttribute(asset_id=asset_id)]


def get_trend_data(asset_id, start_date, end_date):
Expand Down
1 change: 1 addition & 0 deletions icon
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data:image/png;base64,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
3 changes: 2 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ def start_api():
port = int(os.getenv("API_SERVER_PORT", 3000))
uvicorn.run("api.openapi:app", host="0.0.0.0", port=port)

#Initialize()

# Initialize()
start_api()
19 changes: 19 additions & 0 deletions metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Correlation",
"elionaMinVersion": "v11.0.0",
"displayName": {
"en": "correlation app",
"de": "Korrelations-App"
},
"description": {
"en": "This app provides a correlation analysis.",
"de": "Diese App bietet eine Korrelationsanalyse."
},
"dashboardTemplateNames": [
"correlation"
],
"apiUrl": "v1",
"apiSpecificationPath": "/version/openapi.json",
"documentationUrl": "https://doc.eliona.io/eliona/referenzen/app-entwicklung",
"useEnvironment": [ "CONNECTION_STRING", "API_ENDPOINT", "API_TOKEN" ]
}
Loading

0 comments on commit 132bb33

Please sign in to comment.