diff --git a/rssched/app/pages/01_runinfo.py b/rssched/app/pages/01_runinfo.py new file mode 100644 index 0000000..6bc5179 --- /dev/null +++ b/rssched/app/pages/01_runinfo.py @@ -0,0 +1,43 @@ +import pandas as pd +import streamlit as st + +from rssched.app.utils.io import get_uploaded_data + +st.title(f"Run Info") + +# Retrieve uploaded request and response data +request, response, instance_name = get_uploaded_data() + + +request_summary_df = pd.DataFrame( + { + "Locations": [len(request.vehicle_types)], + "Vehicle types": [len(request.vehicle_types)], + "Depots": [len(request.depots)], + "Depot capacity": [sum(depot.capacity for depot in request.depots)], + "Routes": [len(request.routes)], + "Departures": [len(request.departures)], + "Maintenance tracks": [ + sum(slot.track_count for slot in request.maintenance_slots) + ], + } +) + +response_summary_df = pd.DataFrame( + { + "Unserved passengers": [response.objective_value.unserved_passengers], + "Maintenance violoation": [response.objective_value.maintenance_violation], + "Vehicle count": [response.objective_value.vehicle_count], + "Costs": [response.objective_value.costs], + } +) + +st.write(f"**Instance:** {instance_name}") + +st.subheader("Request Summary") +st.write(f"**File:** {st.session_state.request_file.name}") +st.dataframe(request_summary_df, hide_index=True) + +st.subheader("Response Summary") +st.write(f"**File:** {st.session_state.response_file.name}") +st.dataframe(response_summary_df, hide_index=True) diff --git a/rssched/app/pages/02_schedule.py b/rssched/app/pages/02_schedule.py new file mode 100644 index 0000000..4a5e4bc --- /dev/null +++ b/rssched/app/pages/02_schedule.py @@ -0,0 +1,43 @@ +import streamlit as st + +from rssched.app.utils.io import get_uploaded_data +from rssched.visualization.vehicle_type_gantt import plot_gantt_per_vehicle_type + +st.title("Schedule") + +request, response, instance_name = get_uploaded_data() + +plots = plot_gantt_per_vehicle_type(response, instance_name) + +tabs = st.tabs(plots.keys()) + +for tab, (title, fig) in zip(tabs, plots.items()): + with tab: + st.plotly_chart(fig) + +""" +from rssched.visualization.vehicle_utilization import plot_vehicle_utilization +from rssched.visualization.fleet_efficiency import plot_fleet_efficiency +from rssched.visualization.active_events import plot_active_events_over_time + +tabs = st.tabs( + [ + "Gantt Chart", + "Active Events", + "Vehicle Utilization", + "Fleet Efficiency", + ] +) + +with tabs[0]: + st.plotly_chart(plot_gantt_per_vehicle_type(response, instance_name)[0]) + +with tabs[1]: + st.plotly_chart(plot_active_events_over_time(response, instance_name)) + +with tabs[2]: + st.plotly_chart(plot_vehicle_utilization(response, instance_name)) + +with tabs[3]: + st.plotly_chart(plot_fleet_efficiency(response, instance_name)) +""" diff --git a/rssched/app/pages/03_fleet.py b/rssched/app/pages/03_fleet.py new file mode 100644 index 0000000..e4abb9b --- /dev/null +++ b/rssched/app/pages/03_fleet.py @@ -0,0 +1,22 @@ +import streamlit as st + +from rssched.app.utils.io import get_uploaded_data +from rssched.visualization.active_events import plot_active_events_over_time +from rssched.visualization.fleet_efficiency import plot_fleet_efficiency +from rssched.visualization.vehicle_utilization import plot_vehicle_utilization + +st.title("Fleet") + +request, response, instance_name = get_uploaded_data() + + +tabs = st.tabs(["Active Events", "Vehicle Utilization", "Efficiency"]) + +with tabs[0]: + st.plotly_chart(plot_active_events_over_time(response, instance_name)) + +with tabs[1]: + st.plotly_chart(plot_vehicle_utilization(response, instance_name)) + +with tabs[2]: + st.plotly_chart(plot_fleet_efficiency(response, instance_name)) diff --git a/rssched/app/pages/04_depots.py b/rssched/app/pages/04_depots.py new file mode 100644 index 0000000..c14be4f --- /dev/null +++ b/rssched/app/pages/04_depots.py @@ -0,0 +1,7 @@ +import streamlit as st + +from rssched.app.utils.io import get_uploaded_data + +st.title("Depots") + +request, response, instance_name = get_uploaded_data() diff --git a/rssched/app/upload.py b/rssched/app/upload.py new file mode 100644 index 0000000..1c19eb2 --- /dev/null +++ b/rssched/app/upload.py @@ -0,0 +1,57 @@ +import streamlit as st + +from rssched.app.utils.io import import_request, import_response + +st.title("RSSched Analyzer") +st.markdown( + """ +### About +This page provides an aggregated summary of the request data uploaded by the user. +You can see important statistics such as the number of locations, vehicle types, depots, +routes, and other relevant metrics. Additionally, an objective value from the response is also displayed below. +""" +) + +st.subheader("Upload Data") + +# Session state to store file uploads +if "request_file" not in st.session_state: + st.session_state.request_file = None +if "response_file" not in st.session_state: + st.session_state.response_file = None + +# Upload Request and Response Files +request_file = st.file_uploader( + "Set request file (JSON)", type=["json"], key="request_uploader" +) +response_file = st.file_uploader( + "Set response file (JSON)", type=["json"], key="response_uploader" +) + +# If both files are uploaded, process them +if request_file is not None and response_file is not None: + request_instance_name = ".".join(request_file.name.split(".")[:2]) + response_instance_name = ".".join(response_file.name.split(".")[:2]) + + if request_instance_name != response_instance_name: + st.warning( + f"Instance of request '{request_instance_name}' and response '{response_instance_name}' not the same." + ) + + st.session_state.rssched_instance_name = request_instance_name + st.session_state.request_file = request_file + st.session_state.response_file = response_file + st.session_state.instance_name = response_file + st.session_state.request_data = import_request(st.session_state.request_file) + st.session_state.response_data = import_response(st.session_state.response_file) + + st.success("Files uploaded and processed successfully!") + + +# Reset button to clear the uploaded files and session state +if st.button("Reset"): + st.session_state.request_file = None + st.session_state.response_file = None + st.session_state.request_data = None + st.session_state.response_data = None + st.rerun() diff --git a/rssched/app/utils/io.py b/rssched/app/utils/io.py new file mode 100644 index 0000000..38b8062 --- /dev/null +++ b/rssched/app/utils/io.py @@ -0,0 +1,33 @@ +import json +from typing import Tuple + +import streamlit as st + +from rssched.io.reader import convert_keys_to_snake_case +from rssched.model.request import Request +from rssched.model.response import Info, ObjectiveValue, Response, Schedule + + +def import_request(file_obj) -> Request: + data = convert_keys_to_snake_case(json.load(file_obj)) + return Request(**data) + + +def import_response(file_obj) -> Response: + data = convert_keys_to_snake_case(json.load(file_obj)) + + info = Info(**data["info"]) + objective_value = ObjectiveValue(**data["objective_value"]) + schedule = Schedule(**data["schedule"]) + + return Response(info=info, objective_value=objective_value, schedule=schedule) + + +def get_uploaded_data() -> Tuple[Request, Response, str]: + if st.session_state.get("request_data") and st.session_state.get("response_data"): + request_data = st.session_state["request_data"] + response_data = st.session_state["response_data"] + return request_data, response_data, st.session_state["rssched_instance_name"] + else: + st.warning("Please upload both request and response files on the main page.") + st.stop()