Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d02f8f
add multiple airplanes to schema context and types
abdratsd Mar 14, 2025
9ce57b4
Minor changes:
ahmerique May 27, 2025
ca35f9d
Issue #2: Improve line name readability by formatting the displayed l…
ahmerique Jul 1, 2025
f846f8e
Issue #5: Send grid state after line disconnection in Anticipation cards
ahmerique Jul 3, 2025
9ca70f5
Merge pull request #2 from ahmerique/fix/5-2-and-minors
abdratsd Jul 29, 2025
05d414f
resources/checkPorts.sh to check if all needed ports are available
obretteville Jun 20, 2025
5e448a7
Troubleshooting documentation updated
obretteville Jun 20, 2025
8a7fcff
Update scripts to use docker compose V2 instead of old docker-compose
ahmerique Jul 28, 2025
9f316e0
Update scripts to use docker compose V2 instead of old docker-compose
ahmerique Jul 28, 2025
1fbd64e
Handle alert notifications (different than alarm)
ahmerique Sep 25, 2025
1057612
added nocontext var to locales
abdratsd Oct 2, 2025
e6d5e94
plane id is now a string
abdratsd Oct 2, 2025
ff7e0d5
replace old usecase name in filename
abdratsd Oct 2, 2025
d6ddb33
feat(map): add autoFit prop; auto-fit on contextWaypoints only when e…
abdratsd Oct 2, 2025
d775068
fix(ATM): clear previous ROUTE markers before rebuild; stop removing …
abdratsd Oct 2, 2025
613ebfc
i18n(railway): change primary button label to “Get recommendation fro…
abdratsd Oct 2, 2025
edd67b1
fix: rename map markers of old usecases
abdratsd Oct 2, 2025
5958797
Merge pull request #9 from AI4REALNET/fix-context-existence-lang
abdratsd Oct 2, 2025
deeb85b
Merge pull request #3 from ahmerique/update-installation
abdratsd Oct 2, 2025
8d09bc1
Merge pull request #7 from ahmerique/feature/alert_notification
abdratsd Oct 2, 2025
7d8e13b
Merge pull request #10 from AI4REALNET/900-multiple-airplanes-context
abdratsd Oct 2, 2025
5840bf0
Update KPI name
MarouaMed Oct 15, 2025
4b96fb7
Update recommendations titles and description in english
MarouaMed Oct 15, 2025
e8690b9
Update Events title and description in english
MarouaMed Oct 15, 2025
529d66a
Update ontology outputs in english
MarouaMed Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* text=auto
*.sh text eol=lf
*.conf text eol=lf
*.bash text eol=lf
*.yml text eol=lf
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
Empty file modified backend/capitalization-service/entrypoint.sh
100755 → 100644
Empty file.
Empty file modified backend/context-service/entrypoint.sh
100755 → 100644
Empty file.
29 changes: 27 additions & 2 deletions backend/context-service/resources/ATM/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,35 @@

from api.schemas import MetadataSchema
from apiflask.fields import Dict, String, Float, List, Integer
class MetadataSchemaATM(MetadataSchema):
from apiflask import Schema, fields
from marshmallow import pre_load
class PlaneMetadataSchemaATM(MetadataSchema):
id_plane = String()
ApDest = Dict()
Current_airspeed = Float()
Latitude = Float()
Longitude = Float()
wpList = List(Dict())
id_plane = Integer()

class MetadataSchemaATM(MetadataSchema):
airplanes = List(fields.Nested(PlaneMetadataSchemaATM), required=True)

# Backward compatibility: optional fields for the single airplane case
ApDest = Dict(required=False)
Current_airspeed = Float(required=False)
Latitude = Float(required=False)
Longitude = Float(required=False)
wpList = List(Dict(), required=False)

@pre_load
def handle_backward_compatibility(self, data, **kwargs):
# If the new 'airplanes' field is not provided, assume the old format.
if 'airplanes' not in data:
airplane = {}
for field in ['ApDest', 'Current_airspeed', 'Latitude', 'Longitude', 'wpList']:
if field in data:
airplane[field] = data[field]
# Provide a default id_plane if not present.
airplane.setdefault('id_plane', "X")
data['airplanes'] = [airplane]
return data
Empty file modified backend/event-service/entrypoint.sh
100755 → 100644
Empty file.
Empty file modified backend/historic-service/entrypoint.sh
100755 → 100644
Empty file.
Empty file modified backend/recommendation-service/entrypoint.sh
100755 → 100644
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def get_parade_info(self, act):
"Redispatch" # pour renvoyer le kpi type_of_the_reco
)
title.append(
"Parade injection: redispatch de source de production"
"Injection recommendation: production source redispatch"
)
cpt = 0
for gen_idx in range(act.n_gen):
Expand All @@ -213,9 +213,9 @@ def get_parade_info(self, act):
# storage
if act._modif_storage:
kpis["type_of_the_reco"] = (
"Stockage" # pour renvoyer le kpi type_of_the_reco
"Storage" # pour renvoyer le kpi type_of_the_reco
)
title.append("Parade stockage")
title.append("Storage recommendation")
cpt = 0
for stor_idx in range(act.n_storage):
amount_ = act._storage_power[stor_idx]
Expand All @@ -225,8 +225,8 @@ def get_parade_info(self, act):
description.append(", ")
cpt = 1
description.append(
f'Demande à l\'unité "{name_}" de '
f'{"charger" if amount_ > 0.0 else "decharger"} '
f'Ask unit "{name_}" to '
f'{"charge" if amount_ > 0.0 else "discharge"} '
f'{abs(amount_):.2f} MW (setpoint: {amount_:.2f} MW)'
)

Expand All @@ -235,7 +235,7 @@ def get_parade_info(self, act):
kpis["type_of_the_reco"] = (
"Injection" # pour renvoyer le kpi type_of_the_reco
)
title.append("Parade injection")
title.append("Injection recommendation")
cpt = 0
for gen_idx in range(act.n_gen):
amount_ = act._curtail[gen_idx]
Expand All @@ -245,71 +245,71 @@ def get_parade_info(self, act):
description.append(", ")
cpt = 1
description.append(
f'Limiter l\'unité "{name_}" à '
f'{100.0 * amount_:.1f}% de sa capacité max '
f'Limit unit "{name_}" to '
f'{100.0 * amount_:.1f}% of its maximum capacity '
f'(setpoint: {amount_:.3f})'
)

# force line status
force_line_impact = impact["force_line"]
if force_line_impact["changed"]:
kpis["type_of_the_reco"] = (
"Topologique" # pour renvoyer le kpi type_of_the_reco
"Topological" # pour renvoyer le kpi type_of_the_reco
)
title.append(
"Parade topologique: connection/deconnection de ligne"
"Topological recommendation: connection/disconnection of line"
)
reconnections = force_line_impact["reconnections"]
if reconnections["count"] > 0:
description.append(
f"Reconnection de {reconnections['count']} lignes "
f"Reconnection of {reconnections['count']} lines "
f"({reconnections['powerlines']})"
)

disconnections = force_line_impact["disconnections"]
if disconnections["count"] > 0:
description.append(
f"Déconnection de {disconnections['count']} lignes "
f"Disconnection of {disconnections['count']} lines "
f"({disconnections['powerlines']})"
)

# swtich line status
swith_line_impact = impact["switch_line"]
if swith_line_impact["changed"]:
kpis["type_of_the_reco"] = (
"Topologique" # pour renvoyer le kpi type_of_the_reco
"Topological" # pour renvoyer le kpi type_of_the_reco
)
title.append("Parade topologique: changer l'état d'une ligne")
title.append("Topological: change a line state")
description.append(
f"Changer le statut de {swith_line_impact['count']} lignes "
f"Change the state of {swith_line_impact['count']} lines "
f"({swith_line_impact['powerlines']})"
)

# topology
bus_switch_impact = impact["topology"]["bus_switch"]
if len(bus_switch_impact) > 0:
kpis["type_of_the_reco"] = (
"Topologique" # pour renvoyer le kpi type_of_the_reco
"Topological" # pour renvoyer le kpi type_of_the_reco
)
title.append(
"Parade topologique: prise de schéma au poste "
"Topological recommendation: Schematic acquisition at substation "
+ str(bus_switch_impact["substation"])
)
description.append("Changement de bus:")
description.append("Busbar change:")
for switch in bus_switch_impact:
description.append(
f"\t \t - Switch bus de {switch['object_type']} id "
f"{switch['object_id']} [au poste {switch['substation']}]"
f"\t \t - Switch bus of {switch['object_type']} id "
f"{switch['object_id']} [at station {switch['substation']}]"
)

assigned_bus_impact = impact["topology"]["assigned_bus"]
disconnect_bus_impact = impact["topology"]["disconnect_bus"]
if len(assigned_bus_impact) > 0 or len(disconnect_bus_impact) > 0:
kpis["type_of_the_reco"] = (
"Topologique" # pour renvoyer le kpi type_of_the_reco
"Topological" # pour renvoyer le kpi type_of_the_reco
)
title.append(
"Parade topologique: prise de schéma au poste "
"Topological recommendation: Schematic acquisition at substation "
+ str(assigned_bus_impact[0]["substation"])
)
if assigned_bus_impact:
Expand All @@ -320,7 +320,7 @@ def get_parade_info(self, act):
description.append(", ")
cpt = 1
description.append(
f" Assigner le bus {assigned['bus']} à "
f" Assign bus {assigned['bus']} to "
f"{assigned['object_type']} id {assigned['object_id']}"
)
if disconnect_bus_impact:
Expand All @@ -331,20 +331,20 @@ def get_parade_info(self, act):
description.append(", ")
cpt = 1
description.append(
f"Déconnecter {disconnected['object_type']} avec l'id "
f"{disconnected['object_id']} [au niveau du poste "
f"Disconnect {disconnected['object_type']} with id "
f"{disconnected['object_id']} [at the substation level "
f"{disconnected['substation']}]"
)

# Any of the above cases,
# then the recommendation is most likely "Do nothing"
if not title and act == self.action_do_nothing:
kpis["type_of_the_reco"] = (
"Ne rien faire" # pour renvoyer le kpi type_of_the_reco
"Do nothing" # pour renvoyer le kpi type_of_the_reco
)
title.append("Poursuivre")
description.append(
"Poursuite du scénario sans intervention extérieur"
"Continuation of the scenario without operator action"
)

title = "".join(title)
Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
10 changes: 5 additions & 5 deletions backend/recommendation-service/resources/PowerGrid/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ def get_onto_recommendation(self, event_line):
"""
# Default output
output_json = {
"title": "Parade ontologique par defaut",
"title": "Default ontological recommendation",
"description": (
"Aucune recommandation n’a pu être générée car cette surcharge "
"n’a jamais été observée dans le passé"
"No recommendation could be generated because this overload "
"has never been observed in the past"
),
"use_case": "PowerGrid",
"agent_type": AgentType.onto.name,
"actions": [{}],
"kpis": {
"type_of_the_reco": "Null",
"type_of_the_reco": "Ontological",
"efficiency_of_the_reco": 1.99999,
},
}
Expand Down Expand Up @@ -198,7 +198,7 @@ def get_onto_recommendation(self, event_line):
)[0][0]
output_json = {
"title": recommendation,
"description": f"Cette parade a été rencontrée {nb_similar_situations} fois dans le passé.",
"description": f"This recommendation has been encountered {nb_similar_situations} times in the past.",
"use_case": "PowerGrid",
"agent_type": AgentType.onto.name,
"actions": [action_dict],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# backend/recommendation-service/resources/RTE/manager.py
# backend/recommendation-service/resources/Railway/manager.py

from api.manager.base_manager import BaseRecommendationManager

Expand Down
Empty file modified backend/recommendation-service/start_service.bash
100755 → 100644
Empty file.
Empty file modified config/dev/cab-keycloak/export/dev-realm.json
100755 → 100644
Empty file.
Empty file modified config/dev/cab-keycloak/export/dev-users-0.json
100755 → 100644
Empty file.
Empty file modified config/dev/cab-standalone/config_host_ip.bash
100755 → 100644
Empty file.
2 changes: 1 addition & 1 deletion config/dev/cab-standalone/docker-compose.sh
100755 → 100644
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
100755 → 100644
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
100755 → 100644
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
Loading