Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,40 @@ Below are the steps to start all services. For other methods, please consult the

### Running All Services (Dev Mode)

1. **Set-up environement variables**

1. **Set-up environment variables**

`VITE_POWERGRID_SIMU`, `VITE_RAILWAY_SIMU` , `VITE_ATM_SIMU` are the simulators' endpoints.
Put for each UC the corresponding IP address.

Examples:
Configuration is read from a gitignored `.secrets` file that `docker-compose.sh` sources.
Copy the template and fill in your values:

```sh
export VITE_POWERGRID_SIMU=http://[Service url]:[Service port]
export VITE_RAILWAY_SIMU=http://[Service url]:[Service port]
export VITE_ATM_SIMU=http://[Service url]:[Service port]
cd config/dev/cab-standalone
cp .secrets.example .secrets
# then edit .secrets
```
> **_NOTE:_** For this step, you should already have a running simulator. If not, you can use the simulator we provided as an example. For this, please follow the tutorial provided in InteractiveAI/usecases_examples/PowerGrid/ then set the VITE_POWERGRID_SIMU variable to http://YOUR_SERVER_ADDRESS:5100/

Key variables (see `.secrets.example` for all options and per-environment values):

- `VITE_POWERGRID_SIMU` — the frontend's simulator endpoint. Use the same-origin proxy
value `/powergrid-simu` (avoids CORS); set it to `false` to disable the PowerGrid UI.
`VITE_RAILWAY_SIMU` / `VITE_ATM_SIMU` are the equivalents for the other use cases.
- `POWERGRID_SIMU_UPSTREAM` — where nginx actually forwards `/powergrid-simu/`. This is the
only value that changes per environment:
- Local dev : `http://host.docker.internal:5122/` (simulator container on the host)
- LAN : `http://192.168.208.61:5100/`
- Public/k8s: handled by `nginx-kubernetes.conf` via the helm chart (not this variable)
- `RL_AGENT_API_URL` / `RL_AGENT_API_TOKEN` — the deep expert agent that powers PowerGrid
recommendations (see [The PowerGrid expert agent API](#the-powergrid-expert-agent-api) below to
install it). A token is required in every mode:
- Local dev : `http://host.docker.internal:5123/api/v1/recommendation` (agent on the host)
- Server : `http://192.168.208.61:5000/api/v1/recommendation`
- Public : `https://interactiveagent.passerelle.irt-systemx.fr/api/v1/recommendation`

> **_NOTE:_** `host.docker.internal` lets the containers reach services (simulator, expert agent)
> running on the host — this is how local dev connects to them. Make sure those host services
> listen on `0.0.0.0` (not only `127.0.0.1`) so the containers can reach them.
>
> **_NOTE:_** For the simulator itself, you can use the example we provide — follow the tutorial
> in [InteractiveAI/usecases_examples/PowerGrid/](/usecases_examples/PowerGrid/README.md).
>
>
2. **Run InteractiveAI assistant**
Expand Down Expand Up @@ -129,6 +149,38 @@ your-chromium-browser --disable-web-security --user-data-dir="[some directory he

> **_NOTE:_** If you encounter any issues, please refer to our [troubleshooting guide](docs/troubleshooting.md).

### The PowerGrid expert agent API

PowerGrid recommendations are produced by a separate service — the **deep expert agent**. The
`cab_recommendation` service calls it at `RL_AGENT_API_URL`, so it must be running (and reachable)
for recommendations to appear in InteractiveAI.

1. Clone the agent repository and check out the API branch:

```sh
git clone https://github.com/ainetus/T2.1_deep_expert.git
cd T2.1_deep_expert
git checkout feat/api-auth-compose
```

2. Start it by following that repository's README (the `feat/api-auth-compose` branch ships a
Docker Compose and adds token authentication). For local development:
- expose it on port **5123**, and
- make it listen on `0.0.0.0` (not only `127.0.0.1`) so the InteractiveAI containers can reach
it through `host.docker.internal`.

3. Point InteractiveAI at it in `config/dev/cab-standalone/.secrets`, with a token that matches
the one the agent expects:

```sh
export RL_AGENT_API_URL=http://host.docker.internal:5123/api/v1/recommendation
export RL_AGENT_API_TOKEN=<token configured in the expert agent>
```

Then (re)run `./docker-compose.sh` so `cab_recommendation` picks up the values. For the LAN and
public deployments, use the corresponding `RL_AGENT_API_URL` from step 1 of
[Running All Services](#running-all-services-dev-mode) instead.

### Default ports

This project is based on a microservice architecture. Every service run on a specific port. Some of the default ports are as fellow:
Expand All @@ -138,6 +190,11 @@ This project is based on a microservice architecture. Every service run on a spe
* Historic Service: 5200
* Keycloak: 89

Companion services for the PowerGrid use case run on the host (local dev) and are reached by the
containers via `host.docker.internal`:
* PowerGrid simulator (provided example): 5122
* PowerGrid expert agent API: 5123

### Authentication data

For a development environment, the system uses predefined initial data for Keycloak setup.
Expand Down
Binary file modified backend/recommendation-service/requirements.txt
Binary file not shown.
184 changes: 2 additions & 182 deletions backend/recommendation-service/resources/PowerGrid/manager.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import json
import os
import warnings

import requests
import urllib3
from api.manager.base_manager import BaseRecommendationManager
from owlready2 import get_ontology
from settings import logger

from .PowerGridgrid2op_poc_simulator.assistant_manager import AgentType

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


Expand All @@ -21,10 +16,6 @@ class PowerGridManager(BaseRecommendationManager):
"""

def __init__(self):
script_dir = os.path.dirname(os.path.abspath(__file__))
self.owl_file_path = os.path.join(
script_dir, "ontology/Grid2onto_v2_3_1.owl"
)
# Runtime value comes from the RL_AGENT_API_URL env var (set via
# .secrets -> docker-compose.sh -> .env for local Docker, or extraEnv
# for k8s). The fallback is a safe in-cluster default only.
Expand All @@ -36,7 +27,7 @@ def __init__(self):
super().__init__()

def get_recommendation(self, request_data):
"""Get IA agent and ontology recomendations
"""Get IA agent recomendations

Args:
request_data (dict): A dictionary with keys "context" and "event"
Expand All @@ -45,18 +36,7 @@ def get_recommendation(self, request_data):
list[dict]: List of recomendations
"""
logger.info("Getting RL agent recommendations from external API")
parades = self._get_rl_parades(request_data)

onto_recommendation = []
event_data = request_data.get("event", {})
event_line = event_data.get("line")
if event_line:
logger.info("Getting ontology recommendation")
onto_recommendation = self.get_onto_recommendation(event_line)
logger.info(onto_recommendation)
print(onto_recommendation)
# both parades & onto_recommendation should be lists on the same format
return parades + onto_recommendation
return self._get_rl_parades(request_data)

def _get_rl_parades(self, request_data):
"""Call the external RL agent API to get parade recommendations.
Expand Down Expand Up @@ -97,163 +77,3 @@ def _get_rl_parades(self, request_data):
except Exception as e:
logger.error(f"Unexpected error calling RL agent API: {type(e).__name__}: {e}")
return []

def get_onto_recommendation(self, event_line):
"""Get Ontology recomendations

Args:
event_line (string): Line name

Returns:
dict: One ontology recomendation
"""
# Default output
output_json = {
"title": "Default Ontology Recommendation",
"description": (
"No recommendation has been found for this overload, because "
"it has not been observed in the past."
),
"use_case": "PowerGrid",
"agent_type": AgentType.onto.name,
"actions": [{}],
"kpis": {
"type_of_the_reco": "Null",
"efficiency_of_the_reco": 1.99999,
},
}

# Loading ontology
rte_onto = get_ontology(self.owl_file_path).load()

# Get all powerlines
powerlines_query = """
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX x_1.1: <http://purl.org/dc/elements/1.1/>
PREFIX xml: <http://www.w3.org/XML/1998/namespace>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX cab: <http://www.semanticweb.org/emna.amdouni/ontologies/2023/0/Grid2Onto#>

SELECT DISTINCT ?line
WHERE {{
?line rdf:type cab:Powerline .
}}
"""

powerlines_list = list(rte_onto.world.sparql(powerlines_query))

our_powerline = "powerline_" + event_line

selected_powerline = None

for powerline in powerlines_list:

if our_powerline in str(powerline[0]):
selected_powerline = str(powerline[0])
break

if selected_powerline is not None:
parts_prefix = selected_powerline.split(".")
prefix_onto = parts_prefix[0] + "."
selected_powerline = selected_powerline.replace(prefix_onto, "")

selected_powerline_iri = (
"http://www.semanticweb.org/emna.amdouni/ontologies/2023/0/Grid2Onto#"
+ selected_powerline
)

# Get all similar situations
similar_situations_query = """
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX x_1.1: <http://purl.org/dc/elements/1.1/>
PREFIX xml: <http://www.w3.org/XML/1998/namespace>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX cab: <http://www.semanticweb.org/emna.amdouni/ontologies/2023/0/Grid2Onto#>

SELECT DISTINCT ?similarIssue ?line ?pastActionText ?actionDictVal ?category ?efficacity
{{
?similarIssue a cab:Powerline_overload_issue .
?similarIssue cab:is_associated_with ?pastAction .
?pastAction cab:has_initial_value ?pastActionText .
?pastAction rdf:type ?category .
?actionDict a cab:Action_dict .
?pastAction cab:has_part ?actionDict .
?actionDict cab:has_initial_value ?actionDictVal .
?initial a cab:Initial_situation .
?initial cab:has_part ?pastAction .
?line a cab:Powerline .
?initial cab:is_about ?line .
?rho a cab:Rho .
?similarIssue cab:has_measurement ?rho .
?rho cab:has_final_value ?efficacity
FILTER (?category != <http://www.w3.org/2002/07/owl#NamedIndividual>)
FILTER (?line = <{line}>)
}}
""".format(
line=selected_powerline_iri
)

similar_situations = list(
rte_onto.world.sparql(similar_situations_query)
)
if similar_situations:
# compute number of situations and rho max
distinct_recommendations = set()
rho_max = min(sublist[-1] for sublist in similar_situations)

for situation in similar_situations:
recommendation = str(situation[2])
action = str(situation[3])
action = action.replace("'", '"')
action = action.replace("False", "false").replace(
"True", "true"
)
action_dict = json.loads(action)

category = str(situation[4])
category = category.replace(prefix_onto, "")
efficacity = situation[4]
if recommendation not in distinct_recommendations:
distinct_recommendations.add(recommendation)

nb_past_actions_query = f"""
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX x_1.1: <http://purl.org/dc/elements/1.1/>
PREFIX xml: <http://www.w3.org/XML/1998/namespace>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX cab: <http://www.semanticweb.org/emna.amdouni/ontologies/2023/0/Grid2Onto#>

SELECT (COUNT(?pastActionText) AS ?count)
WHERE {{
?similarIssue a cab:Powerline_overload_issue .
?similarIssue cab:is_associated_with ?pastAction .
?pastAction cab:has_initial_value ?pastActionText .
?initial a cab:Initial_situation .
?initial cab:has_part ?pastAction .
?line a cab:Powerline .
?initial cab:is_about ?line .
FILTER (?line = <{selected_powerline_iri}> && CONTAINS(?pastActionText, '{recommendation}'))
}}
"""
nb_similar_situations = list(
rte_onto.world.sparql(nb_past_actions_query)
)[0][0]
output_json = {
"title": recommendation,
"description": f"This pattern has been observed {nb_similar_situations} times in the past.",
"use_case": "PowerGrid",
"agent_type": AgentType.onto.name,
"actions": [action_dict],
"kpis": {
"type_of_the_reco": category,
"efficiency_of_the_reco": rho_max,
},
}

return [output_json]
Loading
Loading