adjusted file with gemini

This commit is contained in:
2025-09-10 22:00:21 +02:00
parent 2fc0d000b6
commit 474482a752
25 changed files with 381849 additions and 80 deletions

View File

@ -3,46 +3,70 @@ import plotly.express as px
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
from ..data.loader import DataSchema
from ..data.loader_gz import MTBFSchema
from . import ids
def render(app: Dash, data: pd.DataFrame) -> html.Div:
@app.callback(
Output(ids.BAR_CHART, "children"),
[
Input(ids.YEAR_DROPDOWN, "value"),
Input(ids.WEEK_DROPDOWN, "value"),
Input(ids.CATEGORY_DROPDOWN, "value"),
Input(ids.HITS_SELECTOR, "value"),
Input(ids.SINGLE_HITTER_FILTER, "value"),
],
)
def update_bar_chart(
years: list[str], weeks: list[str], categories: list[str]
years: list[str], weeks: list[str], hits_type: str, single_hitter_filter: list[str]
) -> html.Div:
filtered_data = data.query(
"year in @years and week in @weeks and category in @categories"
"year in @years and week in @weeks"
)
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(
values=DataSchema.AMOUNT,
index=[DataSchema.CATEGORY],
aggfunc="sum",
fill_value=0,
dropna=False,
)
return pt.reset_index().sort_values(DataSchema.AMOUNT, ascending=False)
if hits_type == "p-hits":
hits_col = MTBFSchema.P_HITS
else:
hits_col = MTBFSchema.U_HITS
# Count 'Y' values and create pareto
pareto_data = filtered_data[filtered_data[hits_col] == 'Y'].groupby(MTBFSchema.AIR).size().reset_index(name='count')
if 'remove_single' in single_hitter_filter:
pareto_data = pareto_data[pareto_data['count'] > 1]
pareto_data = pareto_data.sort_values('count', ascending=False)
fig = px.bar(
create_pivot_table(),
x=DataSchema.CATEGORY,
y=DataSchema.AMOUNT,
color=DataSchema.CATEGORY,
pareto_data,
x=MTBFSchema.AIR,
y="count",
title=f"{hits_type.capitalize()} per AIR (Pareto)",
labels={
MTBFSchema.AIR: "AIR",
"count": "Count",
},
)
return dcc.Graph(figure=fig)
return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART)
return html.Div(id=ids.BAR_CHART)
return html.Div(
children=[
dcc.RadioItems(
id=ids.HITS_SELECTOR,
options=[
{'label': 'P-Hits', 'value': 'p-hits'},
{'label': 'U-Hits', 'value': 'u-hits'},
],
value='p-hits',
labelStyle={'display': 'inline-block'}
),
dcc.Checklist(
id=ids.SINGLE_HITTER_FILTER,
options=[{'label': 'Remove single hitters', 'value': 'remove_single'}],
value=[],
labelStyle={'display': 'inline-block'}
),
html.Div(id=ids.BAR_CHART),
]
)

View File

@ -2,13 +2,20 @@ import pandas as pd
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
from ..data.loader import DataSchema
from ..data.loader_gz import MTBFSchema
from . import ids
def render(app: Dash, data: pd.DataFrame) -> html.Div:
all_categories: list[str] = data[DataSchema.CATEGORY].tolist()
unique_categories: list[str] = sorted(set(all_categories))
all_airs: list[str] = data[MTBFSchema.AIR].tolist()
unique_airs: list[str] = sorted(set(all_airs))
# Define a color palette
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
'#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
# Create a color mapping
color_map = {air: colors[i % len(colors)] for i, air in enumerate(unique_airs)}
@app.callback(
Output(ids.CATEGORY_DROPDOWN, "value"),
@ -18,20 +25,20 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"),
],
)
def select_all_categories(years: list[str], weeks: list[str], _: int) -> list[str]:
def select_all_airs(years: list[str], weeks: list[str], _: int) -> list[str]:
filtered_data = data.query("year in @years and week in @weeks")
return sorted(set(filtered_data[DataSchema.CATEGORY].tolist()))
return sorted(set(filtered_data[MTBFSchema.AIR].tolist()))
return html.Div(
children=[
html.H6("Category"),
html.H6("AIR"),
dcc.Dropdown(
id=ids.CATEGORY_DROPDOWN,
options=[
{"label": category, "value": category}
for category in unique_categories
{"label": air, "value": air, "style": {"color": color_map[air]}}
for air in unique_airs
],
value=unique_categories,
value=unique_airs,
multi=True,
placeholder="Select",
),

View File

@ -4,7 +4,7 @@ from datetime import datetime
import os
import dash_bootstrap_components as dbc
from ..data.loader import DataSchema
from ..data.loader_gz import MTBFSchema
from . import ids
def render(app: Dash, data: pd.DataFrame) -> html.Div:
@ -13,33 +13,48 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
[
Input(ids.YEAR_DROPDOWN, "value"),
Input(ids.WEEK_DROPDOWN, "value"),
Input(ids.CATEGORY_DROPDOWN, "value"),
Input(ids.HITS_SELECTOR, "value"),
Input(ids.SINGLE_HITTER_FILTER, "value"),
],
)
def update_data_table(
years: list[str], weeks: list[str], categories: list[str]
years: list[str], weeks: list[str], hits_type: str, single_hitter_filter: list[str]
) -> html.Div:
filtered_data = data.query(
"year in @years and week in @weeks and category in @categories"
"year in @years and week in @weeks"
)
if filtered_data.shape[0] == 0:
return html.Div("No data selected.")
pt = filtered_data.pivot_table(
values=DataSchema.AMOUNT,
index=[DataSchema.CATEGORY],
aggfunc="sum",
fill_value=0,
dropna=False,
).reset_index().sort_values(DataSchema.AMOUNT, ascending=False)
if hits_type == "p-hits":
hits_col = MTBFSchema.P_HITS
else:
hits_col = MTBFSchema.U_HITS
columns = [{"name": i.capitalize(), "id": i} for i in pt.columns]
# Count 'Y' values
hits_data = filtered_data[filtered_data[hits_col] == 'Y']
table_data = hits_data.groupby(MTBFSchema.AIR).agg(
count=(hits_col, 'size'),
air_issue_description=(MTBFSchema.AIR_ISSUE_DESCRIPTION, lambda x: ', '.join(x.unique())),
close_notes=(MTBFSchema.CLOSE_NOTES, lambda x: ', '.join(x.unique()))
).reset_index()
if 'remove_single' in single_hitter_filter:
table_data = table_data[table_data['count'] > 1]
else:
table_data = table_data[table_data['count'] == 1]
table_data = table_data.sort_values('count', ascending=False)
# Reorder columns
table_data = table_data[[MTBFSchema.AIR, 'air_issue_description', 'close_notes', 'count']]
return dash_table.DataTable(
id=ids.CATEGORY_TABLE,
data=pt.to_dict("records"),
columns=columns,
id=ids.CATEGORY_TABLE, # Using this ID for feedback callbacks
data=table_data.to_dict("records"),
columns=[{"name": i, "id": i} for i in table_data.columns],
page_size=10,
row_selectable='single',
selected_rows=[]
)
@ -54,7 +69,7 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
State(ids.CATEGORY_TABLE, "data"),
State(ids.FEEDBACK_INPUT, "value"),
)
def handle_feedback_modal(selected_rows, save_clicks, close_clicks, is_open, data, comment):
def handle_feedback_modal(selected_rows, save_clicks, close_clicks, is_open, table_data, comment):
ctx = callback_context
if not ctx.triggered:
return False, ""
@ -65,12 +80,12 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
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]
selected_row_data = table_data[selected_rows[0]]
air_value = selected_row_data[MTBFSchema.AIR]
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if not comment:
comment = ""
feedback_data = f'{timestamp},{category},"{comment}"\n'
feedback_data = f'{timestamp},dashboard_table,"AIR: {air_value} - {comment}"\n'
file_path = "data/feedback.csv"
try:
is_new_file = not os.path.exists(file_path) or os.path.getsize(file_path) == 0
@ -78,7 +93,7 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
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}."
message = f"Feedback for AIR '{air_value}' has been saved successfully at {timestamp}."
return False, message
except Exception as e:
return True, "An error occurred while saving feedback."
@ -94,17 +109,17 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
State(ids.CATEGORY_TABLE, "data"),
prevent_initial_call=True
)
def update_feedback_category_label(selected_rows, data):
def update_feedback_air_label(selected_rows, data):
if selected_rows:
category = data[selected_rows[0]][DataSchema.CATEGORY]
return f"Category: {category}"
return "Category: "
air_value = data[selected_rows[0]][MTBFSchema.AIR]
return f"AIR: {air_value}"
return "AIR: "
modal = dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("Submit Feedback")),
dbc.ModalBody([
html.H6("Category: ", id="feedback-category-label"),
html.H6("AIR: ", id="feedback-category-label"),
dcc.Input(id=ids.FEEDBACK_INPUT, type='text', placeholder='Enter feedback...', style={'width': '100%'}),
]),
dbc.ModalFooter([

View File

@ -17,4 +17,16 @@ 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"
CLOSE_FEEDBACK_BUTTON_POPUP = "close-feedback-button-popup"
# Source Table
SOURCE_TABLE = "source-table"
SOURCE_FEEDBACK_MODAL = "source-feedback-modal"
SOURCE_FEEDBACK_INPUT = "source-feedback-input"
SAVE_SOURCE_FEEDBACK_BUTTON = "save-source-feedback-button"
CLOSE_SOURCE_FEEDBACK_BUTTON = "close-source-feedback-button"
SOURCE_FEEDBACK_MESSAGE = "source-feedback-message"
SOURCE_FEEDBACK_LABEL = "source-feedback-label"
HITS_SELECTOR = "hits-selector"
SINGLE_HITTER_FILTER = "single-hitter-filter"

View File

@ -5,9 +5,9 @@ from src.components import (
data_table,
year_dropdown,
week_dropdown,
category_dropdown,
feedback_tab,
explanation_tab,
source_table_tab,
)
def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
@ -26,13 +26,17 @@ def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
children=[
year_dropdown.render(app, data),
week_dropdown.render(app, data),
category_dropdown.render(app, data),
],
),
bar_chart.render(app, data),
data_table.render(app, data),
])
]),
dcc.Tab(label='Source Table', value='tab-source-table', children=[
html.Div(style=tab_content_style, children=[
source_table_tab.render(app, data)
])
]),
dcc.Tab(label='Feedback', value='tab-feedback', children=[
html.Div(style=tab_content_style, children=[
feedback_tab.render(app)

View File

@ -0,0 +1,95 @@
from dash import Dash, dcc, html, dash_table, Input, Output, State, callback_context
from datetime import datetime
import os
import dash_bootstrap_components as dbc
import pandas as pd
from src.data.loader_gz import MTBFSchema
from . import ids
def render(app: Dash, data: pd.DataFrame) -> html.Div:
@app.callback(
Output(ids.SOURCE_FEEDBACK_MODAL, "is_open"),
Output(ids.SOURCE_FEEDBACK_MESSAGE, "children"),
Input(ids.SOURCE_TABLE, "active_cell"),
Input(ids.SAVE_SOURCE_FEEDBACK_BUTTON, "n_clicks"),
Input(ids.CLOSE_SOURCE_FEEDBACK_BUTTON, "n_clicks"),
State(ids.SOURCE_FEEDBACK_MODAL, "is_open"),
State(ids.SOURCE_TABLE, "data"),
State(ids.SOURCE_FEEDBACK_INPUT, "value"),
)
def handle_feedback_modal(active_cell, save_clicks, close_clicks, is_open, table_data, comment):
ctx = callback_context
if not ctx.triggered:
return False, ""
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
if triggered_id == ids.SOURCE_TABLE and active_cell:
return True, ""
if triggered_id == ids.SAVE_SOURCE_FEEDBACK_BUTTON and active_cell:
selected_row_data = table_data[active_cell["row"]]
so_number = selected_row_data[MTBFSchema.SO]
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if not comment:
comment = ""
feedback_data = f'{timestamp},source_table,"SO: {so_number} - {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 SO '{so_number}' 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_SOURCE_FEEDBACK_BUTTON:
return False, ""
return is_open, ""
@app.callback(
Output(ids.SOURCE_FEEDBACK_LABEL, "children"),
Input(ids.SOURCE_TABLE, "active_cell"),
State(ids.SOURCE_TABLE, "data"),
prevent_initial_call=True
)
def update_feedback_so_label(active_cell, table_data):
if active_cell:
so_number = table_data[active_cell["row"]][MTBFSchema.SO]
return f"SO Number: {so_number}"
return "SO Number: "
modal = dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("Submit Feedback")),
dbc.ModalBody([
html.H6("SO Number: ", id=ids.SOURCE_FEEDBACK_LABEL),
dcc.Input(id=ids.SOURCE_FEEDBACK_INPUT, type='text', placeholder='Enter feedback...', style={'width': '100%'}),
]),
dbc.ModalFooter([
dbc.Button("Save", id=ids.SAVE_SOURCE_FEEDBACK_BUTTON, className="ms-auto", n_clicks=0),
dbc.Button("Close", id=ids.CLOSE_SOURCE_FEEDBACK_BUTTON, className="ms-auto", n_clicks=0)
]),
],
id=ids.SOURCE_FEEDBACK_MODAL,
is_open=False,
)
return html.Div([
dash_table.DataTable(
id=ids.SOURCE_TABLE,
data=data.to_dict("records"),
columns=[{"name": i, "id": i} for i in data.columns],
page_size=15,
sort_action="native",
filter_action="native",
cell_selectable=True,
),
html.Div(id=ids.SOURCE_FEEDBACK_MESSAGE),
modal
])

View File

@ -1,27 +1,14 @@
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]
unique_weeks = sorted(list(set(all_weeks)), reverse=True)
default_weeks = unique_weeks[:13]
@app.callback(
Output(ids.WEEK_DROPDOWN, "value"),

View File

@ -35,4 +35,69 @@ def load_spc_data(path: str) -> pd.DataFrame:
data[DataSchema.DATE].dt.isocalendar().year.astype(str)
+ data[DataSchema.DATE].dt.isocalendar().week.astype(str).str.zfill(2)
)
return data
return data
class MTBFSchema:
SYSTEM = "System"
CUSTOMER = "Customer"
ROOT_ERROR = "Root Error"
YEAR = "Year"
WEEK_NUMBER = "Week_Number"
YEAR_WEEK_NUMBER = "Year_Week_Number"
DATE = "Date"
SDT_TIMESTAMP = "SDT_Timestamp"
DESCRIPTION = "Description"
EPMID = "EPmID"
DURATION = "Duration"
FC = "FC"
SO = "SO"
AIR = "AIR"
LINK = "link"
AIR_ISSUE_CONFIRMED = "AIR_issue_confirmed"
AIR_FC = "AIR_FC"
AIR_ISSUE_DESCRIPTION = "Air_issue_description"
CLOSE_NOTES = "Close_notes"
WY = "WY"
P_HITS = "P_Hits"
U_HITS = "U_Hits"
RECONCILED = "Reconciled"
AIR2 = "AIR2"
EVENT_CODE = "Event_code"
CATEGORY = "Category"
XLD = "XLD"
WEEK_DIFF = "Week_Diff"
IN_MTBFP_AND_ULIST = "In_MTBFP_AND_Ulist"
LINK_TIMESTAMP = "Link_Timestamp"
CHECK_P_U = "Check_P_U"
NOMOLAPOS = "NomolaPos"
WEEK = "week"
def load_mtbf_data(path: str) -> pd.DataFrame:
# load the data from the CSV file
data = pd.read_csv(
path,
dtype={
MTBFSchema.EPMID: float,
MTBFSchema.DURATION: float,
MTBFSchema.NOMOLAPOS: int,
MTBFSchema.YEAR: int,
MTBFSchema.WEEK_NUMBER: int,
MTBFSchema.YEAR_WEEK_NUMBER: int,
MTBFSchema.SO: str,
MTBFSchema.AIR_ISSUE_CONFIRMED: str,
MTBFSchema.AIR_FC: str,
MTBFSchema.EVENT_CODE: str,
MTBFSchema.WY: str,
MTBFSchema.AIR: str,
},
parse_dates=[MTBFSchema.DATE],
index_col=0
)
data[MTBFSchema.AIR].fillna('', inplace=True)
data["month"] = data[MTBFSchema.DATE].dt.month.astype(str)
data[MTBFSchema.WEEK] = (
data[MTBFSchema.DATE].dt.isocalendar().year.astype(str)
+ data[MTBFSchema.DATE].dt.isocalendar().week.astype(str).str.zfill(2)
)
data.rename(columns={"Year": "year", "Category": "category"}, inplace=True)
return data