diff --git a/backend/event-service/resources/Railway/event_manager.py b/backend/event-service/resources/Railway/event_manager.py index 1fac9b5c..eab32eb3 100644 --- a/backend/event-service/resources/Railway/event_manager.py +++ b/backend/event-service/resources/Railway/event_manager.py @@ -10,6 +10,7 @@ def __init__(self): # Optional: Customize Event Uniqueness def get_unique_fields(self, data): + id_event = data["data"].get("id_event") id_train = data["data"].get("id_train") event_type = data["data"].get("event_type") - return {"id_train": id_train, "event_type": event_type} + return {"id_event": id_event, "id_train": id_train, "event_type": event_type} diff --git a/backend/event-service/resources/Railway/schemas.py b/backend/event-service/resources/Railway/schemas.py index f088708a..51951c8a 100644 --- a/backend/event-service/resources/Railway/schemas.py +++ b/backend/event-service/resources/Railway/schemas.py @@ -7,6 +7,7 @@ class MetadataSchemaRailway(MetadataSchema): agent_id = String(allow_none=True, required=True) event_type = String(required=True) + id_event = String(allow_none=True, required=True) agent_position = List( Integer(allow_none=True), allow_none=True, default=None ) diff --git a/backend/recommendation-service/resources/Railway/manager.py b/backend/recommendation-service/resources/Railway/manager.py index 881de5c0..cf44bc54 100644 --- a/backend/recommendation-service/resources/Railway/manager.py +++ b/backend/recommendation-service/resources/Railway/manager.py @@ -1,25 +1,47 @@ # backend/recommendation-service/resources/Railway/manager.py - +import json from api.manager.base_manager import BaseRecommendationManager +from .mockRecommendations.mockRecommendations import RECOMMENDATION_CATALOG + +import logging + +logger = logging.getLogger(__name__) class RailwayManager(BaseRecommendationManager): def __init__(self): super().__init__() + def _transform_recommendation(self, reco_json): + """Transform a recommendation from catalog to output format.""" + reco = json.loads(reco_json) + return { + "title": reco["data"]["title"], + "description": reco["data"]["description"], + "use_case": reco["data"]["use_case"], + "agent_type": reco["data"]["agent_type"], + "actions": [{}], + "kpis": reco["data"]["kpis"], + } + def get_recommendation(self, request_data): """ - Override to provide recommendations specific to the RTE use case. + Override to provide recommendations specific to the Railway use case. - This method generates and returns recommendations tailored for RTE. + This method generates and returns recommendations tailored for Railway events. """ - action_dict = {} - - output_json = { - "title": "recommendation", - "description": "description", - "use_case": "Railway", - "agent_type": "agent_type", - "actions": [action_dict], - } + event_data = request_data.get("event", {}) + event_id = str(event_data.get("id_event", "1")) + + logger.info(f"Processing event_id: {event_id}") + + # Get recommendations from catalog + if event_id == "1": + recommendations = RECOMMENDATION_CATALOG.get(event_id, RECOMMENDATION_CATALOG["1"]) + elif event_id == "2": + recommendations = RECOMMENDATION_CATALOG.get(event_id, RECOMMENDATION_CATALOG["1"]) + elif event_id == "3": + recommendations = RECOMMENDATION_CATALOG.get(event_id, RECOMMENDATION_CATALOG["1"]) + # Transform each recommendation to output format + return [self._transform_recommendation(reco) for reco in recommendations] + - return [output_json] \ No newline at end of file diff --git a/backend/recommendation-service/resources/Railway/mockRecommendations/mockRecommendations.py b/backend/recommendation-service/resources/Railway/mockRecommendations/mockRecommendations.py new file mode 100644 index 00000000..fbccba57 --- /dev/null +++ b/backend/recommendation-service/resources/Railway/mockRecommendations/mockRecommendations.py @@ -0,0 +1,249 @@ + +import json + + +################################################################ +######### Event 1 Recommendations ############################## +################################################################ + +Reco11 = json.dumps({ +"data": { + "title": "Stay at the St Pierre des Corps station", + "description" : "Hold the train in the St Pierre des Corps station until traffic resumes.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "78", + "nb_impacted_passengers" : "468", + "total_cost" : "32838", + "delay" : "1h30", + "best": "True", + } + + } +} +) + +Reco12 = json.dumps({ +"data": { + "title": "Delay", + "description" : "Hold the train on the track until traffic resumes. Arrangements for passenger support.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "87", + "nb_impacted_passengers" : "398", + "total_cost" : "34626", + "delay" : "1h30", + "best": "False", + } + + } +} +) + + +Reco13 = json.dumps({ +"data": { + "title": "Reroute", + "description" : "Reroute the trains. Passengers traveling to and from Poitiers are being assisted.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "102", + "nb_impacted_passengers" : "350", + "total_cost" : "35700", + "delay" : "1h40", + "best": "False", + } + + } +} +) + + +Reco14 = json.dumps({ +"data": { + "title": "Cancel", + "description" : "Train services are cancelled. Passengers are advised to postpone their journey.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "233", + "nb_impacted_passengers" : "468", + "total_cost" : "109044", + "delay" : "1h45", + "best": "False", + } + + } +} +) + +################################################################ +######### Event 2 Recommendations ############################## +################################################################ + + +Reco21 = json.dumps({ +"data": { + "title": "Stay at the station", + "description" : "Hold the train in the Poitiers station until traffic resumes.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "123", + "nb_impacted_passengers" : "459", + "total_cost" : "56457", + "delay" : "1h05", + "best": "False", + } + + } +} +) + + + + + +Reco22 = json.dumps({ +"data": { + "title": "Delay", + "description" : "Hold the train on the track until traffic resumes. Arrangements for passenger support.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "129", + "nb_impacted_passengers" : "459", + "total_cost" : "59211", + "delay" : "1h00", + "best": "False", + } + + } +} +) + +Reco23 = json.dumps({ +"data": { + "title": "Reroute", + "description" : "Diversion and rerouting of trains to the standard line between Angoulême and Poitiers. ", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "73", + "nb_impacted_passengers" : "459", + "total_cost" : "33507", + "delay" : "1h00", + "best": "True", + } + + } +} +) + + +Reco24 = json.dumps({ +"data": { + "title": "Cancel", + "description" : "Train services are cancelled. Passengers are advised to postpone their journey.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "221", + "nb_impacted_passengers" : "459", + "total_cost" : "101439", + "delay" : "1h00", + "best": "False", + } + + } +} +) + + +################################################################ +######### Event 3 Recommendations ############################## +################################################################ + +Reco31 = json.dumps({ +"data": { + "title": "Wait at the Bordeaux station", + "description" : "Hold the train at the station until traffic resumes.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "94", + "nb_impacted_passengers" : "348", + "total_cost" : "32712", + "delay" : "00h30", + "best": "True", + } + + } +} +) + + +Reco32 = json.dumps({ +"data": { + "title": "Delay", + "description" : "Hold the train on the track until traffic resumes. Arrangements for passenger support.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "94", + "nb_impacted_passengers" : "347", + "total_cost" : "32618", + "delay" : "00h53", + "best": "False", + } + + } +} +) + + +Reco33 = json.dumps({ +"data": { + "title": "Rerouting from Bordeaux to Angoulême ", + "description" : "Diversion and rerouting of trains to the standard line between Angoulême and Poitiers.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "95", + "nb_impacted_passengers" : "348", + "total_cost" : "33060", + "delay" : "1h24", + "best": "False", + } + + } +} +) + +Reco34 = json.dumps({ +"data": { + "title": "Supprimer", + "description" : "Train services are cancelled. Passengers are advised to postpone their journey.", + "use_case": "Railway", + "agent_type": "AI", + "kpis": { + "cost" : "257", + "nb_impacted_passengers" : "348", + "total_cost" : "89436", + "delay" : "00h40", + "best": "False", + } + + } +} +) + + +RECOMMENDATION_CATALOG = { + "1": [Reco11, Reco12, Reco13, Reco14], + "2": [Reco21, Reco22, Reco23, Reco24], + "3": [Reco31, Reco32, Reco33, Reco34], +} diff --git a/frontend/src/api/services.ts b/frontend/src/api/services.ts index d972a740..1a6cfa91 100644 --- a/frontend/src/api/services.ts +++ b/frontend/src/api/services.ts @@ -1,10 +1,12 @@ import http from '@/plugins/http' import { useAppStore } from '@/stores/app' +import { useCardsStore } from '@/stores/cards' import { useServicesStore } from '@/stores/services' import type { Card } from '@/types/cards' import type { Action, Context, Entity } from '@/types/entities' import type { Procedure } from '@/types/procedure' import type { FullContext, Recommendation, Trace } from '@/types/services' +import { recordTraceForSession } from '@/utils/traceSessionExport' export function getRecommendation(payload: { event: Card['data']['metadata'] @@ -18,10 +20,31 @@ export function getContext() { } export function sendTrace(payload: Trace) { - return http.post>('/cabhistoric/api/v1/traces', { + if (payload.step === 'ASKFORHELP' && typeof payload.data === 'object' && payload.data !== null) { + const cardId = (payload.data as { id?: unknown }).id + if (typeof cardId === 'string') { + const cardsStore = useCardsStore() + const card = cardsStore._cards.find((item) => item.id === cardId) + recordTraceForSession({ + use_case: payload.use_case, + step: 'EVENT', + data: { + card_id: cardId, + process_instance_id: card?.processInstanceId, + start_date: card?.startDate ? new Date(card.startDate).toISOString() : undefined, + metadata: card?.data.metadata + }, + date: new Date().toISOString() + }) + } + } + + const tracePayload = { ...payload, date: new Date().toISOString() - }) + } + recordTraceForSession(tracePayload) + return http.post>('/cabhistoric/api/v1/traces', tracePayload) } export function applyRecommendation(data: Action) { diff --git a/frontend/src/components/organisms/CAB/Assistant/Recommendations.vue b/frontend/src/components/organisms/CAB/Assistant/Recommendations.vue index 5b9aff79..a499c8a7 100644 --- a/frontend/src/components/organisms/CAB/Assistant/Recommendations.vue +++ b/frontend/src/components/organisms/CAB/Assistant/Recommendations.vue @@ -71,6 +71,7 @@ import Card from '@/components/atoms/Card.vue' import Modal from '@/components/atoms/Modal.vue' import type { Entity } from '@/types/entities' import type { Recommendation } from '@/types/services' +import { recordTraceForSession } from '@/utils/traceSessionExport' const props = withDefaults( defineProps<{ buttons: string[]; recommendations: Recommendation[]; collapsed?: boolean }>(), @@ -93,6 +94,15 @@ onBeforeMount(() => { async function close(success: boolean) { if (success) { + recordTraceForSession({ + use_case: selected.value!.use_case, + step: 'FEEDBACK', + date: new Date().toISOString(), + data: { + action: 'confirm_recommendation', + recommendation: selected.value!.title + } + }) await sendFeedback(selected.value!, true) emit('selected', selected.value!) } @@ -100,10 +110,31 @@ async function close(success: boolean) { } function closeKpi(kpi: (typeof props)['buttons'][number]) { + const useCase = selected.value?.use_case ?? props.recommendations[0]?.use_case + if (useCase) + recordTraceForSession({ + use_case: useCase, + step: 'FEEDBACK', + date: new Date().toISOString(), + data: { + action: 'dismiss_kpi', + kpi, + selected_recommendation: selected.value?.title + } + }) buttons.value?.splice(buttons.value.indexOf(kpi), 1) } async function downvote(recommendation: Recommendation) { + recordTraceForSession({ + use_case: recommendation.use_case, + step: 'FEEDBACK', + date: new Date().toISOString(), + data: { + action: 'downvote_recommendation', + recommendation: recommendation.title + } + }) await sendFeedback(recommendation) emit( 'update:recommendations', diff --git a/frontend/src/entities/Railway/CAB/Assistant.vue b/frontend/src/entities/Railway/CAB/Assistant.vue index 82d4f777..b00d5649 100644 --- a/frontend/src/entities/Railway/CAB/Assistant.vue +++ b/frontend/src/entities/Railway/CAB/Assistant.vue @@ -5,19 +5,12 @@ - + + :primary-action="primaryAction">