Add test execution dashboard page
This commit is contained in:
@@ -14,9 +14,12 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
|
from py_dvt_ate.data.repository import SQLiteRepository
|
||||||
|
from py_dvt_ate.framework.runner import TestRunner
|
||||||
from py_dvt_ate.instruments.factory import InstrumentConfig, InstrumentFactory, InstrumentSet
|
from py_dvt_ate.instruments.factory import InstrumentConfig, InstrumentFactory, InstrumentSet
|
||||||
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
||||||
from py_dvt_ate.simulation.server import ServerConfig, SimulationServer
|
from py_dvt_ate.simulation.server import ServerConfig, SimulationServer
|
||||||
|
from py_dvt_ate.tests.thermal.tempco import TempCoTest
|
||||||
|
|
||||||
# History buffer size for charts
|
# History buffer size for charts
|
||||||
HISTORY_SIZE = 500
|
HISTORY_SIZE = 500
|
||||||
@@ -105,12 +108,23 @@ def init_session_state() -> None:
|
|||||||
)
|
)
|
||||||
st.session_state.instruments = InstrumentFactory.create(config)
|
st.session_state.instruments = InstrumentFactory.create(config)
|
||||||
|
|
||||||
|
if "repository" not in st.session_state:
|
||||||
|
# Create test repository (in-memory for dashboard demo)
|
||||||
|
st.session_state.repository = SQLiteRepository(":memory:")
|
||||||
|
|
||||||
|
if "test_runner" not in st.session_state:
|
||||||
|
st.session_state.test_runner = TestRunner(st.session_state.repository)
|
||||||
|
|
||||||
if "history" not in st.session_state:
|
if "history" not in st.session_state:
|
||||||
st.session_state.history = SimulationHistory()
|
st.session_state.history = SimulationHistory()
|
||||||
if "running" not in st.session_state:
|
if "running" not in st.session_state:
|
||||||
st.session_state.running = False
|
st.session_state.running = False
|
||||||
if "last_update" not in st.session_state:
|
if "last_update" not in st.session_state:
|
||||||
st.session_state.last_update = time.time()
|
st.session_state.last_update = time.time()
|
||||||
|
if "test_running" not in st.session_state:
|
||||||
|
st.session_state.test_running = False
|
||||||
|
if "test_run_id" not in st.session_state:
|
||||||
|
st.session_state.test_run_id = None
|
||||||
# Note: time_multiplier, temp_setpoint, input_voltage, output_enabled,
|
# Note: time_multiplier, temp_setpoint, input_voltage, output_enabled,
|
||||||
# load_current are managed by their respective widgets via keys
|
# load_current are managed by their respective widgets via keys
|
||||||
|
|
||||||
@@ -457,6 +471,189 @@ self-heating effects!
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execution_page() -> None:
|
||||||
|
"""Test execution page for running DVT tests."""
|
||||||
|
st.header("Test Execution")
|
||||||
|
st.markdown("Run DVT characterisation tests using the virtual lab bench.")
|
||||||
|
|
||||||
|
# Test selection
|
||||||
|
st.subheader("Select Test")
|
||||||
|
test_options = {
|
||||||
|
"TempCo (Temperature Coefficient)": TempCoTest(),
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_test_name = st.selectbox(
|
||||||
|
"Available Tests",
|
||||||
|
options=list(test_options.keys()),
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_test = test_options[selected_test_name]
|
||||||
|
|
||||||
|
# Display test description
|
||||||
|
st.info(f"**{selected_test.name}**: {selected_test.description}")
|
||||||
|
|
||||||
|
# Test configuration
|
||||||
|
st.subheader("Test Configuration")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if selected_test.name == "tempco":
|
||||||
|
input_voltage = st.number_input(
|
||||||
|
"Input Voltage (V)",
|
||||||
|
min_value=0.0,
|
||||||
|
max_value=12.0,
|
||||||
|
value=5.0,
|
||||||
|
step=0.1,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
load_current = st.number_input(
|
||||||
|
"Load Current (mA)",
|
||||||
|
min_value=0.0,
|
||||||
|
max_value=500.0,
|
||||||
|
value=100.0,
|
||||||
|
step=10.0,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
settle_time = st.number_input(
|
||||||
|
"Settle Time (s)",
|
||||||
|
min_value=0.0,
|
||||||
|
max_value=60.0,
|
||||||
|
value=5.0,
|
||||||
|
step=1.0,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if selected_test.name == "tempco":
|
||||||
|
temp_min = st.number_input(
|
||||||
|
"Min Temperature (C)",
|
||||||
|
min_value=-40.0,
|
||||||
|
max_value=125.0,
|
||||||
|
value=-40.0,
|
||||||
|
step=5.0,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
temp_max = st.number_input(
|
||||||
|
"Max Temperature (C)",
|
||||||
|
min_value=-40.0,
|
||||||
|
max_value=125.0,
|
||||||
|
value=85.0,
|
||||||
|
step=5.0,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
temp_step = st.number_input(
|
||||||
|
"Temperature Step (C)",
|
||||||
|
min_value=1.0,
|
||||||
|
max_value=50.0,
|
||||||
|
value=25.0,
|
||||||
|
step=5.0,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate temperature points
|
||||||
|
if selected_test.name == "tempco":
|
||||||
|
import numpy as np
|
||||||
|
temperatures = list(np.arange(temp_min, temp_max + temp_step / 2, temp_step))
|
||||||
|
st.caption(f"Temperature points: {temperatures}")
|
||||||
|
|
||||||
|
test_config = {
|
||||||
|
"temperatures": temperatures,
|
||||||
|
"input_voltage": input_voltage,
|
||||||
|
"load_current": load_current / 1000.0, # Convert mA to A
|
||||||
|
"settle_time": settle_time,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
test_config = {}
|
||||||
|
|
||||||
|
# Run test button
|
||||||
|
st.subheader("Execution")
|
||||||
|
|
||||||
|
col1, col2 = st.columns([1, 3])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button(
|
||||||
|
"Run Test",
|
||||||
|
type="primary",
|
||||||
|
use_container_width=True,
|
||||||
|
disabled=st.session_state.test_running,
|
||||||
|
):
|
||||||
|
st.session_state.test_running = True
|
||||||
|
st.session_state.test_run_id = None
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.session_state.test_running:
|
||||||
|
st.info("🔄 Test is running...")
|
||||||
|
elif st.session_state.test_run_id:
|
||||||
|
st.success("✅ Test completed!")
|
||||||
|
else:
|
||||||
|
st.caption("Click 'Run Test' to start")
|
||||||
|
|
||||||
|
# Execute test if running
|
||||||
|
if st.session_state.test_running:
|
||||||
|
with st.spinner("Running test..."):
|
||||||
|
try:
|
||||||
|
runner: TestRunner = st.session_state.test_runner
|
||||||
|
instruments: InstrumentSet = st.session_state.instruments
|
||||||
|
|
||||||
|
# Run the test
|
||||||
|
run_id = runner.run_test(
|
||||||
|
test=selected_test,
|
||||||
|
instruments=instruments,
|
||||||
|
config=test_config,
|
||||||
|
operator="dashboard_user",
|
||||||
|
description=f"Dashboard execution: {selected_test_name}",
|
||||||
|
)
|
||||||
|
|
||||||
|
st.session_state.test_run_id = run_id
|
||||||
|
st.session_state.test_running = False
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Test execution failed: {e}")
|
||||||
|
st.session_state.test_running = False
|
||||||
|
|
||||||
|
# Display results if available
|
||||||
|
if st.session_state.test_run_id:
|
||||||
|
st.subheader("Test Results")
|
||||||
|
|
||||||
|
repository: SQLiteRepository = st.session_state.repository
|
||||||
|
run = repository.get_run(st.session_state.test_run_id)
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
with col1:
|
||||||
|
status_color = {"PASSED": "🟢", "FAILED": "🔴", "ERROR": "🟡"}.get(
|
||||||
|
run.status.value, "⚪"
|
||||||
|
)
|
||||||
|
st.metric("Status", f"{status_color} {run.status.value}")
|
||||||
|
with col2:
|
||||||
|
if run.completed_at and run.started_at:
|
||||||
|
duration = (run.completed_at - run.started_at).total_seconds()
|
||||||
|
st.metric("Duration", f"{duration:.1f} s")
|
||||||
|
else:
|
||||||
|
st.metric("Duration", "N/A")
|
||||||
|
with col3:
|
||||||
|
results = repository.get_results(st.session_state.test_run_id)
|
||||||
|
st.metric("Results", len(results))
|
||||||
|
|
||||||
|
# Show detailed results
|
||||||
|
if results:
|
||||||
|
st.markdown("#### Detailed Results")
|
||||||
|
results_data = []
|
||||||
|
for result in results:
|
||||||
|
results_data.append({
|
||||||
|
"Parameter": result.parameter,
|
||||||
|
"Value": f"{result.value:.6f}",
|
||||||
|
"Unit": result.unit or "",
|
||||||
|
"Lower Limit": f"{result.lower_limit:.6f}" if result.lower_limit is not None else "N/A",
|
||||||
|
"Upper Limit": f"{result.upper_limit:.6f}" if result.upper_limit is not None else "N/A",
|
||||||
|
"Status": "✅ PASS" if result.passed else "❌ FAIL",
|
||||||
|
})
|
||||||
|
st.table(results_data)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Main entry point for the Streamlit dashboard."""
|
"""Main entry point for the Streamlit dashboard."""
|
||||||
st.set_page_config(
|
st.set_page_config(
|
||||||
@@ -480,9 +677,20 @@ def main() -> None:
|
|||||||
# Sidebar controls (static - doesn't need fragment)
|
# Sidebar controls (static - doesn't need fragment)
|
||||||
display_controls()
|
display_controls()
|
||||||
|
|
||||||
|
# Create tabs for different views
|
||||||
|
tab1, tab2, tab3 = st.tabs(["🔬 Lab Bench", "🧪 Test Execution", "📊 Results Viewer"])
|
||||||
|
|
||||||
|
with tab1:
|
||||||
# Dynamic simulation display (uses fragment for smooth updates)
|
# Dynamic simulation display (uses fragment for smooth updates)
|
||||||
simulation_display()
|
simulation_display()
|
||||||
|
|
||||||
|
with tab2:
|
||||||
|
test_execution_page()
|
||||||
|
|
||||||
|
with tab3:
|
||||||
|
st.header("Results Viewer")
|
||||||
|
st.info("Results viewer coming soon in Task 17.3!")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user