Compare commits
6 Commits
594e7c3cce
...
main
Author | SHA1 | Date | |
---|---|---|---|
e1b817252c | |||
2d73ae8d63 | |||
ab1d541e2c | |||
cc8de33780 | |||
1c41f666dc | |||
d76db631fd |
@ -59,3 +59,17 @@ hr {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.feedback-button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 2px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
2
data/feedback.csv
Normal file
2
data/feedback.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
timestamp,category,comment
|
||||||
|
2025-09-07 20:02:38,groceries,"ABZT please change this air to P1231313"
|
|
5
main.py
5
main.py
@ -1,6 +1,6 @@
|
|||||||
# initial commit
|
# initial commit
|
||||||
from dash import Dash
|
from dash import Dash
|
||||||
from dash_bootstrap_components.themes import BOOTSTRAP
|
import dash_bootstrap_components as dbc
|
||||||
|
|
||||||
from src.components.layout import create_layout
|
from src.components.layout import create_layout
|
||||||
from src.data.loader_gz import load_spc_data
|
from src.data.loader_gz import load_spc_data
|
||||||
@ -17,10 +17,11 @@ with open(config_file) as config_f:
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
print(os.getenv("MY_ENV_VAR"))
|
print(os.getenv("MY_ENV_VAR"))
|
||||||
|
print(config["Startup"])
|
||||||
# load the data and create the data manager
|
# load the data and create the data manager
|
||||||
data = load_spc_data(config["DATA_PATH"])
|
data = load_spc_data(config["DATA_PATH"])
|
||||||
|
|
||||||
app = Dash(external_stylesheets=[BOOTSTRAP])
|
app = Dash(external_stylesheets=[dbc.themes.LUX])
|
||||||
app.title = "Reliability Dashboard"
|
app.title = "Reliability Dashboard"
|
||||||
app.layout = create_layout(app, data)
|
app.layout = create_layout(app, data)
|
||||||
app.run()
|
app.run()
|
||||||
|
@ -7,4 +7,5 @@ requires-python = ">=3.11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"dash>=3.2.0",
|
"dash>=3.2.0",
|
||||||
"dash-bootstrap-components>=2.0.4",
|
"dash-bootstrap-components>=2.0.4",
|
||||||
|
"dotenv>=0.9.9",
|
||||||
]
|
]
|
||||||
|
@ -12,15 +12,15 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
Output(ids.BAR_CHART, "children"),
|
Output(ids.BAR_CHART, "children"),
|
||||||
[
|
[
|
||||||
Input(ids.YEAR_DROPDOWN, "value"),
|
Input(ids.YEAR_DROPDOWN, "value"),
|
||||||
Input(ids.MONTH_DROPDOWN, "value"),
|
Input(ids.WEEK_DROPDOWN, "value"),
|
||||||
Input(ids.CATEGORY_DROPDOWN, "value"),
|
Input(ids.CATEGORY_DROPDOWN, "value"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def update_bar_chart(
|
def update_bar_chart(
|
||||||
years: list[str], months: list[str], categories: list[str]
|
years: list[str], weeks: list[str], categories: list[str]
|
||||||
) -> html.Div:
|
) -> html.Div:
|
||||||
filtered_data = data.query(
|
filtered_data = data.query(
|
||||||
"year in @years and month in @months and category in @categories"
|
"year in @years and week in @weeks and category in @categories"
|
||||||
)
|
)
|
||||||
|
|
||||||
if filtered_data.shape[0] == 0:
|
if filtered_data.shape[0] == 0:
|
||||||
|
@ -14,12 +14,12 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
Output(ids.CATEGORY_DROPDOWN, "value"),
|
Output(ids.CATEGORY_DROPDOWN, "value"),
|
||||||
[
|
[
|
||||||
Input(ids.YEAR_DROPDOWN, "value"),
|
Input(ids.YEAR_DROPDOWN, "value"),
|
||||||
Input(ids.MONTH_DROPDOWN, "value"),
|
Input(ids.WEEK_DROPDOWN, "value"),
|
||||||
Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"),
|
Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def select_all_categories(years: list[str], months: list[str], _: int) -> list[str]:
|
def select_all_categories(years: list[str], weeks: list[str], _: int) -> list[str]:
|
||||||
filtered_data = data.query("year in @years and month in @months")
|
filtered_data = data.query("year in @years and week in @weeks")
|
||||||
return sorted(set(filtered_data[DataSchema.CATEGORY].tolist()))
|
return sorted(set(filtered_data[DataSchema.CATEGORY].tolist()))
|
||||||
|
|
||||||
return html.Div(
|
return html.Div(
|
||||||
|
@ -1,48 +1,123 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import plotly.express as px
|
from dash import Dash, dcc, html, dash_table, Input, Output, State, callback_context
|
||||||
from dash import Dash, dcc, html
|
from datetime import datetime
|
||||||
from dash.dependencies import Input, Output
|
import os
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
|
||||||
from ..data.loader import DataSchema
|
from ..data.loader import DataSchema
|
||||||
from . import ids
|
from . import ids
|
||||||
|
|
||||||
|
|
||||||
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
||||||
@app.callback(
|
@app.callback(
|
||||||
Output(ids.BAR_CHART, "children"),
|
Output(ids.DATA_TABLE, "children"),
|
||||||
[
|
[
|
||||||
Input(ids.YEAR_DROPDOWN, "value"),
|
Input(ids.YEAR_DROPDOWN, "value"),
|
||||||
Input(ids.MONTH_DROPDOWN, "value"),
|
Input(ids.WEEK_DROPDOWN, "value"),
|
||||||
Input(ids.CATEGORY_DROPDOWN, "value"),
|
Input(ids.CATEGORY_DROPDOWN, "value"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def update_bar_chart(
|
def update_data_table(
|
||||||
years: list[str], months: list[str], categories: list[str]
|
years: list[str], weeks: list[str], categories: list[str]
|
||||||
) -> html.Div:
|
) -> html.Div:
|
||||||
filtered_data = data.query(
|
filtered_data = data.query(
|
||||||
"year in @years and month in @months and category in @categories"
|
"year in @years and week in @weeks and category in @categories"
|
||||||
)
|
)
|
||||||
|
|
||||||
if filtered_data.shape[0] == 0:
|
if filtered_data.shape[0] == 0:
|
||||||
return html.Div("No data selected.", id=ids.BAR_CHART)
|
return html.Div("No data selected.")
|
||||||
|
|
||||||
def create_pivot_table() -> pd.DataFrame:
|
|
||||||
pt = filtered_data.pivot_table(
|
pt = filtered_data.pivot_table(
|
||||||
values=DataSchema.AMOUNT,
|
values=DataSchema.AMOUNT,
|
||||||
index=[DataSchema.CATEGORY],
|
index=[DataSchema.CATEGORY],
|
||||||
aggfunc="sum",
|
aggfunc="sum",
|
||||||
fill_value=0,
|
fill_value=0,
|
||||||
dropna=False,
|
dropna=False,
|
||||||
)
|
).reset_index().sort_values(DataSchema.AMOUNT, ascending=False)
|
||||||
return pt.reset_index().sort_values(DataSchema.AMOUNT, ascending=False)
|
|
||||||
|
|
||||||
fig = px.bar(
|
columns = [{"name": i.capitalize(), "id": i} for i in pt.columns]
|
||||||
create_pivot_table(),
|
|
||||||
x=DataSchema.CATEGORY,
|
return dash_table.DataTable(
|
||||||
y=DataSchema.AMOUNT,
|
id=ids.CATEGORY_TABLE,
|
||||||
color=DataSchema.CATEGORY,
|
data=pt.to_dict("records"),
|
||||||
|
columns=columns,
|
||||||
|
row_selectable='single',
|
||||||
|
selected_rows=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART)
|
@app.callback(
|
||||||
|
Output(ids.FEEDBACK_MODAL, "is_open"),
|
||||||
|
Output(ids.FEEDBACK_MESSAGE, "children"),
|
||||||
|
Input(ids.CATEGORY_TABLE, "selected_rows"),
|
||||||
|
Input(ids.SAVE_FEEDBACK_BUTTON_POPUP, "n_clicks"),
|
||||||
|
Input(ids.CLOSE_FEEDBACK_BUTTON_POPUP, "n_clicks"),
|
||||||
|
State(ids.FEEDBACK_MODAL, "is_open"),
|
||||||
|
State(ids.CATEGORY_TABLE, "data"),
|
||||||
|
State(ids.FEEDBACK_INPUT, "value"),
|
||||||
|
)
|
||||||
|
def handle_feedback_modal(selected_rows, save_clicks, close_clicks, is_open, data, comment):
|
||||||
|
ctx = callback_context
|
||||||
|
if not ctx.triggered:
|
||||||
|
return False, ""
|
||||||
|
|
||||||
return html.Div(id=ids.BAR_CHART)
|
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
||||||
|
|
||||||
|
if triggered_id == ids.CATEGORY_TABLE and selected_rows:
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
if triggered_id == ids.SAVE_FEEDBACK_BUTTON_POPUP and selected_rows:
|
||||||
|
selected_row_data = data[selected_rows[0]]
|
||||||
|
category = selected_row_data[DataSchema.CATEGORY]
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
if not comment:
|
||||||
|
comment = ""
|
||||||
|
feedback_data = f'{timestamp},{category},"{comment}"\n'
|
||||||
|
file_path = "data/feedback.csv"
|
||||||
|
try:
|
||||||
|
is_new_file = not os.path.exists(file_path) or os.path.getsize(file_path) == 0
|
||||||
|
with open(file_path, "a") as f:
|
||||||
|
if is_new_file:
|
||||||
|
f.write("timestamp,category,comment\n")
|
||||||
|
f.write(feedback_data)
|
||||||
|
message = f"Feedback for category '{category}' has been saved successfully at {timestamp}."
|
||||||
|
return False, message
|
||||||
|
except Exception as e:
|
||||||
|
return True, "An error occurred while saving feedback."
|
||||||
|
|
||||||
|
if triggered_id == ids.CLOSE_FEEDBACK_BUTTON_POPUP:
|
||||||
|
return False, ""
|
||||||
|
|
||||||
|
return is_open, ""
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("feedback-category-label", "children"),
|
||||||
|
Input(ids.CATEGORY_TABLE, "selected_rows"),
|
||||||
|
State(ids.CATEGORY_TABLE, "data"),
|
||||||
|
prevent_initial_call=True
|
||||||
|
)
|
||||||
|
def update_feedback_category_label(selected_rows, data):
|
||||||
|
if selected_rows:
|
||||||
|
category = data[selected_rows[0]][DataSchema.CATEGORY]
|
||||||
|
return f"Category: {category}"
|
||||||
|
return "Category: "
|
||||||
|
|
||||||
|
modal = dbc.Modal(
|
||||||
|
[
|
||||||
|
dbc.ModalHeader(dbc.ModalTitle("Submit Feedback")),
|
||||||
|
dbc.ModalBody([
|
||||||
|
html.H6("Category: ", id="feedback-category-label"),
|
||||||
|
dcc.Input(id=ids.FEEDBACK_INPUT, type='text', placeholder='Enter feedback...', style={'width': '100%'}),
|
||||||
|
]),
|
||||||
|
dbc.ModalFooter([
|
||||||
|
dbc.Button("Save", id=ids.SAVE_FEEDBACK_BUTTON_POPUP, className="ms-auto", n_clicks=0),
|
||||||
|
dbc.Button("Close", id=ids.CLOSE_FEEDBACK_BUTTON_POPUP, className="ms-auto", n_clicks=0)
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
id=ids.FEEDBACK_MODAL,
|
||||||
|
is_open=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
return html.Div([
|
||||||
|
html.Div(id=ids.DATA_TABLE),
|
||||||
|
html.Div(id=ids.FEEDBACK_MESSAGE),
|
||||||
|
modal
|
||||||
|
])
|
22
src/components/explanation_tab.py
Normal file
22
src/components/explanation_tab.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from dash import Dash, dcc, html
|
||||||
|
|
||||||
|
def render(_: Dash) -> html.Div:
|
||||||
|
return html.Div([
|
||||||
|
dcc.Markdown("""
|
||||||
|
### How to use this Dashboard
|
||||||
|
|
||||||
|
**Dashboard Tab:**
|
||||||
|
- Use the dropdowns at the top to filter the data by year, week, and category.
|
||||||
|
- The bar chart shows the total amount per category for the selected period.
|
||||||
|
- The data table shows the detailed data.
|
||||||
|
|
||||||
|
**Feedback:**
|
||||||
|
- In the data table on the Dashboard tab, you can select a row to provide feedback.
|
||||||
|
- A popup will appear where you can enter your comments for the selected category.
|
||||||
|
- Click "Save" to store your feedback.
|
||||||
|
|
||||||
|
**Feedback Tab:**
|
||||||
|
- This tab displays all the feedback that has been submitted.
|
||||||
|
- The table on this tab updates automatically every 5 seconds.
|
||||||
|
""")
|
||||||
|
])
|
27
src/components/feedback_tab.py
Normal file
27
src/components/feedback_tab.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from dash import Dash, dcc, html, dash_table
|
||||||
|
from dash.dependencies import Input, Output
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
def render(app: Dash) -> html.Div:
|
||||||
|
@app.callback(
|
||||||
|
Output("feedback-table-content", "children"),
|
||||||
|
Input("feedback-interval", "n_intervals")
|
||||||
|
)
|
||||||
|
def update_feedback_table(_):
|
||||||
|
try:
|
||||||
|
feedback_df = pd.read_csv("data/feedback.csv")
|
||||||
|
return dash_table.DataTable(
|
||||||
|
data=feedback_df.to_dict("records"),
|
||||||
|
columns=[{"name": i, "id": i} for i in feedback_df.columns],
|
||||||
|
page_size=10,
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return html.P("No feedback submitted yet.")
|
||||||
|
except Exception as e:
|
||||||
|
return html.P(f"An error occurred: {e}")
|
||||||
|
|
||||||
|
return html.Div([
|
||||||
|
html.H4("Submitted Feedback"),
|
||||||
|
html.Div(id="feedback-table-content"),
|
||||||
|
dcc.Interval(id="feedback-interval", interval=5 * 1000, n_intervals=0) # 5 seconds
|
||||||
|
])
|
@ -5,8 +5,16 @@ DATA_TABLE = "data-table"
|
|||||||
SELECT_ALL_CATEGORIES_BUTTON = "select-all-categories-button"
|
SELECT_ALL_CATEGORIES_BUTTON = "select-all-categories-button"
|
||||||
CATEGORY_DROPDOWN = "category-dropdown"
|
CATEGORY_DROPDOWN = "category-dropdown"
|
||||||
|
|
||||||
SELECT_ALL_MONTHS_BUTTON = "select-all-months-button"
|
|
||||||
MONTH_DROPDOWN = "month-dropdown"
|
|
||||||
|
|
||||||
YEAR_DROPDOWN = "year-dropdown"
|
YEAR_DROPDOWN = "year-dropdown"
|
||||||
SELECT_ALL_YEARS_BUTTON = "select-all-years-button"
|
SELECT_ALL_YEARS_BUTTON = "select-all-years-button"
|
||||||
|
|
||||||
|
WEEK_DROPDOWN = "week-dropdown"
|
||||||
|
SELECT_ALL_WEEKS_BUTTON = "select-all-weeks-button"
|
||||||
|
|
||||||
|
CATEGORY_TABLE = "category-table"
|
||||||
|
|
||||||
|
FEEDBACK_INPUT = "feedback-input"
|
||||||
|
FEEDBACK_MESSAGE = "feedback-message"
|
||||||
|
FEEDBACK_MODAL = "feedback-modal"
|
||||||
|
SAVE_FEEDBACK_BUTTON_POPUP = "save-feedback-button-popup"
|
||||||
|
CLOSE_FEEDBACK_BUTTON_POPUP = "close-feedback-button-popup"
|
@ -1,29 +1,48 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from dash import Dash, html
|
from dash import Dash, dcc, html
|
||||||
from src.components import (
|
from src.components import (
|
||||||
bar_chart,
|
bar_chart,
|
||||||
category_dropdown,
|
data_table,
|
||||||
month_dropdown,
|
|
||||||
pie_chart,
|
|
||||||
year_dropdown,
|
year_dropdown,
|
||||||
|
week_dropdown,
|
||||||
|
category_dropdown,
|
||||||
|
feedback_tab,
|
||||||
|
explanation_tab,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
|
def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
|
||||||
|
tab_content_style = {'height': 'calc(100vh - 220px)', 'overflowY': 'auto', 'padding': '15px'}
|
||||||
|
|
||||||
return html.Div(
|
return html.Div(
|
||||||
className="app-div",
|
className="app-div",
|
||||||
children=[
|
children=[
|
||||||
html.H1(app.title),
|
html.H1(app.title),
|
||||||
html.Hr(),
|
html.Hr(),
|
||||||
|
dcc.Tabs(id="tabs", value='tab-dashboard', children=[
|
||||||
|
dcc.Tab(label='Dashboard', value='tab-dashboard', children=[
|
||||||
|
html.Div(style=tab_content_style, children=[
|
||||||
html.Div(
|
html.Div(
|
||||||
className="dropdown-container",
|
className="dropdown-container",
|
||||||
children=[
|
children=[
|
||||||
year_dropdown.render(app, data),
|
year_dropdown.render(app, data),
|
||||||
month_dropdown.render(app, data),
|
week_dropdown.render(app, data),
|
||||||
category_dropdown.render(app, data),
|
category_dropdown.render(app, data),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bar_chart.render(app, data),
|
bar_chart.render(app, data),
|
||||||
pie_chart.render(app, data),
|
data_table.render(app, data),
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
dcc.Tab(label='Feedback', value='tab-feedback', children=[
|
||||||
|
html.Div(style=tab_content_style, children=[
|
||||||
|
feedback_tab.render(app)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
dcc.Tab(label='Explanation', value='tab-explanation', children=[
|
||||||
|
html.Div(style=tab_content_style, children=[
|
||||||
|
explanation_tab.render(app)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
)
|
)
|
@ -1,40 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
from dash import Dash, dcc, html
|
|
||||||
from dash.dependencies import Input, Output
|
|
||||||
|
|
||||||
from ..data.loader import DataSchema
|
|
||||||
from . import ids
|
|
||||||
|
|
||||||
|
|
||||||
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|
||||||
all_months: list[str] = data[DataSchema.MONTH].tolist()
|
|
||||||
unique_months = sorted(set(all_months))
|
|
||||||
|
|
||||||
@app.callback(
|
|
||||||
Output(ids.MONTH_DROPDOWN, "value"),
|
|
||||||
[
|
|
||||||
Input(ids.YEAR_DROPDOWN, "value"),
|
|
||||||
Input(ids.SELECT_ALL_MONTHS_BUTTON, "n_clicks"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def select_all_months(years: list[str], _: int) -> list[str]:
|
|
||||||
filtered_data = data.query("year in @years")
|
|
||||||
return sorted(set(filtered_data[DataSchema.MONTH].tolist()))
|
|
||||||
|
|
||||||
return html.Div(
|
|
||||||
children=[
|
|
||||||
html.H6("Month"),
|
|
||||||
dcc.Dropdown(
|
|
||||||
id=ids.MONTH_DROPDOWN,
|
|
||||||
options=[{"label": month, "value": month} for month in unique_months],
|
|
||||||
value=unique_months,
|
|
||||||
multi=True,
|
|
||||||
),
|
|
||||||
html.Button(
|
|
||||||
className="dropdown-button",
|
|
||||||
children=["Select All"],
|
|
||||||
id=ids.SELECT_ALL_MONTHS_BUTTON,
|
|
||||||
n_clicks=0,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
53
src/components/week_dropdown.py
Normal file
53
src/components/week_dropdown.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from dash import Dash, dcc, html
|
||||||
|
from dash.dependencies import Input, Output
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from ..data.loader import DataSchema
|
||||||
|
from . import ids
|
||||||
|
|
||||||
|
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
||||||
|
all_weeks: list[str] = data[DataSchema.WEEK].tolist()
|
||||||
|
unique_weeks = sorted(set(all_weeks), reverse=True)
|
||||||
|
|
||||||
|
# determine default weeks (last 13)
|
||||||
|
today = datetime.now()
|
||||||
|
last_13_weeks = []
|
||||||
|
for i in range(13):
|
||||||
|
date = today - timedelta(weeks=i)
|
||||||
|
year, week, _ = date.isocalendar()
|
||||||
|
last_13_weeks.append(f"{year}{week:02d}")
|
||||||
|
|
||||||
|
default_weeks = [week for week in last_13_weeks if week in unique_weeks]
|
||||||
|
if not default_weeks and unique_weeks:
|
||||||
|
default_weeks = unique_weeks[:13]
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output(ids.WEEK_DROPDOWN, "value"),
|
||||||
|
[
|
||||||
|
Input(ids.YEAR_DROPDOWN, "value"),
|
||||||
|
Input(ids.SELECT_ALL_WEEKS_BUTTON, "n_clicks"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def select_all_weeks(years: list[str], _: int) -> list[str]:
|
||||||
|
filtered_data = data.query("year in @years")
|
||||||
|
return sorted(set(filtered_data[DataSchema.WEEK].tolist()))
|
||||||
|
|
||||||
|
return html.Div(
|
||||||
|
children=[
|
||||||
|
html.H6("Week"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id=ids.WEEK_DROPDOWN,
|
||||||
|
options=[{"label": f"{week[:4]}-{week[4:]}", "value": week} for week in unique_weeks],
|
||||||
|
value=default_weeks,
|
||||||
|
multi=True,
|
||||||
|
),
|
||||||
|
html.Button(
|
||||||
|
className="dropdown-button",
|
||||||
|
children=["Select All"],
|
||||||
|
id=ids.SELECT_ALL_WEEKS_BUTTON,
|
||||||
|
n_clicks=0,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
@ -7,6 +7,7 @@ class DataSchema:
|
|||||||
DATE = "date"
|
DATE = "date"
|
||||||
MONTH = "month"
|
MONTH = "month"
|
||||||
YEAR = "year"
|
YEAR = "year"
|
||||||
|
WEEK = "week"
|
||||||
|
|
||||||
|
|
||||||
def load_transaction_data(path: str) -> pd.DataFrame:
|
def load_transaction_data(path: str) -> pd.DataFrame:
|
||||||
@ -22,4 +23,8 @@ def load_transaction_data(path: str) -> pd.DataFrame:
|
|||||||
)
|
)
|
||||||
data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str)
|
data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str)
|
||||||
data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str)
|
data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str)
|
||||||
|
data[DataSchema.WEEK] = (
|
||||||
|
data[DataSchema.DATE].dt.isocalendar().year.astype(str)
|
||||||
|
+ data[DataSchema.DATE].dt.isocalendar().week.astype(str).str.zfill(2)
|
||||||
|
)
|
||||||
return data
|
return data
|
@ -6,6 +6,7 @@ class DataSchema:
|
|||||||
DATE = "date"
|
DATE = "date"
|
||||||
MONTH = "month"
|
MONTH = "month"
|
||||||
YEAR = "year"
|
YEAR = "year"
|
||||||
|
WEEK = "week"
|
||||||
|
|
||||||
class SPC_Schema:
|
class SPC_Schema:
|
||||||
FC = "FC"
|
FC = "FC"
|
||||||
@ -30,4 +31,8 @@ def load_spc_data(path: str) -> pd.DataFrame:
|
|||||||
)
|
)
|
||||||
data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str)
|
data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str)
|
||||||
data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str)
|
data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str)
|
||||||
|
data[DataSchema.WEEK] = (
|
||||||
|
data[DataSchema.DATE].dt.isocalendar().year.astype(str)
|
||||||
|
+ data[DataSchema.DATE].dt.isocalendar().week.astype(str).str.zfill(2)
|
||||||
|
)
|
||||||
return data
|
return data
|
22
uv.lock
generated
22
uv.lock
generated
@ -9,12 +9,14 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "dash" },
|
{ name = "dash" },
|
||||||
{ name = "dash-bootstrap-components" },
|
{ name = "dash-bootstrap-components" },
|
||||||
|
{ name = "dotenv" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "dash", specifier = ">=3.2.0" },
|
{ name = "dash", specifier = ">=3.2.0" },
|
||||||
{ name = "dash-bootstrap-components", specifier = ">=2.0.4" },
|
{ name = "dash-bootstrap-components", specifier = ">=2.0.4" },
|
||||||
|
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -141,6 +143,17 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d6/38/1efeec8b4d741c09ccd169baf8a00c07a0176b58e418d4cd0c30dffedd22/dash_bootstrap_components-2.0.4-py3-none-any.whl", hash = "sha256:767cf0084586c1b2b614ccf50f79fe4525fdbbf8e3a161ed60016e584a14f5d1", size = 204044 },
|
{ url = "https://files.pythonhosted.org/packages/d6/38/1efeec8b4d741c09ccd169baf8a00c07a0176b58e418d4cd0c30dffedd22/dash_bootstrap_components-2.0.4-py3-none-any.whl", hash = "sha256:767cf0084586c1b2b614ccf50f79fe4525fdbbf8e3a161ed60016e584a14f5d1", size = 204044 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dotenv"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
]
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask"
|
name = "flask"
|
||||||
version = "3.1.2"
|
version = "3.1.2"
|
||||||
@ -288,6 +301,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl", hash = "sha256:7ad806edce9d3cdd882eaebaf97c0c9e252043ed1ed3d382c3e3520ec07806d4", size = 9791257 },
|
{ url = "https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl", hash = "sha256:7ad806edce9d3cdd882eaebaf97c0c9e252043ed1ed3d382c3e3520ec07806d4", size = 9791257 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.32.5"
|
version = "2.32.5"
|
||||||
|
Reference in New Issue
Block a user