diff --git a/README.md b/README.md index 5cab8a5e..fe622d82 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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 ``` diff --git a/config/dev/cab-standalone/docker-compose.sh b/config/dev/cab-standalone/docker-compose.sh index 281eeeec..bac36288 100644 --- a/config/dev/cab-standalone/docker-compose.sh +++ b/config/dev/cab-standalone/docker-compose.sh @@ -44,4 +44,4 @@ fi echo "HOST_IP=${HOST_IP}" >> .env cat .env -docker-compose up -d +docker compose up -d diff --git a/config/dev/cab-standalone/nginx-cors-permissive.conf b/config/dev/cab-standalone/nginx-cors-permissive.conf index 3a6979ac..87c4e5f5 100644 --- a/config/dev/cab-standalone/nginx-cors-permissive.conf +++ b/config/dev/cab-standalone/nginx-cors-permissive.conf @@ -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; diff --git a/config/dev/cab-standalone/nginx-kubernetes.conf b/config/dev/cab-standalone/nginx-kubernetes.conf index 0a47b524..3a331ce7 100644 --- a/config/dev/cab-standalone/nginx-kubernetes.conf +++ b/config/dev/cab-standalone/nginx-kubernetes.conf @@ -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 diff --git a/config/dev/cab-standalone/nginx.conf b/config/dev/cab-standalone/nginx.conf index bf718b7e..2631ed6b 100644 --- a/config/dev/cab-standalone/nginx.conf +++ b/config/dev/cab-standalone/nginx.conf @@ -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 diff --git a/config/dev/cab-standalone/stopOpfab.sh b/config/dev/cab-standalone/stopOpfab.sh index 354ca1f4..43ca918a 100644 --- a/config/dev/cab-standalone/stopOpfab.sh +++ b/config/dev/cab-standalone/stopOpfab.sh @@ -1,3 +1,3 @@ #!/bin/bash -docker-compose down -v \ No newline at end of file +docker compose down -v \ No newline at end of file diff --git a/config/dev/recommendation-service/docker-compose.bash b/config/dev/recommendation-service/docker-compose.bash index 1a4adccf..72e6e5db 100644 --- a/config/dev/recommendation-service/docker-compose.bash +++ b/config/dev/recommendation-service/docker-compose.bash @@ -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 diff --git a/config/dev/recommendation-service/nginx.conf b/config/dev/recommendation-service/nginx.conf index 5cbc5a0c..b9ad9014 100644 --- a/config/dev/recommendation-service/nginx.conf +++ b/config/dev/recommendation-service/nginx.conf @@ -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; diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 142a6d68..86a0f403 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -18,6 +18,6 @@ The .env should contain: HOST_IP= ``` -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. \ No newline at end of file diff --git a/resources/README.md b/resources/README.md index a1bbec0f..57e2b630 100644 --- a/resources/README.md +++ b/resources/README.md @@ -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 diff --git a/usecases_examples/PowerGrid/README.md b/usecases_examples/PowerGrid/README.md index fb782d0f..2355bbee 100644 --- a/usecases_examples/PowerGrid/README.md +++ b/usecases_examples/PowerGrid/README.md @@ -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: @@ -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: diff --git a/usecases_examples/PowerGrid/app/models/Communicate.py b/usecases_examples/PowerGrid/app/models/Communicate.py index f98c8c96..cb3e18ab 100644 --- a/usecases_examples/PowerGrid/app/models/Communicate.py +++ b/usecases_examples/PowerGrid/app/models/Communicate.py @@ -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): """ @@ -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))}", @@ -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: diff --git a/usecases_examples/PowerGrid/app/models/Listener.py b/usecases_examples/PowerGrid/app/models/Listener.py index f5ba0fd9..6e35e0cc 100644 --- a/usecases_examples/PowerGrid/app/models/Listener.py +++ b/usecases_examples/PowerGrid/app/models/Listener.py @@ -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. @@ -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): @@ -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") diff --git a/usecases_examples/PowerGrid/app/models/Simulator.py b/usecases_examples/PowerGrid/app/models/Simulator.py index 6ea7716d..9d6683ad 100644 --- a/usecases_examples/PowerGrid/app/models/Simulator.py +++ b/usecases_examples/PowerGrid/app/models/Simulator.py @@ -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) @@ -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: @@ -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\", " @@ -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, @@ -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 @@ -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 ' " diff --git a/usecases_examples/PowerGrid/app/models/utils.py b/usecases_examples/PowerGrid/app/models/utils.py index 1af3638d..b1a2202b 100644 --- a/usecases_examples/PowerGrid/app/models/utils.py +++ b/usecases_examples/PowerGrid/app/models/utils.py @@ -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.