Spaces:
Sleeping
Sleeping
mriusero
commited on
Commit
·
f731ddb
1
Parent(s):
f8d9c8a
feat: general metrics
Browse files- src/production/downtime.py +10 -10
- src/production/flow.py +1 -1
- src/production/metrics/machine.py +7 -8
- src/ui/dashboard.py +9 -14
- src/ui/graphs/general_graphs.py +292 -13
- src/ui/graphs/tools_graphs.py +140 -51
src/production/downtime.py
CHANGED
|
@@ -5,60 +5,60 @@ machine_errors = {
|
|
| 5 |
"description": "Calibration Error",
|
| 6 |
"cause": "The machine is not correctly calibrated.",
|
| 7 |
"solution": "Recalibrate the machine according to the manufacturer's specifications.",
|
| 8 |
-
"downtime": timedelta(
|
| 9 |
},
|
| 10 |
"E002": {
|
| 11 |
"description": "Motor Overheating",
|
| 12 |
"cause": "The motor has exceeded the maximum operating temperature.",
|
| 13 |
"solution": "Stop the machine and let it cool down. Check the cooling system.",
|
| 14 |
-
"downtime": timedelta(
|
| 15 |
},
|
| 16 |
"E003": {
|
| 17 |
"description": "Material Jam",
|
| 18 |
"cause": "Accumulation of material in the processing area.",
|
| 19 |
"solution": "Clean the processing area and check the feeding mechanisms.",
|
| 20 |
-
"downtime": timedelta(minutes=
|
| 21 |
},
|
| 22 |
"E004": {
|
| 23 |
"description": "Sensor Error",
|
| 24 |
"cause": "A sensor is not functioning correctly.",
|
| 25 |
"solution": "Check the sensor connections and replace if necessary.",
|
| 26 |
-
"downtime": timedelta(
|
| 27 |
},
|
| 28 |
"E005": {
|
| 29 |
"description": "Power Failure",
|
| 30 |
"cause": "Electrical supply interrupted.",
|
| 31 |
"solution": "Check the electrical supply and fuses. Restart the machine.",
|
| 32 |
-
"downtime": timedelta(minutes=
|
| 33 |
},
|
| 34 |
"E006": {
|
| 35 |
"description": "Software Error",
|
| 36 |
"cause": "Bug in the machine control software.",
|
| 37 |
"solution": "Restart the software or update the firmware.",
|
| 38 |
-
"downtime": timedelta(
|
| 39 |
},
|
| 40 |
"E007": {
|
| 41 |
"description": "Wear and Tear of Parts",
|
| 42 |
"cause": "The machine parts are worn out.",
|
| 43 |
"solution": "Inspect the parts and replace if necessary.",
|
| 44 |
-
"downtime": timedelta(
|
| 45 |
},
|
| 46 |
"E008": {
|
| 47 |
"description": "Communication Error",
|
| 48 |
"cause": "Communication problem between different machine modules.",
|
| 49 |
"solution": "Check communication cables and protocols.",
|
| 50 |
-
"downtime": timedelta(
|
| 51 |
},
|
| 52 |
"E009": {
|
| 53 |
"description": "Low Lubricant Level",
|
| 54 |
"cause": "The lubricant level is insufficient.",
|
| 55 |
"solution": "Refill the lubricant reservoir according to specifications.",
|
| 56 |
-
"downtime": timedelta(minutes=
|
| 57 |
},
|
| 58 |
"E010": {
|
| 59 |
"description": "Positioning Error",
|
| 60 |
"cause": "The tooling is not positioning correctly.",
|
| 61 |
"solution": "Check the positioning mechanisms and recalibrate if necessary.",
|
| 62 |
-
"downtime": timedelta(
|
| 63 |
}
|
| 64 |
}
|
|
|
|
| 5 |
"description": "Calibration Error",
|
| 6 |
"cause": "The machine is not correctly calibrated.",
|
| 7 |
"solution": "Recalibrate the machine according to the manufacturer's specifications.",
|
| 8 |
+
"downtime": timedelta(minutes=15)
|
| 9 |
},
|
| 10 |
"E002": {
|
| 11 |
"description": "Motor Overheating",
|
| 12 |
"cause": "The motor has exceeded the maximum operating temperature.",
|
| 13 |
"solution": "Stop the machine and let it cool down. Check the cooling system.",
|
| 14 |
+
"downtime": timedelta(minutes=20)
|
| 15 |
},
|
| 16 |
"E003": {
|
| 17 |
"description": "Material Jam",
|
| 18 |
"cause": "Accumulation of material in the processing area.",
|
| 19 |
"solution": "Clean the processing area and check the feeding mechanisms.",
|
| 20 |
+
"downtime": timedelta(minutes=8)
|
| 21 |
},
|
| 22 |
"E004": {
|
| 23 |
"description": "Sensor Error",
|
| 24 |
"cause": "A sensor is not functioning correctly.",
|
| 25 |
"solution": "Check the sensor connections and replace if necessary.",
|
| 26 |
+
"downtime": timedelta(minutes=20)
|
| 27 |
},
|
| 28 |
"E005": {
|
| 29 |
"description": "Power Failure",
|
| 30 |
"cause": "Electrical supply interrupted.",
|
| 31 |
"solution": "Check the electrical supply and fuses. Restart the machine.",
|
| 32 |
+
"downtime": timedelta(minutes=10)
|
| 33 |
},
|
| 34 |
"E006": {
|
| 35 |
"description": "Software Error",
|
| 36 |
"cause": "Bug in the machine control software.",
|
| 37 |
"solution": "Restart the software or update the firmware.",
|
| 38 |
+
"downtime": timedelta(minutes=15)
|
| 39 |
},
|
| 40 |
"E007": {
|
| 41 |
"description": "Wear and Tear of Parts",
|
| 42 |
"cause": "The machine parts are worn out.",
|
| 43 |
"solution": "Inspect the parts and replace if necessary.",
|
| 44 |
+
"downtime": timedelta(minutes=15)
|
| 45 |
},
|
| 46 |
"E008": {
|
| 47 |
"description": "Communication Error",
|
| 48 |
"cause": "Communication problem between different machine modules.",
|
| 49 |
"solution": "Check communication cables and protocols.",
|
| 50 |
+
"downtime": timedelta(minutes=20)
|
| 51 |
},
|
| 52 |
"E009": {
|
| 53 |
"description": "Low Lubricant Level",
|
| 54 |
"cause": "The lubricant level is insufficient.",
|
| 55 |
"solution": "Refill the lubricant reservoir according to specifications.",
|
| 56 |
+
"downtime": timedelta(minutes=8)
|
| 57 |
},
|
| 58 |
"E010": {
|
| 59 |
"description": "Positioning Error",
|
| 60 |
"cause": "The tooling is not positioning correctly.",
|
| 61 |
"solution": "Check the positioning mechanisms and recalibrate if necessary.",
|
| 62 |
+
"downtime": timedelta(minutes=15)
|
| 63 |
}
|
| 64 |
}
|
src/production/flow.py
CHANGED
|
@@ -32,7 +32,7 @@ async def generate_data(state):
|
|
| 32 |
if not state["running"]:
|
| 33 |
break
|
| 34 |
|
| 35 |
-
if random.random() < 0.
|
| 36 |
error_key = random.choice(list(machine_errors.keys()))
|
| 37 |
error = machine_errors[error_key]
|
| 38 |
downtime = error["downtime"]
|
|
|
|
| 32 |
if not state["running"]:
|
| 33 |
break
|
| 34 |
|
| 35 |
+
if random.random() < 0.01: # 0.005
|
| 36 |
error_key = random.choice(list(machine_errors.keys()))
|
| 37 |
error = machine_errors[error_key]
|
| 38 |
downtime = error["downtime"]
|
src/production/metrics/machine.py
CHANGED
|
@@ -8,29 +8,28 @@ async def machine_metrics(raw_data):
|
|
| 8 |
df[col] = pd.to_datetime(df[col], errors='coerce', format="%Y-%m-%d %H:%M:%S")
|
| 9 |
|
| 10 |
opening_time = df['Timestamp'].max() - df['Timestamp'].min()
|
| 11 |
-
required_time = opening_time
|
|
|
|
| 12 |
|
| 13 |
downtime_df = df.dropna(subset=['Downtime Start', 'Downtime End'])
|
| 14 |
unplanned_stop_time = (downtime_df['Downtime End'] - downtime_df['Downtime Start']).sum()
|
| 15 |
operating_time = required_time - unplanned_stop_time
|
| 16 |
|
| 17 |
-
net_time = operating_time
|
|
|
|
| 18 |
|
| 19 |
nok_count = (df['Compliance'] != 'OK').sum()
|
| 20 |
useful_time = net_time - pd.Timedelta(seconds=nok_count)
|
| 21 |
|
| 22 |
-
total_parts = len(df)
|
| 23 |
-
compliant_parts = (df['Compliance'] == 'OK').sum()
|
| 24 |
-
|
| 25 |
operating_sec = operating_time.total_seconds()
|
| 26 |
net_sec = net_time.total_seconds()
|
| 27 |
required_sec = required_time.total_seconds()
|
| 28 |
|
| 29 |
-
quality_rate = (
|
| 30 |
operating_rate = (net_sec / operating_sec) * 100 if operating_sec > 0 else 0
|
| 31 |
availability_rate = (operating_sec / required_sec) * 100 if required_sec > 0 else 0
|
| 32 |
|
| 33 |
-
|
| 34 |
|
| 35 |
downtime_count = len(downtime_df)
|
| 36 |
mtbf = operating_time / downtime_count if downtime_count > 0 else pd.Timedelta(0)
|
|
@@ -46,7 +45,7 @@ async def machine_metrics(raw_data):
|
|
| 46 |
"quality_rate": quality_rate,
|
| 47 |
"operating_rate": operating_rate,
|
| 48 |
"availability_rate": availability_rate,
|
| 49 |
-
"
|
| 50 |
"MTBF": str(mtbf),
|
| 51 |
"MTTR": str(mttr)
|
| 52 |
}
|
|
|
|
| 8 |
df[col] = pd.to_datetime(df[col], errors='coerce', format="%Y-%m-%d %H:%M:%S")
|
| 9 |
|
| 10 |
opening_time = df['Timestamp'].max() - df['Timestamp'].min()
|
| 11 |
+
required_time = opening_time
|
| 12 |
+
# planned_stop_time = 0 non implémenté
|
| 13 |
|
| 14 |
downtime_df = df.dropna(subset=['Downtime Start', 'Downtime End'])
|
| 15 |
unplanned_stop_time = (downtime_df['Downtime End'] - downtime_df['Downtime Start']).sum()
|
| 16 |
operating_time = required_time - unplanned_stop_time
|
| 17 |
|
| 18 |
+
net_time = operating_time
|
| 19 |
+
# cadency_variance = 0 non implémenté
|
| 20 |
|
| 21 |
nok_count = (df['Compliance'] != 'OK').sum()
|
| 22 |
useful_time = net_time - pd.Timedelta(seconds=nok_count)
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
operating_sec = operating_time.total_seconds()
|
| 25 |
net_sec = net_time.total_seconds()
|
| 26 |
required_sec = required_time.total_seconds()
|
| 27 |
|
| 28 |
+
quality_rate = (useful_time / net_time) * 100 if net_time else 0
|
| 29 |
operating_rate = (net_sec / operating_sec) * 100 if operating_sec > 0 else 0
|
| 30 |
availability_rate = (operating_sec / required_sec) * 100 if required_sec > 0 else 0
|
| 31 |
|
| 32 |
+
OEE = (quality_rate / 100) * (operating_rate / 100) * (availability_rate / 100) * 100
|
| 33 |
|
| 34 |
downtime_count = len(downtime_df)
|
| 35 |
mtbf = operating_time / downtime_count if downtime_count > 0 else pd.Timedelta(0)
|
|
|
|
| 45 |
"quality_rate": quality_rate,
|
| 46 |
"operating_rate": operating_rate,
|
| 47 |
"availability_rate": availability_rate,
|
| 48 |
+
"OEE": OEE,
|
| 49 |
"MTBF": str(mtbf),
|
| 50 |
"MTTR": str(mttr)
|
| 51 |
}
|
src/ui/dashboard.py
CHANGED
|
@@ -17,6 +17,7 @@ def hash_dataframe(df):
|
|
| 17 |
"""Computes a simple hash to detect changes in the DataFrame."""
|
| 18 |
return pd.util.hash_pandas_object(df).sum()
|
| 19 |
|
|
|
|
| 20 |
async def dataflow(state):
|
| 21 |
"""
|
| 22 |
Main function that updates data if necessary.
|
|
@@ -101,6 +102,8 @@ def init_components(n=TOOLS_COUNT):
|
|
| 101 |
- tool_plots: list of tool-related Gradio components
|
| 102 |
- general_plots: list of general-related Gradio components
|
| 103 |
"""
|
|
|
|
|
|
|
| 104 |
displays = []
|
| 105 |
tool_plots = []
|
| 106 |
general_plots = []
|
|
@@ -109,7 +112,7 @@ def init_components(n=TOOLS_COUNT):
|
|
| 109 |
main_display = GeneralMetricsDisplay()
|
| 110 |
displays.append(main_display)
|
| 111 |
general_plots.extend(
|
| 112 |
-
main_display.
|
| 113 |
all_tools_df=pd.DataFrame(),
|
| 114 |
issues_df=pd.DataFrame(),
|
| 115 |
efficiency_data={}
|
|
@@ -127,11 +130,9 @@ def init_components(n=TOOLS_COUNT):
|
|
| 127 |
async def on_tick(state, displays):
|
| 128 |
"""
|
| 129 |
Tick function called periodically to update plots if data has changed.
|
| 130 |
-
|
| 131 |
Handles:
|
| 132 |
- Tool-specific plots (tool_1, tool_2, ..., tool_n)
|
| 133 |
- General plots (all tools, issues, efficiency)
|
| 134 |
-
|
| 135 |
Returns two lists of plots separately for tools and general metrics, plus state.
|
| 136 |
"""
|
| 137 |
async with state.setdefault('lock', asyncio.Lock()):
|
|
@@ -146,32 +147,26 @@ async def on_tick(state, displays):
|
|
| 146 |
general_plots = []
|
| 147 |
general_display = displays[0]
|
| 148 |
general_plots.extend(
|
| 149 |
-
|
| 150 |
all_tools_df=all_tools_df,
|
| 151 |
issues_df=issues_df,
|
| 152 |
efficiency_data=efficiency_data
|
| 153 |
)
|
| 154 |
)
|
|
|
|
| 155 |
# Update tool-specific plots
|
| 156 |
tool_plots = []
|
| 157 |
for df, display in zip(tool_dfs, displays[1:]):
|
| 158 |
tool_plots.extend(
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
display.gauge(df, type='cpk', cote='pos'),
|
| 163 |
-
display.normal_curve(df, cote='ori'),
|
| 164 |
-
display.gauge(df, type='cp', cote='ori'),
|
| 165 |
-
display.gauge(df, type='cpk', cote='ori'),
|
| 166 |
-
display.control_graph(df),
|
| 167 |
-
]
|
| 168 |
)
|
| 169 |
return tool_plots + general_plots + [state]
|
| 170 |
|
| 171 |
def dashboard_ui(state):
|
| 172 |
"""
|
| 173 |
Creates the Gradio interface and sets a refresh every second.
|
| 174 |
-
|
| 175 |
The outputs are separated into two groups for tools and general metrics to
|
| 176 |
preserve layout order and grouping.
|
| 177 |
"""
|
|
|
|
| 17 |
"""Computes a simple hash to detect changes in the DataFrame."""
|
| 18 |
return pd.util.hash_pandas_object(df).sum()
|
| 19 |
|
| 20 |
+
|
| 21 |
async def dataflow(state):
|
| 22 |
"""
|
| 23 |
Main function that updates data if necessary.
|
|
|
|
| 102 |
- tool_plots: list of tool-related Gradio components
|
| 103 |
- general_plots: list of general-related Gradio components
|
| 104 |
"""
|
| 105 |
+
print("Initializing components...")
|
| 106 |
+
|
| 107 |
displays = []
|
| 108 |
tool_plots = []
|
| 109 |
general_plots = []
|
|
|
|
| 112 |
main_display = GeneralMetricsDisplay()
|
| 113 |
displays.append(main_display)
|
| 114 |
general_plots.extend(
|
| 115 |
+
main_display.general_block(
|
| 116 |
all_tools_df=pd.DataFrame(),
|
| 117 |
issues_df=pd.DataFrame(),
|
| 118 |
efficiency_data={}
|
|
|
|
| 130 |
async def on_tick(state, displays):
|
| 131 |
"""
|
| 132 |
Tick function called periodically to update plots if data has changed.
|
|
|
|
| 133 |
Handles:
|
| 134 |
- Tool-specific plots (tool_1, tool_2, ..., tool_n)
|
| 135 |
- General plots (all tools, issues, efficiency)
|
|
|
|
| 136 |
Returns two lists of plots separately for tools and general metrics, plus state.
|
| 137 |
"""
|
| 138 |
async with state.setdefault('lock', asyncio.Lock()):
|
|
|
|
| 147 |
general_plots = []
|
| 148 |
general_display = displays[0]
|
| 149 |
general_plots.extend(
|
| 150 |
+
general_display.refresh(
|
| 151 |
all_tools_df=all_tools_df,
|
| 152 |
issues_df=issues_df,
|
| 153 |
efficiency_data=efficiency_data
|
| 154 |
)
|
| 155 |
)
|
| 156 |
+
|
| 157 |
# Update tool-specific plots
|
| 158 |
tool_plots = []
|
| 159 |
for df, display in zip(tool_dfs, displays[1:]):
|
| 160 |
tool_plots.extend(
|
| 161 |
+
display.refresh(
|
| 162 |
+
df=df
|
| 163 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
)
|
| 165 |
return tool_plots + general_plots + [state]
|
| 166 |
|
| 167 |
def dashboard_ui(state):
|
| 168 |
"""
|
| 169 |
Creates the Gradio interface and sets a refresh every second.
|
|
|
|
| 170 |
The outputs are separated into two groups for tools and general metrics to
|
| 171 |
preserve layout order and grouping.
|
| 172 |
"""
|
src/ui/graphs/general_graphs.py
CHANGED
|
@@ -1,18 +1,297 @@
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
class GeneralMetricsDisplay:
|
| 5 |
def __init__(self):
|
| 6 |
-
self.
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import plotly.graph_objects as go
|
| 4 |
|
| 5 |
|
| 6 |
class GeneralMetricsDisplay:
|
| 7 |
def __init__(self):
|
| 8 |
+
self.plots = []
|
| 9 |
+
|
| 10 |
+
@staticmethod
|
| 11 |
+
def kpi_rate(percentage, title="KPI"):
|
| 12 |
+
if percentage is None or not (0 <= percentage <= 100):
|
| 13 |
+
fig = go.Figure()
|
| 14 |
+
fig.update_layout(
|
| 15 |
+
template='plotly_dark',
|
| 16 |
+
width=320,
|
| 17 |
+
height=150,
|
| 18 |
+
margin=dict(l=10, r=10, t=10, b=10),
|
| 19 |
+
annotations=[dict(
|
| 20 |
+
text="No data",
|
| 21 |
+
showarrow=False,
|
| 22 |
+
font=dict(size=16, color="white"),
|
| 23 |
+
x=0.5, y=0.5, xanchor="center", yanchor="middle"
|
| 24 |
+
)]
|
| 25 |
+
)
|
| 26 |
+
return fig
|
| 27 |
+
fig = go.Figure(data=[go.Pie(
|
| 28 |
+
values=[percentage, 100 - percentage],
|
| 29 |
+
labels=['', ''],
|
| 30 |
+
hole=0.6,
|
| 31 |
+
marker_colors=['#2CFCFF', '#444444'],
|
| 32 |
+
textinfo='none',
|
| 33 |
+
hoverinfo='skip',
|
| 34 |
+
sort=False,
|
| 35 |
+
domain=dict(x=[0.4, 0.95], y=[0.15, 0.85])
|
| 36 |
+
)])
|
| 37 |
+
fig.update_layout(
|
| 38 |
+
template='plotly_dark',
|
| 39 |
+
annotations=[
|
| 40 |
+
dict(
|
| 41 |
+
text=f"{percentage:.0f}%",
|
| 42 |
+
x=0.675, y=0.5,
|
| 43 |
+
font_size=20,
|
| 44 |
+
showarrow=False,
|
| 45 |
+
font=dict(color="white"),
|
| 46 |
+
xanchor="center", yanchor="middle"
|
| 47 |
+
),
|
| 48 |
+
dict(
|
| 49 |
+
text=title,
|
| 50 |
+
x=0.05, y=0.5,
|
| 51 |
+
showarrow=False,
|
| 52 |
+
font=dict(size=16, color="white"),
|
| 53 |
+
xanchor="left", yanchor="middle"
|
| 54 |
+
)
|
| 55 |
+
],
|
| 56 |
+
showlegend=False,
|
| 57 |
+
margin=dict(l=10, r=10, t=10, b=10),
|
| 58 |
+
width=320,
|
| 59 |
+
height=150
|
| 60 |
+
)
|
| 61 |
+
return fig
|
| 62 |
+
|
| 63 |
+
@staticmethod
|
| 64 |
+
def kpi_value(value, title="Valeur"):
|
| 65 |
+
width = 360
|
| 66 |
+
if value is None or (isinstance(value, str) and (value.strip() == "" or value.strip().isdigit() and len(value.strip()) > 8)):
|
| 67 |
+
fig = go.Figure()
|
| 68 |
+
fig.update_layout(
|
| 69 |
+
template='plotly_dark',
|
| 70 |
+
width=width,
|
| 71 |
+
height=125,
|
| 72 |
+
margin=dict(l=30, r=0, t=0, b=30),
|
| 73 |
+
xaxis=dict(visible=False),
|
| 74 |
+
yaxis=dict(visible=False),
|
| 75 |
+
plot_bgcolor='#111111',
|
| 76 |
+
paper_bgcolor='#111111',
|
| 77 |
+
annotations=[dict(
|
| 78 |
+
text="No data",
|
| 79 |
+
showarrow=False,
|
| 80 |
+
font=dict(size=16, color="white"),
|
| 81 |
+
x=0.5, y=0.5,
|
| 82 |
+
xanchor="center", yanchor="middle"
|
| 83 |
+
)]
|
| 84 |
+
)
|
| 85 |
+
return fig
|
| 86 |
+
try:
|
| 87 |
+
if isinstance(value, (int, float)):
|
| 88 |
+
formatted = f"{int(value)}" if float(value).is_integer() else f"{float(value):.2f}"
|
| 89 |
+
else:
|
| 90 |
+
td = pd.to_timedelta(value)
|
| 91 |
+
total_seconds = int(td.total_seconds())
|
| 92 |
+
hours, remainder = divmod(total_seconds, 3600)
|
| 93 |
+
minutes, seconds = divmod(remainder, 60)
|
| 94 |
+
formatted = f"{hours}h {minutes}m {seconds}s"
|
| 95 |
+
except (ValueError, TypeError):
|
| 96 |
+
try:
|
| 97 |
+
numeric_value = float(value)
|
| 98 |
+
formatted = f"{int(numeric_value)}" if numeric_value.is_integer() else f"{numeric_value:.2f}"
|
| 99 |
+
except (ValueError, TypeError):
|
| 100 |
+
formatted = str(value)
|
| 101 |
+
fig = go.Figure()
|
| 102 |
+
fig.add_annotation(
|
| 103 |
+
text=f"<b>{formatted}</b>",
|
| 104 |
+
x=0.5, y=0.5,
|
| 105 |
+
showarrow=False,
|
| 106 |
+
font=dict(size=24, color="white"),
|
| 107 |
+
xanchor="center", yanchor="middle"
|
| 108 |
+
)
|
| 109 |
+
fig.add_annotation(
|
| 110 |
+
text=title,
|
| 111 |
+
x=0.5, y=1.8,
|
| 112 |
+
showarrow=False,
|
| 113 |
+
font=dict(size=16, color="lightgray"),
|
| 114 |
+
xanchor="center", yanchor="middle"
|
| 115 |
+
)
|
| 116 |
+
fig.update_layout(
|
| 117 |
+
template='plotly_dark',
|
| 118 |
+
width=width,
|
| 119 |
+
height=150,
|
| 120 |
+
margin=dict(l=40, r=0, t=0, b=40),
|
| 121 |
+
xaxis=dict(visible=False),
|
| 122 |
+
yaxis=dict(visible=False),
|
| 123 |
+
plot_bgcolor='#111111',
|
| 124 |
+
paper_bgcolor='#111111'
|
| 125 |
+
)
|
| 126 |
+
return fig
|
| 127 |
+
|
| 128 |
+
@staticmethod
|
| 129 |
+
def get_max_part_id(df):
|
| 130 |
+
if not df.empty and 'Part ID' in df.columns:
|
| 131 |
+
try:
|
| 132 |
+
numeric_ids = pd.to_numeric(df['Part ID'], errors='coerce')
|
| 133 |
+
return int(numeric_ids.dropna().max())
|
| 134 |
+
except Exception:
|
| 135 |
+
return None
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def pareto(issues_df, error_col='Error Type'):
|
| 140 |
+
if issues_df is None or issues_df.empty:
|
| 141 |
+
fig = go.Figure()
|
| 142 |
+
fig.update_layout(
|
| 143 |
+
template='plotly_dark',
|
| 144 |
+
annotations=[dict(
|
| 145 |
+
text="No data",
|
| 146 |
+
showarrow=False,
|
| 147 |
+
font=dict(size=16, color="white")
|
| 148 |
+
)]
|
| 149 |
+
)
|
| 150 |
+
return fig
|
| 151 |
+
issues_df['Downtime Start'] = pd.to_datetime(issues_df['Downtime Start'], errors='coerce')
|
| 152 |
+
issues_df['Downtime End'] = pd.to_datetime(issues_df['Downtime End'], errors='coerce')
|
| 153 |
+
issues_df['Downtime Duration'] = (issues_df['Downtime End'] - issues_df[
|
| 154 |
+
'Downtime Start']).dt.total_seconds() / 60
|
| 155 |
+
issues_df = issues_df.dropna(subset=['Downtime Duration'])
|
| 156 |
+
grouped = issues_df.groupby(error_col)['Downtime Duration'].sum().sort_values(ascending=False)
|
| 157 |
+
if grouped.empty:
|
| 158 |
+
fig = go.Figure()
|
| 159 |
+
fig.update_layout(
|
| 160 |
+
template='plotly_dark',
|
| 161 |
+
annotations=[dict(
|
| 162 |
+
text="No Error",
|
| 163 |
+
showarrow=False,
|
| 164 |
+
font=dict(size=16, color="white")
|
| 165 |
+
)]
|
| 166 |
+
)
|
| 167 |
+
return fig
|
| 168 |
+
cumulative = grouped.cumsum() / grouped.sum() * 100
|
| 169 |
+
labels = grouped.index.tolist()
|
| 170 |
+
durations = grouped.values
|
| 171 |
+
fig = go.Figure()
|
| 172 |
+
fig.add_trace(
|
| 173 |
+
go.Bar(
|
| 174 |
+
x=labels,
|
| 175 |
+
y=durations,
|
| 176 |
+
name='Downtime (min)',
|
| 177 |
+
marker_color='#2CFCFF',
|
| 178 |
+
yaxis='y1'
|
| 179 |
+
)
|
| 180 |
+
)
|
| 181 |
+
fig.add_trace(go.Scatter(
|
| 182 |
+
x=labels,
|
| 183 |
+
y=cumulative,
|
| 184 |
+
name='Cumulative %',
|
| 185 |
+
yaxis='y2',
|
| 186 |
+
mode='lines+markers',
|
| 187 |
+
line=dict(color='orange', width=2),
|
| 188 |
+
marker=dict(size=8)
|
| 189 |
+
))
|
| 190 |
+
fig.update_layout(
|
| 191 |
+
template='plotly_dark',
|
| 192 |
+
title="Pareto of errors by downtime",
|
| 193 |
+
xaxis=dict(title="Errors"),
|
| 194 |
+
yaxis=dict(
|
| 195 |
+
title='Downtime (minutes)',
|
| 196 |
+
showgrid=False,
|
| 197 |
+
side='left'
|
| 198 |
+
),
|
| 199 |
+
yaxis2=dict(
|
| 200 |
+
title='Cumulative percentage (%)',
|
| 201 |
+
overlaying='y',
|
| 202 |
+
side='right',
|
| 203 |
+
range=[0, 110],
|
| 204 |
+
showgrid=False,
|
| 205 |
+
tickformat='%'
|
| 206 |
+
),
|
| 207 |
+
legend=dict(x=0.7, y=1.1),
|
| 208 |
+
margin=dict(l=70, r=70, t=50, b=50),
|
| 209 |
+
)
|
| 210 |
+
return fig
|
| 211 |
+
|
| 212 |
+
def general_block(self, all_tools_df, issues_df, efficiency_data):
|
| 213 |
+
header = f"Metrics Summary"
|
| 214 |
+
html_content = f"""
|
| 215 |
+
<div style="display: flex; align-items: center; justify-content: flex-start; width: 100%;">
|
| 216 |
+
<div style="flex: 0 0 2%; border-top: 1px solid white;"></div>
|
| 217 |
+
<h2 style="flex: 0 0 auto; margin: 0 10px;">{header}</h2>
|
| 218 |
+
<div style="flex: 1; border-top: 1px solid white;"></div>
|
| 219 |
+
</div>
|
| 220 |
+
"""
|
| 221 |
+
gr.HTML(html_content)
|
| 222 |
+
with gr.Row():
|
| 223 |
+
with gr.Group():
|
| 224 |
+
with gr.Row(height=125):
|
| 225 |
+
total_count = gr.Plot(
|
| 226 |
+
self.kpi_value(
|
| 227 |
+
value=self.get_max_part_id(all_tools_df),
|
| 228 |
+
title="Total Count"
|
| 229 |
+
)
|
| 230 |
+
)
|
| 231 |
+
total_time = gr.Plot(
|
| 232 |
+
self.kpi_value(
|
| 233 |
+
value=efficiency_data.get("opening_time", "0 days 00:00:00"),
|
| 234 |
+
title="Total Time"
|
| 235 |
+
)
|
| 236 |
+
)
|
| 237 |
+
mtbf_plot = gr.Plot(
|
| 238 |
+
self.kpi_value(
|
| 239 |
+
value=efficiency_data.get("MTBF", "0 days 00:00:00"),
|
| 240 |
+
title="MTBF"
|
| 241 |
+
)
|
| 242 |
+
)
|
| 243 |
+
mttr_plot = gr.Plot(
|
| 244 |
+
self.kpi_value(
|
| 245 |
+
value=efficiency_data.get("MTTR", "0 days 00:00:00"),
|
| 246 |
+
title="MTTR"
|
| 247 |
+
)
|
| 248 |
+
)
|
| 249 |
+
with gr.Row():
|
| 250 |
+
with gr.Column(scale=1):
|
| 251 |
+
with gr.Group():
|
| 252 |
+
with gr.Row(height=150):
|
| 253 |
+
oee_plot = gr.Plot(
|
| 254 |
+
self.kpi_rate(
|
| 255 |
+
percentage=efficiency_data.get('OEE', 0),
|
| 256 |
+
title="OEE"
|
| 257 |
+
)
|
| 258 |
+
)
|
| 259 |
+
with gr.Row(height=150):
|
| 260 |
+
quality_rate_plot = gr.Plot(
|
| 261 |
+
self.kpi_rate(
|
| 262 |
+
percentage=efficiency_data.get("quality_rate", 0),
|
| 263 |
+
title="Quality Rate"
|
| 264 |
+
)
|
| 265 |
+
)
|
| 266 |
+
with gr.Row(height=150):
|
| 267 |
+
availability_plot = gr.Plot(
|
| 268 |
+
self.kpi_rate(
|
| 269 |
+
percentage=efficiency_data.get("availability_rate", 0),
|
| 270 |
+
title="Availability"
|
| 271 |
+
)
|
| 272 |
+
)
|
| 273 |
+
with gr.Column(scale=10):
|
| 274 |
+
with gr.Group():
|
| 275 |
+
with gr.Row(height=450):
|
| 276 |
+
pareto = gr.Plot(
|
| 277 |
+
self.pareto(issues_df, error_col='Error Code')
|
| 278 |
+
)
|
| 279 |
+
self.plots = [
|
| 280 |
+
total_count, total_time,
|
| 281 |
+
oee_plot, quality_rate_plot, availability_plot,
|
| 282 |
+
mtbf_plot, mttr_plot,
|
| 283 |
+
pareto,
|
| 284 |
+
]
|
| 285 |
+
return self.plots
|
| 286 |
+
|
| 287 |
+
def refresh(self, all_tools_df, issues_df, efficiency_data):
|
| 288 |
+
return [
|
| 289 |
+
self.kpi_value(value=self.get_max_part_id(all_tools_df), title="Total Count"),
|
| 290 |
+
self.kpi_value(value=efficiency_data.get("opening_time", "0 days 00:00:00"), title="Total Time"),
|
| 291 |
+
self.kpi_rate(percentage=efficiency_data.get('OEE', 0), title="OEE"),
|
| 292 |
+
self.kpi_rate(percentage=efficiency_data.get("quality_rate", 0), title="Quality Rate"),
|
| 293 |
+
self.kpi_rate(percentage=efficiency_data.get("availability_rate", 0), title="Availability"),
|
| 294 |
+
self.kpi_value(value=efficiency_data.get("MTBF", "0 days 00:00:00"), title="MTBF"),
|
| 295 |
+
self.kpi_value(value=efficiency_data.get("MTTR", "0 days 00:00:00"), title="MTTR"),
|
| 296 |
+
self.pareto(issues_df, error_col='Error Code')
|
| 297 |
+
]
|
src/ui/graphs/tools_graphs.py
CHANGED
|
@@ -13,21 +13,23 @@ class ToolMetricsDisplay:
|
|
| 13 |
|
| 14 |
@staticmethod
|
| 15 |
def gauge(df, type=None, cote=None):
|
|
|
|
|
|
|
|
|
|
| 16 |
if df is None or df.empty:
|
| 17 |
fig = go.Figure()
|
| 18 |
fig.update_layout(
|
| 19 |
template='plotly_dark',
|
| 20 |
-
width=
|
| 21 |
-
height=
|
| 22 |
-
margin=
|
| 23 |
annotations=[dict(
|
| 24 |
-
text="No data
|
| 25 |
showarrow=False,
|
| 26 |
font=dict(size=16, color="white")
|
| 27 |
)]
|
| 28 |
)
|
| 29 |
return fig
|
| 30 |
-
|
| 31 |
column = f"{cote}_rolling_{type}"
|
| 32 |
idx = df['Timestamp'].idxmax()
|
| 33 |
value = df.loc[idx, column]
|
|
@@ -50,74 +52,110 @@ class ToolMetricsDisplay:
|
|
| 50 |
))
|
| 51 |
fig.update_layout(
|
| 52 |
template='plotly_dark',
|
| 53 |
-
width=
|
| 54 |
-
height=
|
| 55 |
-
margin=
|
| 56 |
)
|
| 57 |
return fig
|
| 58 |
|
| 59 |
def control_graph(self, df):
|
|
|
|
|
|
|
|
|
|
| 60 |
if df is None or df.empty:
|
| 61 |
fig = go.Figure()
|
| 62 |
fig.update_layout(
|
| 63 |
template='plotly_dark',
|
| 64 |
xaxis_title='Timestamp',
|
| 65 |
-
yaxis_title='
|
| 66 |
showlegend=True,
|
| 67 |
-
width=
|
| 68 |
-
height=
|
| 69 |
-
margin=
|
| 70 |
annotations=[dict(
|
| 71 |
-
text="No data
|
| 72 |
showarrow=False,
|
| 73 |
font=dict(size=20, color="white")
|
| 74 |
)]
|
| 75 |
)
|
| 76 |
return fig
|
| 77 |
-
|
| 78 |
fig = go.Figure()
|
| 79 |
-
fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.3] * len(df), mode='lines',
|
| 80 |
-
line=dict(dash='dot', color=self.pos_color, width=1), name='lsl pos'))
|
| 81 |
-
fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.5] * len(df), mode='lines',
|
| 82 |
-
line=dict(dash='dot', color=self.pos_color, width=1), name='usl pos'))
|
| 83 |
-
fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.2] * len(df), mode='lines',
|
| 84 |
-
line=dict(dash='dot', color=self.ori_color, width=1), name='lsl ori'))
|
| 85 |
-
fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.6] * len(df), mode='lines',
|
| 86 |
-
line=dict(dash='dot', color=self.ori_color, width=1), name='usl ori'))
|
| 87 |
fig.add_trace(
|
| 88 |
-
go.Scatter(
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
fig.add_trace(
|
| 91 |
-
go.Scatter(
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
fig.update_layout(
|
| 94 |
template='plotly_dark',
|
| 95 |
xaxis_title='Timestamp',
|
| 96 |
yaxis_title='Valeurs',
|
| 97 |
showlegend=True,
|
| 98 |
-
width=
|
| 99 |
-
height=
|
| 100 |
-
margin=
|
| 101 |
)
|
| 102 |
return fig
|
| 103 |
|
| 104 |
def normal_curve(self, df, cote=None):
|
|
|
|
|
|
|
|
|
|
| 105 |
if df is None or df.empty:
|
| 106 |
fig = go.Figure()
|
| 107 |
fig.update_layout(
|
| 108 |
template='plotly_dark',
|
| 109 |
showlegend=False,
|
| 110 |
-
width=
|
| 111 |
-
height=
|
| 112 |
-
margin=
|
| 113 |
annotations=[dict(
|
| 114 |
-
text="No data
|
| 115 |
showarrow=False,
|
| 116 |
font=dict(size=16, color="white")
|
| 117 |
)]
|
| 118 |
)
|
| 119 |
return fig
|
| 120 |
-
|
| 121 |
if cote == 'pos':
|
| 122 |
color = self.pos_color
|
| 123 |
lsl = 0.3
|
|
@@ -133,17 +171,30 @@ class ToolMetricsDisplay:
|
|
| 133 |
x = np.linspace(mu - 3 * std, mu + 3 * std, 100)
|
| 134 |
y = norm.pdf(x, mu, std)
|
| 135 |
fig = go.Figure()
|
| 136 |
-
fig.add_trace(
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
fig.update_layout(
|
| 142 |
template='plotly_dark',
|
| 143 |
showlegend=False,
|
| 144 |
-
width=
|
| 145 |
-
height=
|
| 146 |
-
margin=
|
| 147 |
)
|
| 148 |
return fig
|
| 149 |
|
|
@@ -162,26 +213,64 @@ class ToolMetricsDisplay:
|
|
| 162 |
gr.Markdown("### `Position`")
|
| 163 |
with gr.Group():
|
| 164 |
with gr.Row(height=250):
|
| 165 |
-
pos_normal_plot = gr.Plot(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
with gr.Row(height=150):
|
| 167 |
-
pos_cp_gauge = gr.Plot(
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
with gr.Column(scale=1):
|
| 170 |
gr.Markdown("### `Orientation`")
|
| 171 |
with gr.Group():
|
| 172 |
with gr.Row(height=250):
|
| 173 |
-
ori_normal_plot = gr.Plot(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
with gr.Row(height=150):
|
| 175 |
-
ori_cp_gauge = gr.Plot(
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
with gr.Column(scale=2):
|
| 178 |
gr.Markdown("### `Control card`")
|
| 179 |
with gr.Row(height=400):
|
| 180 |
-
control_plot = gr.Plot(
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
| 182 |
self.plots = [
|
| 183 |
pos_normal_plot, pos_cp_gauge, pos_cpk_gauge,
|
| 184 |
ori_normal_plot, ori_cp_gauge, ori_cpk_gauge,
|
| 185 |
control_plot
|
| 186 |
]
|
| 187 |
-
return self.plots
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
@staticmethod
|
| 15 |
def gauge(df, type=None, cote=None):
|
| 16 |
+
width = 205
|
| 17 |
+
height = 150
|
| 18 |
+
margin = dict(l=30, r=50, t=50, b=0)
|
| 19 |
if df is None or df.empty:
|
| 20 |
fig = go.Figure()
|
| 21 |
fig.update_layout(
|
| 22 |
template='plotly_dark',
|
| 23 |
+
width=width,
|
| 24 |
+
height=height,
|
| 25 |
+
margin=margin,
|
| 26 |
annotations=[dict(
|
| 27 |
+
text="No data",
|
| 28 |
showarrow=False,
|
| 29 |
font=dict(size=16, color="white")
|
| 30 |
)]
|
| 31 |
)
|
| 32 |
return fig
|
|
|
|
| 33 |
column = f"{cote}_rolling_{type}"
|
| 34 |
idx = df['Timestamp'].idxmax()
|
| 35 |
value = df.loc[idx, column]
|
|
|
|
| 52 |
))
|
| 53 |
fig.update_layout(
|
| 54 |
template='plotly_dark',
|
| 55 |
+
width=width,
|
| 56 |
+
height=height,
|
| 57 |
+
margin=margin,
|
| 58 |
)
|
| 59 |
return fig
|
| 60 |
|
| 61 |
def control_graph(self, df):
|
| 62 |
+
width = 832
|
| 63 |
+
height = 400
|
| 64 |
+
margin = dict(l=70, r=100, t=30, b=70)
|
| 65 |
if df is None or df.empty:
|
| 66 |
fig = go.Figure()
|
| 67 |
fig.update_layout(
|
| 68 |
template='plotly_dark',
|
| 69 |
xaxis_title='Timestamp',
|
| 70 |
+
yaxis_title='Values',
|
| 71 |
showlegend=True,
|
| 72 |
+
width=width,
|
| 73 |
+
height=height,
|
| 74 |
+
margin=margin,
|
| 75 |
annotations=[dict(
|
| 76 |
+
text="No data",
|
| 77 |
showarrow=False,
|
| 78 |
font=dict(size=20, color="white")
|
| 79 |
)]
|
| 80 |
)
|
| 81 |
return fig
|
|
|
|
| 82 |
fig = go.Figure()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
fig.add_trace(
|
| 84 |
+
go.Scatter(
|
| 85 |
+
x=df['Timestamp'], y=[0.3] * len(df),
|
| 86 |
+
mode='lines',
|
| 87 |
+
line=dict(dash='dot', color=self.pos_color, width=1),
|
| 88 |
+
name='lsl pos'
|
| 89 |
+
)
|
| 90 |
+
)
|
| 91 |
+
fig.add_trace(
|
| 92 |
+
go.Scatter(
|
| 93 |
+
x=df['Timestamp'], y=[0.5] * len(df),
|
| 94 |
+
mode='lines',
|
| 95 |
+
line=dict(dash='dot', color=self.pos_color, width=1),
|
| 96 |
+
name='usl pos'
|
| 97 |
+
)
|
| 98 |
+
)
|
| 99 |
+
fig.add_trace(
|
| 100 |
+
go.Scatter(
|
| 101 |
+
x=df['Timestamp'], y=[0.2] * len(df),
|
| 102 |
+
mode='lines',
|
| 103 |
+
line=dict(dash='dot', color=self.ori_color, width=1),
|
| 104 |
+
name='lsl ori'
|
| 105 |
+
)
|
| 106 |
+
)
|
| 107 |
+
fig.add_trace(
|
| 108 |
+
go.Scatter(
|
| 109 |
+
x=df['Timestamp'], y=[0.6] * len(df),
|
| 110 |
+
mode='lines',
|
| 111 |
+
line=dict(dash='dot', color=self.ori_color, width=1),
|
| 112 |
+
name='usl ori'
|
| 113 |
+
)
|
| 114 |
+
)
|
| 115 |
+
fig.add_trace(
|
| 116 |
+
go.Scatter(
|
| 117 |
+
x=df['Timestamp'], y=df['Position'],
|
| 118 |
+
mode='lines+markers', line=dict(color=self.pos_color),
|
| 119 |
+
name='pos'
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
fig.add_trace(
|
| 123 |
+
go.Scatter(
|
| 124 |
+
x=df['Timestamp'], y=df['Orientation'],
|
| 125 |
+
mode='lines+markers', line=dict(color=self.ori_color),
|
| 126 |
+
name='ori'
|
| 127 |
+
)
|
| 128 |
+
)
|
| 129 |
fig.update_layout(
|
| 130 |
template='plotly_dark',
|
| 131 |
xaxis_title='Timestamp',
|
| 132 |
yaxis_title='Valeurs',
|
| 133 |
showlegend=True,
|
| 134 |
+
width=width,
|
| 135 |
+
height=height,
|
| 136 |
+
margin=margin
|
| 137 |
)
|
| 138 |
return fig
|
| 139 |
|
| 140 |
def normal_curve(self, df, cote=None):
|
| 141 |
+
width = 410
|
| 142 |
+
height= 250
|
| 143 |
+
margin= dict(l=20, r=70, t=20, b=20)
|
| 144 |
if df is None or df.empty:
|
| 145 |
fig = go.Figure()
|
| 146 |
fig.update_layout(
|
| 147 |
template='plotly_dark',
|
| 148 |
showlegend=False,
|
| 149 |
+
width=width,
|
| 150 |
+
height=height,
|
| 151 |
+
margin=margin,
|
| 152 |
annotations=[dict(
|
| 153 |
+
text="No data",
|
| 154 |
showarrow=False,
|
| 155 |
font=dict(size=16, color="white")
|
| 156 |
)]
|
| 157 |
)
|
| 158 |
return fig
|
|
|
|
| 159 |
if cote == 'pos':
|
| 160 |
color = self.pos_color
|
| 161 |
lsl = 0.3
|
|
|
|
| 171 |
x = np.linspace(mu - 3 * std, mu + 3 * std, 100)
|
| 172 |
y = norm.pdf(x, mu, std)
|
| 173 |
fig = go.Figure()
|
| 174 |
+
fig.add_trace(
|
| 175 |
+
go.Scatter(
|
| 176 |
+
x=x, y=y,
|
| 177 |
+
mode='lines',
|
| 178 |
+
name='Normal Curve',
|
| 179 |
+
line=dict(color=color)
|
| 180 |
+
)
|
| 181 |
+
)
|
| 182 |
+
fig.add_shape(
|
| 183 |
+
type="line", x0=usl, y0=0, x1=usl, y1=max(y),
|
| 184 |
+
line=dict(color="red", width=1, dash="dot"),
|
| 185 |
+
name='usl'
|
| 186 |
+
)
|
| 187 |
+
fig.add_shape(
|
| 188 |
+
type="line", x0=lsl, y0=0, x1=lsl, y1=max(y),
|
| 189 |
+
line=dict(color="red", width=1, dash="dot"),
|
| 190 |
+
name='lsl'
|
| 191 |
+
)
|
| 192 |
fig.update_layout(
|
| 193 |
template='plotly_dark',
|
| 194 |
showlegend=False,
|
| 195 |
+
width=width,
|
| 196 |
+
height=height,
|
| 197 |
+
margin=margin
|
| 198 |
)
|
| 199 |
return fig
|
| 200 |
|
|
|
|
| 213 |
gr.Markdown("### `Position`")
|
| 214 |
with gr.Group():
|
| 215 |
with gr.Row(height=250):
|
| 216 |
+
pos_normal_plot = gr.Plot(
|
| 217 |
+
self.normal_curve(
|
| 218 |
+
df=df, cote='pos'
|
| 219 |
+
)
|
| 220 |
+
)
|
| 221 |
with gr.Row(height=150):
|
| 222 |
+
pos_cp_gauge = gr.Plot(
|
| 223 |
+
self.gauge(
|
| 224 |
+
df=df, type='cp', cote='pos'
|
| 225 |
+
)
|
| 226 |
+
)
|
| 227 |
+
pos_cpk_gauge = gr.Plot(
|
| 228 |
+
self.gauge(
|
| 229 |
+
df=df, type='cpk', cote='pos'
|
| 230 |
+
)
|
| 231 |
+
)
|
| 232 |
with gr.Column(scale=1):
|
| 233 |
gr.Markdown("### `Orientation`")
|
| 234 |
with gr.Group():
|
| 235 |
with gr.Row(height=250):
|
| 236 |
+
ori_normal_plot = gr.Plot(
|
| 237 |
+
self.normal_curve(
|
| 238 |
+
df=df, cote='ori'
|
| 239 |
+
)
|
| 240 |
+
)
|
| 241 |
with gr.Row(height=150):
|
| 242 |
+
ori_cp_gauge = gr.Plot(
|
| 243 |
+
self.gauge(
|
| 244 |
+
df=df, type='cp', cote='ori'
|
| 245 |
+
)
|
| 246 |
+
)
|
| 247 |
+
ori_cpk_gauge = gr.Plot(
|
| 248 |
+
self.gauge(
|
| 249 |
+
df=df, type='cpk', cote='ori'
|
| 250 |
+
)
|
| 251 |
+
)
|
| 252 |
with gr.Column(scale=2):
|
| 253 |
gr.Markdown("### `Control card`")
|
| 254 |
with gr.Row(height=400):
|
| 255 |
+
control_plot = gr.Plot(
|
| 256 |
+
self.control_graph(
|
| 257 |
+
df=df
|
| 258 |
+
)
|
| 259 |
+
)
|
| 260 |
self.plots = [
|
| 261 |
pos_normal_plot, pos_cp_gauge, pos_cpk_gauge,
|
| 262 |
ori_normal_plot, ori_cp_gauge, ori_cpk_gauge,
|
| 263 |
control_plot
|
| 264 |
]
|
| 265 |
+
return self.plots
|
| 266 |
+
|
| 267 |
+
def refresh(self, df):
|
| 268 |
+
return [
|
| 269 |
+
self.normal_curve(df, cote='pos'),
|
| 270 |
+
self.gauge(df, type='cp', cote='pos'),
|
| 271 |
+
self.gauge(df, type='cpk', cote='pos'),
|
| 272 |
+
self.normal_curve(df, cote='ori'),
|
| 273 |
+
self.gauge(df, type='cp', cote='ori'),
|
| 274 |
+
self.gauge(df, type='cpk', cote='ori'),
|
| 275 |
+
self.control_graph(df),
|
| 276 |
+
]
|