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
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ The platform uses the project **OperatorFabric** for notification management.
### Prerequisites

- [Git (version 2.40.1)](https://git-scm.com/)
- [Docker (version 24.0.2)](https://www.docker.com/)
- [Docker Compose (version 1.25.0 or later)](https://www.docker.com/)
- [Docker Engine (version 27)](https://www.docker.com/)
- [Docker Compose V2](https://www.docker.com/)


### Setting Up the Environment
Expand All @@ -63,7 +63,7 @@ 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 environement variables**


`VITE_POWERGRID_SIMU`, `VITE_RAILWAY_SIMU` , `VITE_ATM_SIMU` are the simulators' endpoints.
Expand All @@ -79,31 +79,34 @@ export VITE_ATM_SIMU=http://[Service url]:[Service port]
> **_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/
>
>
2. Run InteractiveAI assistant
2. **Run InteractiveAI assistant**
```sh
cd config/dev/cab-standalone
./docker-compose.sh
```
> **_NOTE:_** You will see the word cab (Cockpit Assistant Bidirectionnel) on most files in the project. Note that it was the initial project name of InteractiveAI. Might be updated later.

3. Setting up Keycloak `Frontend URL`
* **Access Keycloak Interface**:
3. **Setting up Keycloak `Frontend URL`**
* Access Keycloak Interface:
- Ensure that your Keycloak instance is running and accessible.
- Open a web browser and navigate to the Keycloak admin console, typically available at `http://localhost:89/auth/admin`.
* **Login to Keycloak Admin Console**:
* Login to Keycloak Admin Console:
- Log in to the Keycloak admin console using your administrator credentials (`admin:admin` by default)
* **Navigate to Client Settings**:
* Configure frontendUrl:
- On the Keycloak admin console, locate and click on the "Realm Settings" section.
- In the Frontend URL setting, add the URL of your Assistant Platform frontend as a valid redirect URI. This URL is typically where your frontend application is hosted. For example, if your frontend is hosted locally for development purposes, you might add `http://localhost:3200/*`.
- After adding the frontend URL, save the changes to update the client settings.
* Configure Valid Redirect URIs:
- On the Keycloak admin console, locate and click on the "Clients" section.
- Select the client representing your Assistant Platform application.
* **Configure FrontendUrl**:
- Within the client settings, look for the "Valid Redirect URIs" or similar configuration field.
- Add the URL of your Assistant Platform frontend as a valid redirect URI. This URL is typically where your frontend application is hosted. For example, if your frontend is hosted locally for development purposes, you might add `http://localhost:3200/*`.
- Ensure that the frontend URL you specify matches the actual URL where your frontend application is accessible.
* **Save Changes**:
- After adding the frontend URL, save the changes to update the client settings.
- Add the URL of your Assistant Platform frontend, it should match the one used in the frontendUrl setting.
- After adding the Valid Redirect URIs, save the changes to update the client settings.


4. **Load resources**

4. Load resources
**WARINING:** You need to restart the frontend after updating the URL on keycloak do it before loading the resources.
**WARNING:** You need to restart the frontend after updating the URL on keycloak do it before loading the resources.
```sh
docker restart frontend
```
Expand Down
2 changes: 1 addition & 1 deletion config/dev/cab-standalone/docker-compose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ fi
echo "HOST_IP=${HOST_IP}" >> .env

cat .env
docker-compose up -d
docker compose up -d
2 changes: 1 addition & 1 deletion config/dev/cab-standalone/nginx-cors-permissive.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# docker-compose DNS used to resolved keycloak services
# docker compose DNS used to resolved keycloak services
resolver 127.0.0.11 ipv6=off;
server {
listen 80;
Expand Down
2 changes: 1 addition & 1 deletion config/dev/cab-standalone/nginx-kubernetes.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# docker-compose DNS used to resolved users service
# docker compose DNS used to resolved users service
# resolver 127.0.0.11 ipv6=off;

# Log format to have msec in time + request processing time
Expand Down
2 changes: 1 addition & 1 deletion config/dev/cab-standalone/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# docker-compose DNS used to resolved users service
# docker compose DNS used to resolved users service
resolver 127.0.0.11 ipv6=off;

# Log format to have msec in time + request processing time
Expand Down
2 changes: 1 addition & 1 deletion config/dev/cab-standalone/stopOpfab.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

docker-compose down -v
docker compose down -v
2 changes: 1 addition & 1 deletion config/dev/recommendation-service/docker-compose.bash
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ fi

echo "HOST_IP=${HOST_IP}" > .env

docker-compose -f "docker-compose-recommendation-service.yml" up --build
docker compose -f "docker-compose-recommendation-service.yml" up --build
2 changes: 1 addition & 1 deletion config/dev/recommendation-service/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# docker-compose DNS used to resolved keycloak services
# docker compose DNS used to resolved keycloak services
resolver 127.0.0.11 ipv6=off;
server {
listen 80;
Expand Down
2 changes: 1 addition & 1 deletion docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ The .env should contain:
HOST_IP=<IP_Address>
```

If the IP_Address is not your network IP address, please set it manually and run the system using native docker-compose commands.
If the IP_Address is not your network IP address, please set it manually and run the system using native docker compose commands.

> **_NOTE:_** You are welcome to contribute with any issue that you encounter during setup.
2 changes: 1 addition & 1 deletion resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pip install -r requirements-app.txt
3. For the Docker environment, use the provided docker-compose.yml and Dockerfile.
```
cd PowerGrid
docker-compose up -d --build
docker compose up -d --build
```

# 2 Run the simulator
Expand Down
4 changes: 2 additions & 2 deletions usecases_examples/PowerGrid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pip install -r requirements-consol.txt
2. Launch the InteractiveAI event listener server, compatible with the console simulator.

```commandline
docker-compose up -d --build api
docker compose up -d --build api
```

The launched API might be accessible at this address:
Expand All @@ -88,7 +88,7 @@ pip install -r requirements-app.txt
**2. Launch the whole all in one web app simulator through docker on a computer (BEST APPROACH):**

```commandline
docker-compose up -d --build app
docker compose up -d --build app
```

The launched app might be accessible at this address:
Expand Down
29 changes: 28 additions & 1 deletion usecases_examples/PowerGrid/app/models/Communicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ def send_event_online(self,
duration=None,
case_overload=False,
case_assist_alarm=False,
case_assist_alert=False,
case_anticip=False,
case_line_lost=False):
"""
Expand Down Expand Up @@ -396,7 +397,7 @@ def send_event_online(self,
payload_dict = {}
payload_dict = {
"criticality": "MEDIUM",
"title": "Alerte Agent IA",
"title": "Alarme Agent IA",
"description": f"Soyez vigilant sur la zone {zone}",
"start_date": f"{context_date}",
"end_date": f"{context_date + timedelta(minutes=float(5))}",
Expand All @@ -417,6 +418,32 @@ def send_event_online(self,
except Exception as e:
logging.error(e)

if ("Assistant raised an alert" in current_issues) and case_assist_alert:
try:
self.payload = {}
payload_dict = {}
payload_dict = {
"criticality": "MEDIUM",
"title": "Alerte Agent IA",
"description": f"Risque sur les lignes : {line}",
"start_date": f"{context_date}",
"end_date": f"{context_date + timedelta(minutes=float(5))}",
"data": {
"event_type": "agent",
"line": line,
"kpis": kpis,
"event_context": img_b64
},
"use_case": "PowerGrid",
"is_active": False
}

payload = json.dumps(payload_dict)
# print(f"Assistant alarm description: {payload}")
self.send_payload_and_store_it(payload, obs, scn_first_step)
except Exception as e:
logging.error(e)

if ("Anticipation N-1" in current_issues) and case_anticip:
anticip_date = context_date + timedelta(minutes=float(5*duration))
try:
Expand Down
28 changes: 22 additions & 6 deletions usecases_examples/PowerGrid/app/models/Listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def _stop_if_line_disconnected(self, obs):
return True
return False

def _stop_if_alarm(self, obs):
def _stop_if_alarm(self, obs, otherwise__=""):
"""
Checks if an alarm has been triggered by the assistant.

Expand All @@ -78,11 +78,24 @@ def _stop_if_alarm(self, obs):
Returns:
bool: True if an alarm has been triggered, False otherwise.
"""
do_stop_if_alarm = True
if do_stop_if_alarm:
if np.any(obs.time_since_last_alarm == 0):
logging.info("Assistant raised an alarm")
return True
if np.any(obs.time_since_last_alarm == 0):
logging.info("Assistant raised an alarm")
return True
return False

def _stop_if_alert(self, obs, otherwise__=""):
"""
Checks if an alert has been triggered by the assistant.

Args:
obs: The current observation of the network.

Returns:
bool: True if an alert has been triggered, False otherwise.
"""
if np.any(obs.time_since_last_alert == 0):
logging.info("Assistant raised an alert")
return True
return False

def _stop_if_anticipation_security_analysis(self, obs, env, contingency_line_ids):
Expand Down Expand Up @@ -136,6 +149,9 @@ def _stop_if_issue(self, obs, f_obs, f_env, contingency_line_ids):
if self._stop_if_alarm(obs):
issues.append("Assistant raised an alarm")

if self._stop_if_alert(obs):
issues.append("Assistant raised an alert")

if self._stop_if_bad_kpi(obs):
issues.append("Overload")

Expand Down
48 changes: 41 additions & 7 deletions usecases_examples/PowerGrid/app/models/Simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
matplotlib.use('agg')
from app.models.Listener import Listener
from config.config import logging, set_pause, get_pause_status
from app.models.utils import (create_observation_image, search_chronic_num_from_name,
from app.models.utils import (create_observation_image, get_alert_lines, search_chronic_num_from_name,
get_curent_lines_in_bad_kpi, get_curent_lines_lost,
get_zone_where_alarm_occured, expand_act_from_cab,
load_assistant, local_xd_silly, targeted_scenario_act_fixed, generate_graph_html)
Expand Down Expand Up @@ -294,8 +294,8 @@ def run_simulator(self, com):
line_name=get_curent_lines_in_bad_kpi(
self.obs),
case_overload=True)
if (self.obs.current_step < self.config['scenario_first_step']) or \
(com.cab_api_on is False):
if (self.obs.current_step < self.config['scenario_first_step']) or (com.cab_api_on is False):
# Utiliser XD_Silly en cache (en local)
act = local_xd_silly(self.obs, self.local_assistant)
if com.cab_api_on is False:
Expand Down Expand Up @@ -336,7 +336,7 @@ def run_simulator(self, com):
img_b64_current)
context_just_sent = True

logging.info("Status: Il y a une alerte de l'agent IA")
logging.info("Status: Il y a une alarme de l'agent IA")
yield (
"data: {\"div\": \"events-div\", \"content\": "
"{ \"title\": \"Status: Il y a une alerte de l'agent IA\", "
Expand All @@ -348,8 +348,7 @@ def run_simulator(self, com):
self.obs)
com.send_event_online(context_date,
self.config['scenario_first_step'],
self.listen.trigger_kpis(
self.obs, act),
self.listen.trigger_kpis(self.obs, act),
self.obs,
self.listen.current_issues,
img_b64_current,
Expand All @@ -358,6 +357,41 @@ def run_simulator(self, com):
case_assist_alarm=True)
event_resolved_trigger = True

if "Assistant raised an alert" in self.listen.current_issues:
if self.obs.current_step >= self.config['scenario_first_step']:

com.push_step = self.obs.current_step + send_tempo
if com.cab_api_on is True and context_just_sent is False:
if not img_b64_current:
img_b64_current = create_observation_image(self.env,
self.obs)
if img_b64_current:
com.send_context_online(self.obs,
self.config['scenario_first_step'],
context_date,
img_b64_current)
context_just_sent = True

logging.info("Status: Il y a une alerte de l'agent IA")
yield (
"data: {\"div\": \"events-div\", \"content\": "
"{ \"title\": \"Status: Il y a une alerte de l'agent IA\", "
"\"description\": \"\" } }\n\n"
)

if not img_b64_current:
img_b64_current = create_observation_image(self.env,
self.obs)
com.send_event_online(context_date,
self.config['scenario_first_step'],
self.listen.trigger_kpis(self.obs, act),
self.obs,
self.listen.current_issues,
img_b64_current,
line=get_alert_lines(self.obs),
case_assist_alert=True)
event_resolved_trigger = True

if "Anticipation N-1" in self.listen.current_issues:
if self.obs.current_step >= self.config['scenario_first_step']:
com.push_step = self.obs.current_step + send_tempo
Expand Down Expand Up @@ -440,7 +474,7 @@ def run_simulator(self, com):
get_curent_lines_lost(self.obs))

logging.info(
"Status: Il y a un événement de type 'anticipation N-1' ")
"Status: Il y a un événement de type 'perte de ligne' ")
message = {
"div": "message-container",
"content": "Status: Il y a une perte de ligne ' "
Expand Down
16 changes: 16 additions & 0 deletions usecases_examples/PowerGrid/app/models/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,25 @@ def get_curent_lines_lost(obs):
res = (obs.line_status is False).tolist().index(True)
return get_formatted_name_line(obs, res)


def get_alert_lines(obs):
"""
Identifies the lines where an alert occured.

Args:
obs: The current observation.

Returns:
str: Name of the first lost line in the following format: {line_or_to_subid}:{line_ex_to_subid}:{name_line}.
"""
idx_list = np.where(obs.active_alert)[0]
return [get_formatted_name_line(obs, idx) for idx in idx_list]


def get_formatted_name_line(obs, idx):
return f"{obs.line_or_to_subid[idx]}:{obs.line_ex_to_subid[idx]}:{obs.name_line[idx]}"


def get_zone_where_alarm_occured(obs):
"""
Determines the cardinal zone of the grid where the event occurred.
Expand Down