testerkit 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- testerkit/__init__.py +68 -0
- testerkit/_docs/_assets/operator-ui/channels/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/dashboard/runs.png +0 -0
- testerkit/_docs/_assets/operator-ui/dashboard/stations.png +0 -0
- testerkit/_docs/_assets/operator-ui/events/filters.png +0 -0
- testerkit/_docs/_assets/operator-ui/events/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/explore/chart.png +0 -0
- testerkit/_docs/_assets/operator-ui/explore/filters.png +0 -0
- testerkit/_docs/_assets/operator-ui/explore/plot-controls.png +0 -0
- testerkit/_docs/_assets/operator-ui/files/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/launch/form.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/filters.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/pareto.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/ppk.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/yield.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-header.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-measurements.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-overview.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-steps.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/stats.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/designer.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/docs.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/fixtures.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/instruments.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/parts.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/profiles.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/stations.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/tests.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/uuts.png +0 -0
- testerkit/_docs/concepts/configuration/capabilities.md +447 -0
- testerkit/_docs/concepts/configuration/fixtures.md +335 -0
- testerkit/_docs/concepts/configuration/index.md +14 -0
- testerkit/_docs/concepts/configuration/parts.md +276 -0
- testerkit/_docs/concepts/configuration/stations.md +214 -0
- testerkit/_docs/concepts/data/data-stores.md +199 -0
- testerkit/_docs/concepts/data/event-log.md +185 -0
- testerkit/_docs/concepts/data/event-sourcing.md +76 -0
- testerkit/_docs/concepts/data/flight-streaming.md +58 -0
- testerkit/_docs/concepts/data/index.md +17 -0
- testerkit/_docs/concepts/data/sessions.md +78 -0
- testerkit/_docs/concepts/data/three-verbs.md +260 -0
- testerkit/_docs/concepts/execution/index.md +13 -0
- testerkit/_docs/concepts/execution/outcomes.md +199 -0
- testerkit/_docs/concepts/execution/step-hierarchy.md +170 -0
- testerkit/_docs/concepts/execution/step-manifest.md +115 -0
- testerkit/_docs/concepts/index.md +40 -0
- testerkit/_docs/concepts/overview/ai-integration.md +57 -0
- testerkit/_docs/concepts/overview/architecture.md +357 -0
- testerkit/_docs/concepts/overview/index.md +14 -0
- testerkit/_docs/concepts/overview/platform-vs-framework.md +147 -0
- testerkit/_docs/concepts/overview/pytest.md +53 -0
- testerkit/_docs/concepts/overview/tiers.md +46 -0
- testerkit/_docs/how-to/catalog/datasheet-to-test.md +169 -0
- testerkit/_docs/how-to/catalog/index.md +11 -0
- testerkit/_docs/how-to/configuration/configuring-stations.md +364 -0
- testerkit/_docs/how-to/configuration/custom-drivers.md +484 -0
- testerkit/_docs/how-to/configuration/index.md +13 -0
- testerkit/_docs/how-to/configuration/mock-mode.md +342 -0
- testerkit/_docs/how-to/data/benchmarking.md +120 -0
- testerkit/_docs/how-to/data/capture-an-artifact.md +84 -0
- testerkit/_docs/how-to/data/capture-waveform.md +110 -0
- testerkit/_docs/how-to/data/choosing-a-channel-verb.md +108 -0
- testerkit/_docs/how-to/data/compare-runs.md +120 -0
- testerkit/_docs/how-to/data/export-results.md +104 -0
- testerkit/_docs/how-to/data/find-flaky-tests.md +108 -0
- testerkit/_docs/how-to/data/grafana-dashboards.md +148 -0
- testerkit/_docs/how-to/data/index.md +23 -0
- testerkit/_docs/how-to/data/mcp-debug-failures.md +125 -0
- testerkit/_docs/how-to/data/mcp-query-runs.md +120 -0
- testerkit/_docs/how-to/data/querying-channels.md +117 -0
- testerkit/_docs/how-to/data/querying-events.md +85 -0
- testerkit/_docs/how-to/data/stream-live-channel.md +134 -0
- testerkit/_docs/how-to/execution/custom-operator-ui.md +136 -0
- testerkit/_docs/how-to/execution/index.md +21 -0
- testerkit/_docs/how-to/execution/limits.md +193 -0
- testerkit/_docs/how-to/execution/managing-sessions.md +100 -0
- testerkit/_docs/how-to/execution/multi-uut-testing.md +211 -0
- testerkit/_docs/how-to/execution/operator-prompts.md +151 -0
- testerkit/_docs/how-to/execution/profiles.md +304 -0
- testerkit/_docs/how-to/execution/spec-driven-testing.md +169 -0
- testerkit/_docs/how-to/execution/test-context.md +212 -0
- testerkit/_docs/how-to/execution/traceability.md +252 -0
- testerkit/_docs/how-to/execution/vector-expansion.md +394 -0
- testerkit/_docs/how-to/execution/writing-tests.md +363 -0
- testerkit/_docs/how-to/index.md +59 -0
- testerkit/_docs/how-to/overview/index.md +11 -0
- testerkit/_docs/how-to/overview/mcp-integration.md +168 -0
- testerkit/_docs/how-to/overview/operator-ui-tour.md +234 -0
- testerkit/_docs/integration/data/grafana.md +162 -0
- testerkit/_docs/integration/data/index.md +15 -0
- testerkit/_docs/integration/data/lakehouse-import.md +226 -0
- testerkit/_docs/integration/data/logging.md +131 -0
- testerkit/_docs/integration/data/results-api.md +51 -0
- testerkit/_docs/integration/index.md +20 -0
- testerkit/_docs/integration/runtime/harness.md +281 -0
- testerkit/_docs/integration/runtime/index.md +13 -0
- testerkit/_docs/integration/runtime/instruments.md +284 -0
- testerkit/_docs/integration/runtime/pytest-existing.md +315 -0
- testerkit/_docs/reference/catalog/cookbook.md +389 -0
- testerkit/_docs/reference/catalog/index.md +11 -0
- testerkit/_docs/reference/catalog/schema.md +375 -0
- testerkit/_docs/reference/cli.md +660 -0
- testerkit/_docs/reference/configuration.md +406 -0
- testerkit/_docs/reference/data/channels-schema.md +145 -0
- testerkit/_docs/reference/data/event-types.md +554 -0
- testerkit/_docs/reference/data/events-schema.md +120 -0
- testerkit/_docs/reference/data/files-schema.md +141 -0
- testerkit/_docs/reference/data/index.md +18 -0
- testerkit/_docs/reference/data/models.md +2229 -0
- testerkit/_docs/reference/data/outputs.md +82 -0
- testerkit/_docs/reference/data/parquet-schema.md +529 -0
- testerkit/_docs/reference/data/performance-limits.md +224 -0
- testerkit/_docs/reference/data/query-api.md +247 -0
- testerkit/_docs/reference/index.md +69 -0
- testerkit/_docs/reference/operator-ui/channels/detail.md +82 -0
- testerkit/_docs/reference/operator-ui/channels/list.md +70 -0
- testerkit/_docs/reference/operator-ui/dashboard.md +87 -0
- testerkit/_docs/reference/operator-ui/designer.md +167 -0
- testerkit/_docs/reference/operator-ui/events.md +89 -0
- testerkit/_docs/reference/operator-ui/files.md +74 -0
- testerkit/_docs/reference/operator-ui/fixtures.md +100 -0
- testerkit/_docs/reference/operator-ui/instruments.md +126 -0
- testerkit/_docs/reference/operator-ui/launch.md +79 -0
- testerkit/_docs/reference/operator-ui/live.md +102 -0
- testerkit/_docs/reference/operator-ui/measurements.md +159 -0
- testerkit/_docs/reference/operator-ui/metrics.md +217 -0
- testerkit/_docs/reference/operator-ui/parts.md +98 -0
- testerkit/_docs/reference/operator-ui/profiles.md +103 -0
- testerkit/_docs/reference/operator-ui/results/detail.md +214 -0
- testerkit/_docs/reference/operator-ui/results/list.md +106 -0
- testerkit/_docs/reference/operator-ui/stations.md +98 -0
- testerkit/_docs/reference/operator-ui/tests.md +173 -0
- testerkit/_docs/reference/operator-ui/uuts.md +67 -0
- testerkit/_docs/reference/overview/index.md +11 -0
- testerkit/_docs/reference/overview/pytest-native.md +142 -0
- testerkit/_docs/reference/overview/skills.md +177 -0
- testerkit/_docs/reference/pytest/fixtures.md +342 -0
- testerkit/_docs/reference/pytest/index.md +12 -0
- testerkit/_docs/reference/pytest/markers.md +257 -0
- testerkit/_docs/reference/runtime/api.md +246 -0
- testerkit/_docs/reference/runtime/client.md +280 -0
- testerkit/_docs/reference/runtime/connect.md +140 -0
- testerkit/_docs/reference/runtime/index.md +13 -0
- testerkit/_docs/tutorial/01-first-test.md +113 -0
- testerkit/_docs/tutorial/02-mock-instruments.md +88 -0
- testerkit/_docs/tutorial/03-fixtures.md +169 -0
- testerkit/_docs/tutorial/04-limits.md +139 -0
- testerkit/_docs/tutorial/05-configuration.md +194 -0
- testerkit/_docs/tutorial/06-specifications.md +273 -0
- testerkit/_docs/tutorial/07-real-instruments.md +245 -0
- testerkit/_docs/tutorial/08-capabilities.md +223 -0
- testerkit/_docs/tutorial/09-production.md +374 -0
- testerkit/_docs/tutorial/10-live-monitoring.md +156 -0
- testerkit/_docs/tutorial/11-waveforms-and-evidence.md +169 -0
- testerkit/_docs/tutorial/12-continuous-monitoring.md +145 -0
- testerkit/_docs/tutorial/13-parallel-testing.md +233 -0
- testerkit/_docs/tutorial/index.md +61 -0
- testerkit/_docs/tutorial/quickstart.md +282 -0
- testerkit/analysis/__init__.py +9 -0
- testerkit/analysis/_common.py +24 -0
- testerkit/analysis/measurement_facets.py +524 -0
- testerkit/analysis/measurements_query.py +1458 -0
- testerkit/analysis/metrics.py +389 -0
- testerkit/analysis/runs_query.py +434 -0
- testerkit/analysis/steps_query.py +413 -0
- testerkit/api/__init__.py +6 -0
- testerkit/api/_mime.py +71 -0
- testerkit/api/app.py +1171 -0
- testerkit/api/dialogs/__init__.py +36 -0
- testerkit/api/dialogs/manager.py +617 -0
- testerkit/api/dialogs/models.py +79 -0
- testerkit/api/models.py +80 -0
- testerkit/api/responses.py +205 -0
- testerkit/api/runner.py +222 -0
- testerkit/api/schemas.py +311 -0
- testerkit/benchmark/__init__.py +34 -0
- testerkit/benchmark/concurrency.py +248 -0
- testerkit/benchmark/core.py +232 -0
- testerkit/benchmark/runner.py +628 -0
- testerkit/benchmark/scenario.py +313 -0
- testerkit/benchmark/system.py +191 -0
- testerkit/benchmark/workloads.py +507 -0
- testerkit/catalog/__init__.py +6 -0
- testerkit/catalog/generic/generic_dmm.yaml +61 -0
- testerkit/catalog/generic/generic_eload.yaml +31 -0
- testerkit/catalog/generic/generic_fgen.yaml +56 -0
- testerkit/catalog/generic/generic_oscilloscope.yaml +45 -0
- testerkit/catalog/generic/generic_psu.yaml +77 -0
- testerkit/catalog/generic/generic_smu.yaml +73 -0
- testerkit/channels.py +538 -0
- testerkit/cli/__init__.py +32 -0
- testerkit/cli/_common.py +113 -0
- testerkit/cli/_time.py +179 -0
- testerkit/cli/benchmark_cmd.py +56 -0
- testerkit/cli/catalog_cmd.py +39 -0
- testerkit/cli/daemon.py +157 -0
- testerkit/cli/data_cmd.py +1040 -0
- testerkit/cli/discover_cmd.py +94 -0
- testerkit/cli/docs_cmd.py +100 -0
- testerkit/cli/instrument.py +172 -0
- testerkit/cli/mcp_cmd.py +37 -0
- testerkit/cli/metrics.py +366 -0
- testerkit/cli/project.py +312 -0
- testerkit/cli/root.py +14 -0
- testerkit/cli/runs.py +412 -0
- testerkit/cli/schema_cmd.py +87 -0
- testerkit/cli/serve_cmd.py +50 -0
- testerkit/cli/setup_cmd.py +604 -0
- testerkit/cli/station.py +294 -0
- testerkit/cli/validate.py +112 -0
- testerkit/client.py +463 -0
- testerkit/connect.py +635 -0
- testerkit/data/__init__.py +8 -0
- testerkit/data/_accumulator_pool.py +455 -0
- testerkit/data/_atomic.py +59 -0
- testerkit/data/_collection_indices.py +67 -0
- testerkit/data/_daemon_lifecycle.py +535 -0
- testerkit/data/_duckdb_daemon.py +695 -0
- testerkit/data/_duckdb_flight_server.py +813 -0
- testerkit/data/_event_filters.py +15 -0
- testerkit/data/_event_reader.py +101 -0
- testerkit/data/_flight_errors.py +199 -0
- testerkit/data/_flight_query.py +202 -0
- testerkit/data/_flight_retry.py +148 -0
- testerkit/data/_flight_subscribe.py +67 -0
- testerkit/data/_index_epoch.py +342 -0
- testerkit/data/_ipc_writer.py +185 -0
- testerkit/data/_json_safe.py +63 -0
- testerkit/data/_process.py +18 -0
- testerkit/data/_push_relay.py +124 -0
- testerkit/data/_runs_duckdb_daemon.py +2704 -0
- testerkit/data/_session_reaper.py +110 -0
- testerkit/data/_sql_helpers.py +30 -0
- testerkit/data/_store.py +24 -0
- testerkit/data/backends/__init__.py +7 -0
- testerkit/data/backends/_event_accumulator.py +957 -0
- testerkit/data/backends/_row_helpers.py +1172 -0
- testerkit/data/backends/parquet.py +1143 -0
- testerkit/data/backends/protocol.py +35 -0
- testerkit/data/channels/__init__.py +8 -0
- testerkit/data/channels/_flight_daemon.py +75 -0
- testerkit/data/channels/client.py +242 -0
- testerkit/data/channels/flight_manager.py +88 -0
- testerkit/data/channels/index.py +664 -0
- testerkit/data/channels/models.py +418 -0
- testerkit/data/channels/server.py +162 -0
- testerkit/data/channels/store.py +1481 -0
- testerkit/data/data_dir.py +61 -0
- testerkit/data/duckdb_manager.py +79 -0
- testerkit/data/event_log.py +384 -0
- testerkit/data/event_store.py +768 -0
- testerkit/data/events.py +1025 -0
- testerkit/data/exporters/__init__.py +6 -0
- testerkit/data/exporters/_helpers.py +40 -0
- testerkit/data/exporters/csv_exporter.py +168 -0
- testerkit/data/exporters/hdf5.py +234 -0
- testerkit/data/exporters/json_exporter.py +229 -0
- testerkit/data/exporters/mdf4.py +201 -0
- testerkit/data/exporters/stdf.py +255 -0
- testerkit/data/exporters/tdms.py +292 -0
- testerkit/data/files/__init__.py +71 -0
- testerkit/data/files/_backend.py +207 -0
- testerkit/data/files/_catalog_daemon.py +84 -0
- testerkit/data/files/catalog.py +218 -0
- testerkit/data/files/catalog_manager.py +325 -0
- testerkit/data/files/models.py +60 -0
- testerkit/data/files/serializers.py +414 -0
- testerkit/data/files/store.py +463 -0
- testerkit/data/files/streaming.py +783 -0
- testerkit/data/models.py +604 -0
- testerkit/data/ref.py +152 -0
- testerkit/data/retention.py +297 -0
- testerkit/data/run_store.py +367 -0
- testerkit/data/runs_duckdb_manager.py +103 -0
- testerkit/data/schema_dispatch.py +191 -0
- testerkit/data/schema_migrate.py +76 -0
- testerkit/data/schema_versions.py +85 -0
- testerkit/data/schemas.py +288 -0
- testerkit/data/subscribers/__init__.py +48 -0
- testerkit/data/subscribers/_base.py +20 -0
- testerkit/data/subscribers/_output_file.py +25 -0
- testerkit/data/subscribers/replay.py +55 -0
- testerkit/environment.py +84 -0
- testerkit/execution/__init__.py +11 -0
- testerkit/execution/_git.py +190 -0
- testerkit/execution/_state.py +740 -0
- testerkit/execution/accessors.py +54 -0
- testerkit/execution/audit.py +51 -0
- testerkit/execution/cascade.py +85 -0
- testerkit/execution/connections.py +491 -0
- testerkit/execution/harness.py +1931 -0
- testerkit/execution/instrument_events.py +42 -0
- testerkit/execution/limits.py +154 -0
- testerkit/execution/metadata.py +102 -0
- testerkit/execution/mocks.py +63 -0
- testerkit/execution/profiles.py +616 -0
- testerkit/execution/run_scope.py +1331 -0
- testerkit/execution/session_scope.py +196 -0
- testerkit/execution/sidecar.py +280 -0
- testerkit/execution/site_runner.py +705 -0
- testerkit/execution/sites.py +183 -0
- testerkit/execution/sync.py +294 -0
- testerkit/execution/uut_provider.py +231 -0
- testerkit/execution/vectors.py +205 -0
- testerkit/execution/verify.py +396 -0
- testerkit/expand.py +75 -0
- testerkit/files.py +235 -0
- testerkit/fixtures/__init__.py +6 -0
- testerkit/fixtures/manager.py +357 -0
- testerkit/grafana/__init__.py +1 -0
- testerkit/grafana/bootstrap.py +61 -0
- testerkit/grafana/cli.py +314 -0
- testerkit/grafana/dashboards/asset_utilization.json +92 -0
- testerkit/grafana/dashboards/channel_explorer.json +77 -0
- testerkit/grafana/dashboards/event_log.json +92 -0
- testerkit/grafana/dashboards/failure_pareto.json +104 -0
- testerkit/grafana/dashboards/measurement_distribution.json +97 -0
- testerkit/grafana/dashboards/measurement_trend.json +161 -0
- testerkit/grafana/dashboards/station_comparison.json +95 -0
- testerkit/grafana/dashboards/test_duration.json +104 -0
- testerkit/grafana/dashboards/unit_traceability.json +73 -0
- testerkit/grafana/dashboards/yield_overview.json +189 -0
- testerkit/grafana/provisioning/dashboards.yaml.j2 +12 -0
- testerkit/grafana/provisioning/datasources.yaml.j2 +17 -0
- testerkit/grafana/server.py +282 -0
- testerkit/init.py +724 -0
- testerkit/instruments/__init__.py +10 -0
- testerkit/instruments/base.py +82 -0
- testerkit/instruments/discovery/__init__.py +62 -0
- testerkit/instruments/discovery/_base.py +174 -0
- testerkit/instruments/discovery/lxi.py +152 -0
- testerkit/instruments/discovery/ni.py +95 -0
- testerkit/instruments/discovery/serial.py +73 -0
- testerkit/instruments/discovery/visa.py +98 -0
- testerkit/instruments/lifecycle.py +213 -0
- testerkit/instruments/loader.py +79 -0
- testerkit/instruments/locks.py +178 -0
- testerkit/instruments/mocks.py +230 -0
- testerkit/instruments/observer.py +270 -0
- testerkit/instruments/observers/__init__.py +36 -0
- testerkit/instruments/observers/_base.py +68 -0
- testerkit/instruments/observers/daqmx.py +70 -0
- testerkit/instruments/observers/descriptor.py +69 -0
- testerkit/instruments/observers/generic.py +92 -0
- testerkit/instruments/observers/lantz.py +31 -0
- testerkit/instruments/observers/modbus.py +96 -0
- testerkit/instruments/observers/motion.py +68 -0
- testerkit/instruments/observers/ni_modular.py +73 -0
- testerkit/instruments/observers/ophyd.py +80 -0
- testerkit/instruments/observers/pymeasure.py +120 -0
- testerkit/instruments/observers/qcodes.py +66 -0
- testerkit/instruments/observers/scpi.py +88 -0
- testerkit/instruments/observers/tektronix.py +57 -0
- testerkit/instruments/observers/visa.py +103 -0
- testerkit/instruments/pool.py +459 -0
- testerkit/instruments/proxy.py +59 -0
- testerkit/instruments/route_manager.py +329 -0
- testerkit/instruments/routed_proxy.py +88 -0
- testerkit/instruments/server.py +508 -0
- testerkit/instruments/switch.py +43 -0
- testerkit/instruments/visa.py +294 -0
- testerkit/matching/__init__.py +6 -0
- testerkit/matching/service.py +969 -0
- testerkit/mcp/__init__.py +6 -0
- testerkit/mcp/server.py +681 -0
- testerkit/mcp/tools.py +1868 -0
- testerkit/models/__init__.py +18 -0
- testerkit/models/capability.py +598 -0
- testerkit/models/catalog.py +76 -0
- testerkit/models/data_options.py +153 -0
- testerkit/models/enums.py +334 -0
- testerkit/models/instrument.py +152 -0
- testerkit/models/instrument_asset.py +25 -0
- testerkit/models/part.py +271 -0
- testerkit/models/part_manifest.py +96 -0
- testerkit/models/project.py +114 -0
- testerkit/models/station.py +140 -0
- testerkit/models/test_config.py +748 -0
- testerkit/ontology/__init__.py +39 -0
- testerkit/ontology/schema.py +188 -0
- testerkit/ontology/testerkit.yaml +1420 -0
- testerkit/parts/__init__.py +8 -0
- testerkit/parts/context.py +181 -0
- testerkit/parts/folder.py +251 -0
- testerkit/parts/loader.py +27 -0
- testerkit/prompts/__init__.py +17 -0
- testerkit/prompts/core.py +133 -0
- testerkit/pytest_plugin/__init__.py +1287 -0
- testerkit/pytest_plugin/autouse.py +369 -0
- testerkit/pytest_plugin/helpers.py +354 -0
- testerkit/pytest_plugin/hooks.py +1982 -0
- testerkit/pytest_plugin/markers.py +203 -0
- testerkit/pytest_plugin/retry.py +37 -0
- testerkit/pytest_plugin/sweeps.py +117 -0
- testerkit/queries.py +78 -0
- testerkit/reports/__init__.py +6 -0
- testerkit/reports/core.py +492 -0
- testerkit/reports/datasheet.py +831 -0
- testerkit/reports/templates/datasheet.html +383 -0
- testerkit/reports/templates/default.html +153 -0
- testerkit/sbom.py +142 -0
- testerkit/schema_export.py +101 -0
- testerkit/signals.py +63 -0
- testerkit/skills/README.md +58 -0
- testerkit/skills/templates/project-instructions.md +125 -0
- testerkit/skills/testerkit-analysis/SKILL.md +87 -0
- testerkit/skills/testerkit-capture/SKILL.md +114 -0
- testerkit/skills/testerkit-data/SKILL.md +138 -0
- testerkit/skills/testerkit-datasheets/SKILL.md +100 -0
- testerkit/skills/testerkit-datasheets/agents/scaffold-writer.md +115 -0
- testerkit/skills/testerkit-datasheets/agents/section-extractor.md +98 -0
- testerkit/skills/testerkit-datasheets/agents/section-reviewer.md +153 -0
- testerkit/skills/testerkit-datasheets/agents/section-splitter.md +89 -0
- testerkit/skills/testerkit-datasheets/agents/section-writer.md +226 -0
- testerkit/skills/testerkit-datasheets/references/catalog-pipeline.md +271 -0
- testerkit/skills/testerkit-datasheets/references/process-queue.md +42 -0
- testerkit/skills/testerkit-datasheets/references/scaffold.md +156 -0
- testerkit/skills/testerkit-datasheets/references/test-pipeline.md +312 -0
- testerkit/skills/testerkit-debug/SKILL.md +127 -0
- testerkit/skills/testerkit-interactive/SKILL.md +188 -0
- testerkit/skills/testerkit-interactive/references/live-ui-patterns.md +102 -0
- testerkit/skills/testerkit-mocks/SKILL.md +128 -0
- testerkit/skills/testerkit-parts/SKILL.md +126 -0
- testerkit/skills/testerkit-profiles/SKILL.md +139 -0
- testerkit/skills/testerkit-sites/SKILL.md +140 -0
- testerkit/skills/testerkit-stations/SKILL.md +146 -0
- testerkit/skills/testerkit-tests/SKILL.md +136 -0
- testerkit/store.py +1639 -0
- testerkit/ui/__init__.py +71 -0
- testerkit/ui/_asgi.py +162 -0
- testerkit/ui/app.py +32 -0
- testerkit/ui/components/__init__.py +13 -0
- testerkit/ui/components/artifact_viewer.py +250 -0
- testerkit/ui/components/channel_values.py +159 -0
- testerkit/ui/components/event_timeline.py +222 -0
- testerkit/ui/components/execution_gantt.py +210 -0
- testerkit/ui/components/file_streams.py +147 -0
- testerkit/ui/components/instrument_activity.py +146 -0
- testerkit/ui/components/session_table.py +118 -0
- testerkit/ui/pages/__init__.py +25 -0
- testerkit/ui/pages/channels/__init__.py +4 -0
- testerkit/ui/pages/channels/detail.py +932 -0
- testerkit/ui/pages/channels/list.py +515 -0
- testerkit/ui/pages/dashboard.py +236 -0
- testerkit/ui/pages/designer/__init__.py +5 -0
- testerkit/ui/pages/designer/graph.py +635 -0
- testerkit/ui/pages/designer/matching.py +663 -0
- testerkit/ui/pages/designer/page.py +593 -0
- testerkit/ui/pages/designer/properties.py +591 -0
- testerkit/ui/pages/designer/state.py +468 -0
- testerkit/ui/pages/docs/__init__.py +6 -0
- testerkit/ui/pages/docs/index.py +95 -0
- testerkit/ui/pages/docs/page.py +698 -0
- testerkit/ui/pages/events/__init__.py +3 -0
- testerkit/ui/pages/events/list.py +263 -0
- testerkit/ui/pages/explore.py +1108 -0
- testerkit/ui/pages/files/__init__.py +4 -0
- testerkit/ui/pages/files/detail.py +426 -0
- testerkit/ui/pages/files/list.py +447 -0
- testerkit/ui/pages/fixtures/__init__.py +8 -0
- testerkit/ui/pages/fixtures/detail.py +280 -0
- testerkit/ui/pages/fixtures/edit.py +370 -0
- testerkit/ui/pages/fixtures/list.py +180 -0
- testerkit/ui/pages/fixtures/new.py +195 -0
- testerkit/ui/pages/instruments/__init__.py +4 -0
- testerkit/ui/pages/instruments/detail.py +320 -0
- testerkit/ui/pages/instruments/edit.py +482 -0
- testerkit/ui/pages/instruments/list.py +249 -0
- testerkit/ui/pages/instruments/new.py +200 -0
- testerkit/ui/pages/launch.py +261 -0
- testerkit/ui/pages/live.py +124 -0
- testerkit/ui/pages/metrics_page.py +1229 -0
- testerkit/ui/pages/parts/__init__.py +11 -0
- testerkit/ui/pages/parts/detail.py +202 -0
- testerkit/ui/pages/parts/edit.py +310 -0
- testerkit/ui/pages/parts/list.py +160 -0
- testerkit/ui/pages/parts/new.py +170 -0
- testerkit/ui/pages/parts/requirements.py +153 -0
- testerkit/ui/pages/parts/stations.py +199 -0
- testerkit/ui/pages/profiles/__init__.py +4 -0
- testerkit/ui/pages/profiles/detail.py +113 -0
- testerkit/ui/pages/profiles/list.py +86 -0
- testerkit/ui/pages/results/__init__.py +4 -0
- testerkit/ui/pages/results/detail.py +631 -0
- testerkit/ui/pages/results/list.py +230 -0
- testerkit/ui/pages/stations/__init__.py +4 -0
- testerkit/ui/pages/stations/detail.py +256 -0
- testerkit/ui/pages/stations/edit.py +273 -0
- testerkit/ui/pages/stations/list.py +180 -0
- testerkit/ui/pages/stations/new.py +311 -0
- testerkit/ui/pages/tests/__init__.py +4 -0
- testerkit/ui/pages/tests/detail.py +152 -0
- testerkit/ui/pages/tests/list.py +194 -0
- testerkit/ui/pages/uuts/__init__.py +4 -0
- testerkit/ui/pages/uuts/list.py +97 -0
- testerkit/ui/shared/__init__.py +8 -0
- testerkit/ui/shared/components.py +1826 -0
- testerkit/ui/shared/dialogs.py +105 -0
- testerkit/ui/shared/event_binding.py +194 -0
- testerkit/ui/shared/layout.py +314 -0
- testerkit/ui/shared/services.py +1719 -0
- testerkit/ui/shared/timestamps.py +21 -0
- testerkit/ui/static/branding/favicon-16.png +0 -0
- testerkit/ui/static/branding/favicon-32.png +0 -0
- testerkit/ui/static/branding/favicon-48.png +0 -0
- testerkit/ui/static/branding/testerkit-icon-256.png +0 -0
- testerkit/ui/static/branding/testerkit-mark.svg +1 -0
- testerkit/ui/static/branding/testerkit-wordmark.svg +1 -0
- testerkit/ui/static/global.css +465 -0
- testerkit/utils/__init__.py +17 -0
- testerkit/utils/enum_meta.py +800 -0
- testerkit/utils/paths.py +75 -0
- testerkit/utils/ranges.py +257 -0
- testerkit/validation.py +82 -0
- testerkit/verbs.py +142 -0
- testerkit-0.4.0.dist-info/METADATA +250 -0
- testerkit-0.4.0.dist-info/RECORD +521 -0
- testerkit-0.4.0.dist-info/WHEEL +4 -0
- testerkit-0.4.0.dist-info/entry_points.txt +5 -0
- testerkit-0.4.0.dist-info/licenses/LICENSE +191 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""New part creation page."""
|
|
2
|
+
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
|
|
5
|
+
from testerkit.ui.shared.components import validate_resource_id
|
|
6
|
+
from testerkit.ui.shared.layout import create_layout
|
|
7
|
+
from testerkit.ui.shared.services import create_part, discover_parts
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@ui.page("/parts/new")
|
|
11
|
+
def new_part_page():
|
|
12
|
+
"""Create a new part."""
|
|
13
|
+
create_layout("New Part")
|
|
14
|
+
|
|
15
|
+
# Get existing part IDs to check for duplicates
|
|
16
|
+
existing_ids = {p["id"] for p in discover_parts()}
|
|
17
|
+
|
|
18
|
+
# Form state
|
|
19
|
+
form = {
|
|
20
|
+
"part_id": "",
|
|
21
|
+
"name": "",
|
|
22
|
+
"description": "",
|
|
23
|
+
}
|
|
24
|
+
validation = {
|
|
25
|
+
"id_error": "",
|
|
26
|
+
"name_error": "",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
with ui.column().classes("w-full p-6 gap-6"):
|
|
30
|
+
# Header
|
|
31
|
+
with ui.row().classes("items-center gap-2"):
|
|
32
|
+
ui.icon("add_circle").classes("text-slate-600")
|
|
33
|
+
ui.label("Create New Part").classes("text-lg font-semibold text-slate-700")
|
|
34
|
+
|
|
35
|
+
# Form card
|
|
36
|
+
with ui.card().classes("w-full max-w-xl"):
|
|
37
|
+
with ui.card_section():
|
|
38
|
+
ui.label("Part Information").classes("font-semibold mb-4")
|
|
39
|
+
|
|
40
|
+
with ui.column().classes("gap-4 w-full"):
|
|
41
|
+
# Part ID
|
|
42
|
+
with ui.column().classes("gap-1 w-full"):
|
|
43
|
+
ui.label("Part ID").classes("text-sm font-medium text-slate-700")
|
|
44
|
+
ui.label(
|
|
45
|
+
"Unique identifier (lowercase, letters/numbers/hyphens only)"
|
|
46
|
+
).classes("text-xs text-slate-400")
|
|
47
|
+
id_input = (
|
|
48
|
+
ui.input(
|
|
49
|
+
placeholder="e.g., tps54302",
|
|
50
|
+
)
|
|
51
|
+
.props("outlined dense")
|
|
52
|
+
.classes("w-full")
|
|
53
|
+
)
|
|
54
|
+
ui.label("").classes("text-xs text-red-500").bind_text_from(
|
|
55
|
+
validation, "id_error"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def validate_id(e):
|
|
59
|
+
value = e.value.lower().strip()
|
|
60
|
+
form["part_id"] = value
|
|
61
|
+
id_input.value = value
|
|
62
|
+
validation["id_error"] = validate_resource_id(
|
|
63
|
+
value, existing_ids, "Part ID"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
id_input.on("change", validate_id)
|
|
67
|
+
|
|
68
|
+
# Name
|
|
69
|
+
with ui.column().classes("gap-1 w-full"):
|
|
70
|
+
ui.label("Name").classes("text-sm font-medium text-slate-700")
|
|
71
|
+
name_input = (
|
|
72
|
+
ui.input(
|
|
73
|
+
placeholder="e.g., TPS54302 Buck Converter",
|
|
74
|
+
)
|
|
75
|
+
.props("outlined dense")
|
|
76
|
+
.classes("w-full")
|
|
77
|
+
)
|
|
78
|
+
ui.label("").classes("text-xs text-red-500").bind_text_from(
|
|
79
|
+
validation, "name_error"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def validate_name(e):
|
|
83
|
+
value = e.value.strip()
|
|
84
|
+
form["name"] = value
|
|
85
|
+
|
|
86
|
+
if not value:
|
|
87
|
+
validation["name_error"] = "Name is required"
|
|
88
|
+
else:
|
|
89
|
+
validation["name_error"] = ""
|
|
90
|
+
|
|
91
|
+
name_input.on("change", validate_name)
|
|
92
|
+
|
|
93
|
+
# Description
|
|
94
|
+
with ui.column().classes("gap-1 w-full"):
|
|
95
|
+
ui.label("Description").classes("text-sm font-medium text-slate-700")
|
|
96
|
+
ui.label("Optional").classes("text-xs text-slate-400")
|
|
97
|
+
desc_input = (
|
|
98
|
+
ui.textarea(
|
|
99
|
+
placeholder="Brief description of the part...",
|
|
100
|
+
)
|
|
101
|
+
.props("outlined dense")
|
|
102
|
+
.classes("w-full")
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def update_description(e):
|
|
106
|
+
value = e.value or ""
|
|
107
|
+
form["description"] = value.strip()
|
|
108
|
+
|
|
109
|
+
desc_input.on("change", update_description)
|
|
110
|
+
|
|
111
|
+
with ui.card_actions().classes("justify-end"):
|
|
112
|
+
ui.button(
|
|
113
|
+
"Cancel",
|
|
114
|
+
icon="close",
|
|
115
|
+
on_click=lambda: ui.navigate.to("/parts"),
|
|
116
|
+
).props("flat")
|
|
117
|
+
|
|
118
|
+
def create():
|
|
119
|
+
# Validate
|
|
120
|
+
if not form["part_id"]:
|
|
121
|
+
validation["id_error"] = "Part ID is required"
|
|
122
|
+
return
|
|
123
|
+
if not form["name"]:
|
|
124
|
+
validation["name_error"] = "Name is required"
|
|
125
|
+
return
|
|
126
|
+
if validation["id_error"] or validation["name_error"]:
|
|
127
|
+
ui.notify("Please fix validation errors", type="warning")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Create part
|
|
131
|
+
result = create_part(
|
|
132
|
+
part_id=form["part_id"],
|
|
133
|
+
name=form["name"],
|
|
134
|
+
description=form["description"],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if result:
|
|
138
|
+
ui.notify(
|
|
139
|
+
f"Part '{result['name']}' created successfully",
|
|
140
|
+
type="positive",
|
|
141
|
+
)
|
|
142
|
+
ui.navigate.to(f"/parts/{result['id']}/edit")
|
|
143
|
+
else:
|
|
144
|
+
ui.notify(
|
|
145
|
+
"Part ID already exists",
|
|
146
|
+
type="negative",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
ui.button(
|
|
150
|
+
"Create Part",
|
|
151
|
+
icon="add",
|
|
152
|
+
on_click=create,
|
|
153
|
+
).props("color=primary")
|
|
154
|
+
|
|
155
|
+
# Help text
|
|
156
|
+
with ui.card().classes("w-full max-w-xl bg-blue-50"):
|
|
157
|
+
with ui.card_section():
|
|
158
|
+
with ui.row().classes("items-start gap-3"):
|
|
159
|
+
ui.icon("lightbulb").classes("text-blue-500 mt-0.5")
|
|
160
|
+
with ui.column().classes("gap-1"):
|
|
161
|
+
ui.label("What happens next?").classes("font-medium text-blue-700")
|
|
162
|
+
ui.label(
|
|
163
|
+
"After creating the part, you'll be taken to the edit page "
|
|
164
|
+
"where you can add characteristics, test requirements, and more."
|
|
165
|
+
).classes("text-sm text-blue-600")
|
|
166
|
+
ui.label(
|
|
167
|
+
"You can also upload a datasheet to help extract specifications."
|
|
168
|
+
).classes("text-sm text-blue-600")
|
|
169
|
+
|
|
170
|
+
ui.link("← Back to Parts", "/parts").classes("text-blue-600 hover:underline")
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Part requirements page for procurement planning."""
|
|
2
|
+
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
|
|
5
|
+
from testerkit.ui.shared.components import data_table
|
|
6
|
+
from testerkit.ui.shared.layout import create_layout
|
|
7
|
+
from testerkit.ui.shared.services import (
|
|
8
|
+
discover_parts,
|
|
9
|
+
get_all_station_matches_for_part,
|
|
10
|
+
get_required_capabilities,
|
|
11
|
+
load_part_model,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@ui.page("/parts/{part_id}/requirements")
|
|
16
|
+
def requirements_page(part_id: str):
|
|
17
|
+
"""View required instruments for testing this part.
|
|
18
|
+
|
|
19
|
+
This helps with procurement - know what to order early,
|
|
20
|
+
mock tests while waiting for hardware.
|
|
21
|
+
"""
|
|
22
|
+
parts = discover_parts()
|
|
23
|
+
part = next((p for p in parts if p["id"] == part_id), None)
|
|
24
|
+
|
|
25
|
+
if part:
|
|
26
|
+
create_layout(f"{part['name']} - Requirements")
|
|
27
|
+
else:
|
|
28
|
+
create_layout("Part Not Found")
|
|
29
|
+
|
|
30
|
+
with ui.column().classes("w-full p-6 gap-6"):
|
|
31
|
+
if part:
|
|
32
|
+
_render_requirements(part_id, part)
|
|
33
|
+
else:
|
|
34
|
+
_render_not_found()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _render_requirements(part_id: str, part: dict):
|
|
38
|
+
"""Render the requirements view."""
|
|
39
|
+
part_model = load_part_model(part_id)
|
|
40
|
+
|
|
41
|
+
# Header with back link
|
|
42
|
+
with ui.row().classes("items-center gap-4 mb-4"):
|
|
43
|
+
ui.link(f"← {part['name']}", f"/parts/{part_id}").classes("text-blue-600 hover:underline")
|
|
44
|
+
|
|
45
|
+
# Required capabilities card
|
|
46
|
+
with ui.card().classes("w-full"):
|
|
47
|
+
with ui.card_section():
|
|
48
|
+
ui.label("Required Instrument Capabilities").classes("text-lg font-semibold")
|
|
49
|
+
ui.label(
|
|
50
|
+
"These capabilities are needed to test this part. "
|
|
51
|
+
"Order missing instruments early - you can mock in the meantime."
|
|
52
|
+
).classes("text-sm text-slate-500")
|
|
53
|
+
|
|
54
|
+
with ui.card_section():
|
|
55
|
+
if part_model:
|
|
56
|
+
capabilities = get_required_capabilities(part_model)
|
|
57
|
+
if capabilities:
|
|
58
|
+
columns = [
|
|
59
|
+
{
|
|
60
|
+
"name": "characteristic",
|
|
61
|
+
"label": "For Characteristic",
|
|
62
|
+
"field": "characteristic",
|
|
63
|
+
"align": "left",
|
|
64
|
+
},
|
|
65
|
+
{"name": "function", "label": "Function", "field": "function"},
|
|
66
|
+
{
|
|
67
|
+
"name": "direction",
|
|
68
|
+
"label": "Instrument Direction",
|
|
69
|
+
"field": "direction",
|
|
70
|
+
},
|
|
71
|
+
{"name": "signals", "label": "Signals", "field": "signals"},
|
|
72
|
+
]
|
|
73
|
+
rows = [
|
|
74
|
+
{
|
|
75
|
+
"characteristic": cap["characteristic"],
|
|
76
|
+
"function": cap["function"],
|
|
77
|
+
"direction": cap["direction"],
|
|
78
|
+
"signals": cap.get("signals", ""),
|
|
79
|
+
}
|
|
80
|
+
for cap in capabilities
|
|
81
|
+
]
|
|
82
|
+
data_table(columns=columns, rows=rows, row_key="characteristic")
|
|
83
|
+
else:
|
|
84
|
+
ui.label(
|
|
85
|
+
"No characteristics defined - add characteristics to generate requirements."
|
|
86
|
+
).classes("text-slate-500 italic")
|
|
87
|
+
else:
|
|
88
|
+
ui.label("Could not load part model.").classes("text-amber-600")
|
|
89
|
+
|
|
90
|
+
# Station coverage card
|
|
91
|
+
station_matches = get_all_station_matches_for_part(part_id)
|
|
92
|
+
compatible = station_matches.get("compatible", [])
|
|
93
|
+
partial = station_matches.get("partial", [])
|
|
94
|
+
|
|
95
|
+
with ui.card().classes("w-full mt-4"):
|
|
96
|
+
with ui.card_section():
|
|
97
|
+
ui.label("Station Coverage").classes("text-lg font-semibold")
|
|
98
|
+
|
|
99
|
+
with ui.card_section():
|
|
100
|
+
if compatible:
|
|
101
|
+
with ui.row().classes("items-center gap-2 mb-3"):
|
|
102
|
+
ui.icon("check_circle").classes("text-emerald-500")
|
|
103
|
+
ui.label(f"{len(compatible)} station(s) fully compatible").classes(
|
|
104
|
+
"text-emerald-600 font-medium"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if partial:
|
|
108
|
+
ui.label("Partially Compatible Stations").classes(
|
|
109
|
+
"font-medium text-amber-600 mt-2 mb-2"
|
|
110
|
+
)
|
|
111
|
+
ui.label(
|
|
112
|
+
"These stations have some capabilities. "
|
|
113
|
+
"Add missing instruments to enable full testing."
|
|
114
|
+
).classes("text-xs text-slate-500 mb-2")
|
|
115
|
+
|
|
116
|
+
for station in partial:
|
|
117
|
+
with ui.row().classes("items-center gap-3 py-2 border-b border-slate-100"):
|
|
118
|
+
ui.icon("warning").classes("text-amber-500")
|
|
119
|
+
with ui.column().classes("flex-1"):
|
|
120
|
+
ui.label(station["name"]).classes("font-medium")
|
|
121
|
+
with ui.row().classes("gap-1 flex-wrap"):
|
|
122
|
+
ui.label("Missing:").classes("text-xs text-slate-500")
|
|
123
|
+
for cap in station.get("missing", []):
|
|
124
|
+
ui.badge(cap, color="amber").props("outline dense")
|
|
125
|
+
ui.badge(f"{station['coverage']}%", color="amber")
|
|
126
|
+
|
|
127
|
+
if not compatible and not partial:
|
|
128
|
+
with ui.row().classes("items-center gap-2"):
|
|
129
|
+
ui.icon("info").classes("text-slate-400")
|
|
130
|
+
ui.label("No stations have any required instruments.").classes("text-slate-500")
|
|
131
|
+
ui.label("Consider creating a station or ordering instruments.").classes(
|
|
132
|
+
"text-sm text-slate-400 mt-1"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Action buttons
|
|
136
|
+
with ui.row().classes("mt-4 gap-3"):
|
|
137
|
+
ui.button(
|
|
138
|
+
"View Compatible Stations",
|
|
139
|
+
icon="memory",
|
|
140
|
+
on_click=lambda: ui.navigate.to(f"/parts/{part_id}/stations"),
|
|
141
|
+
).props("outline")
|
|
142
|
+
ui.button(
|
|
143
|
+
"Launch with Mocks",
|
|
144
|
+
icon="play_arrow",
|
|
145
|
+
on_click=lambda: ui.navigate.to(f"/launch?part={part_id}&mock=1"),
|
|
146
|
+
).props("color=primary")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _render_not_found():
|
|
150
|
+
"""Render part not found message."""
|
|
151
|
+
with ui.card().classes("w-full p-6 text-center"):
|
|
152
|
+
ui.label("Part not found.").classes("text-xl text-slate-600")
|
|
153
|
+
ui.link("← Back to Parts", "/parts").classes("text-blue-600 hover:underline")
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Part station compatibility page."""
|
|
2
|
+
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
|
|
5
|
+
from testerkit.ui.shared.layout import create_layout
|
|
6
|
+
from testerkit.ui.shared.services import (
|
|
7
|
+
discover_parts,
|
|
8
|
+
get_all_station_matches_for_part,
|
|
9
|
+
load_part_model,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@ui.page("/parts/{part_id}/stations")
|
|
14
|
+
def part_stations_page(part_id: str):
|
|
15
|
+
"""View station compatibility for this part.
|
|
16
|
+
|
|
17
|
+
Shows which stations can fully or partially test this part.
|
|
18
|
+
Helps with procurement planning and test execution decisions.
|
|
19
|
+
"""
|
|
20
|
+
parts = discover_parts()
|
|
21
|
+
part = next((p for p in parts if p["id"] == part_id), None)
|
|
22
|
+
|
|
23
|
+
if part:
|
|
24
|
+
create_layout(f"{part['name']} - Station Compatibility")
|
|
25
|
+
else:
|
|
26
|
+
create_layout("Part Not Found")
|
|
27
|
+
|
|
28
|
+
with ui.column().classes("w-full p-6 gap-6"):
|
|
29
|
+
if part:
|
|
30
|
+
_render_station_compatibility(part_id, part)
|
|
31
|
+
else:
|
|
32
|
+
_render_not_found()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _render_station_compatibility(part_id: str, part: dict):
|
|
36
|
+
"""Render the station compatibility view."""
|
|
37
|
+
part_model = load_part_model(part_id)
|
|
38
|
+
station_matches = get_all_station_matches_for_part(part_id)
|
|
39
|
+
|
|
40
|
+
compatible = station_matches.get("compatible", [])
|
|
41
|
+
partial = station_matches.get("partial", [])
|
|
42
|
+
incompatible = station_matches.get("incompatible", [])
|
|
43
|
+
|
|
44
|
+
# Header with back link
|
|
45
|
+
with ui.row().classes("items-center gap-4 mb-4"):
|
|
46
|
+
ui.link(f"← {part['name']}", f"/parts/{part_id}").classes("text-blue-600 hover:underline")
|
|
47
|
+
|
|
48
|
+
# Summary card
|
|
49
|
+
with ui.card().classes("w-full"):
|
|
50
|
+
with ui.card_section():
|
|
51
|
+
ui.label(f"Station Compatibility for {part['name']}").classes("text-lg font-semibold")
|
|
52
|
+
if part_model:
|
|
53
|
+
char_count = len(part_model.characteristics)
|
|
54
|
+
ui.label(f"Matching stations against {char_count} characteristic(s)").classes(
|
|
55
|
+
"text-sm text-slate-500"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
with ui.card_section():
|
|
59
|
+
with ui.row().classes("gap-6"):
|
|
60
|
+
with ui.column().classes("items-center"):
|
|
61
|
+
ui.label(str(len(compatible))).classes("text-2xl font-bold text-emerald-600")
|
|
62
|
+
ui.label("Ready").classes("text-xs text-slate-500")
|
|
63
|
+
with ui.column().classes("items-center"):
|
|
64
|
+
ui.label(str(len(partial))).classes("text-2xl font-bold text-amber-600")
|
|
65
|
+
ui.label("Partial").classes("text-xs text-slate-500")
|
|
66
|
+
with ui.column().classes("items-center"):
|
|
67
|
+
ui.label(str(len(incompatible))).classes("text-2xl font-bold text-slate-400")
|
|
68
|
+
ui.label("None").classes("text-xs text-slate-500")
|
|
69
|
+
|
|
70
|
+
# Fully compatible stations
|
|
71
|
+
if compatible:
|
|
72
|
+
with ui.card().classes("w-full mt-4"):
|
|
73
|
+
with ui.card_section():
|
|
74
|
+
with ui.row().classes("items-center gap-2"):
|
|
75
|
+
ui.icon("check_circle").classes("text-emerald-500")
|
|
76
|
+
ui.label("Ready to Test").classes("text-lg font-semibold text-emerald-600")
|
|
77
|
+
ui.label("These stations have all required instruments.").classes(
|
|
78
|
+
"text-sm text-slate-500"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
with ui.card_section():
|
|
82
|
+
for station in compatible:
|
|
83
|
+
_render_compatible_station(part_id, station)
|
|
84
|
+
|
|
85
|
+
# Partially compatible stations
|
|
86
|
+
if partial:
|
|
87
|
+
with ui.card().classes("w-full mt-4"):
|
|
88
|
+
with ui.card_section():
|
|
89
|
+
with ui.row().classes("items-center gap-2"):
|
|
90
|
+
ui.icon("warning").classes("text-amber-500")
|
|
91
|
+
ui.label("Needs Additional Instruments").classes(
|
|
92
|
+
"text-lg font-semibold text-amber-600"
|
|
93
|
+
)
|
|
94
|
+
ui.label(
|
|
95
|
+
"Add missing instruments to enable full testing, or use simulation."
|
|
96
|
+
).classes("text-sm text-slate-500")
|
|
97
|
+
|
|
98
|
+
with ui.card_section():
|
|
99
|
+
for station in partial:
|
|
100
|
+
_render_partial_station(part_id, station)
|
|
101
|
+
|
|
102
|
+
# Incompatible stations (if any exist)
|
|
103
|
+
if incompatible:
|
|
104
|
+
with ui.expansion("Incompatible Stations", icon="block").classes("w-full mt-4"):
|
|
105
|
+
ui.label("These stations have none of the required instruments.").classes(
|
|
106
|
+
"text-sm text-slate-500 mb-2"
|
|
107
|
+
)
|
|
108
|
+
for station in incompatible:
|
|
109
|
+
with ui.row().classes("items-center gap-3 py-2 border-b border-slate-100"):
|
|
110
|
+
ui.icon("block").classes("text-slate-300")
|
|
111
|
+
ui.label(station["name"]).classes("text-slate-500")
|
|
112
|
+
if station.get("location"):
|
|
113
|
+
ui.label(f"({station['location']})").classes("text-xs text-slate-400")
|
|
114
|
+
|
|
115
|
+
# No stations at all
|
|
116
|
+
if not compatible and not partial and not incompatible:
|
|
117
|
+
with ui.card().classes("w-full mt-4 p-6 text-center"):
|
|
118
|
+
ui.icon("search_off").classes("text-4xl text-slate-300")
|
|
119
|
+
ui.label("No stations found").classes("text-lg text-slate-500 mt-2")
|
|
120
|
+
ui.label("Create a station configuration to enable testing.").classes(
|
|
121
|
+
"text-sm text-slate-400"
|
|
122
|
+
)
|
|
123
|
+
ui.button(
|
|
124
|
+
"Create Station",
|
|
125
|
+
icon="add",
|
|
126
|
+
on_click=lambda: ui.navigate.to("/stations/new"),
|
|
127
|
+
).classes("mt-4")
|
|
128
|
+
|
|
129
|
+
# Action buttons
|
|
130
|
+
with ui.row().classes("mt-4 gap-3"):
|
|
131
|
+
ui.button(
|
|
132
|
+
"View Requirements",
|
|
133
|
+
icon="list_alt",
|
|
134
|
+
on_click=lambda: ui.navigate.to(f"/parts/{part_id}/requirements"),
|
|
135
|
+
).props("outline")
|
|
136
|
+
if compatible:
|
|
137
|
+
ui.button(
|
|
138
|
+
"Launch Tests",
|
|
139
|
+
icon="play_arrow",
|
|
140
|
+
on_click=lambda: ui.navigate.to(f"/launch?part={part_id}"),
|
|
141
|
+
).props("color=primary")
|
|
142
|
+
else:
|
|
143
|
+
ui.button(
|
|
144
|
+
"Launch with Mocks",
|
|
145
|
+
icon="play_arrow",
|
|
146
|
+
on_click=lambda: ui.navigate.to(f"/launch?part={part_id}&mock=1"),
|
|
147
|
+
).props("color=primary")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _render_compatible_station(part_id: str, station: dict):
|
|
151
|
+
"""Render a fully compatible station row."""
|
|
152
|
+
with ui.row().classes("items-center justify-between py-3 border-b border-slate-100 w-full"):
|
|
153
|
+
with ui.column().classes("flex-1"):
|
|
154
|
+
ui.label(station["name"]).classes("font-semibold")
|
|
155
|
+
if station.get("location"):
|
|
156
|
+
ui.label(station["location"]).classes("text-sm text-slate-500")
|
|
157
|
+
with ui.row().classes("items-center gap-3"):
|
|
158
|
+
ui.badge("100%", color="green").props("dense")
|
|
159
|
+
ui.button(
|
|
160
|
+
"Launch",
|
|
161
|
+
icon="play_arrow",
|
|
162
|
+
on_click=lambda _, s=station: ui.navigate.to(
|
|
163
|
+
f"/launch?part={part_id}&station={s['id']}"
|
|
164
|
+
),
|
|
165
|
+
).props("flat dense")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _render_partial_station(part_id: str, station: dict):
|
|
169
|
+
"""Render a partially compatible station row."""
|
|
170
|
+
with ui.row().classes("items-center justify-between py-3 border-b border-slate-100 w-full"):
|
|
171
|
+
with ui.column().classes("flex-1"):
|
|
172
|
+
ui.label(station["name"]).classes("font-semibold")
|
|
173
|
+
if station.get("location"):
|
|
174
|
+
ui.label(station["location"]).classes("text-sm text-slate-500")
|
|
175
|
+
# Show missing capabilities
|
|
176
|
+
with ui.row().classes("gap-1 flex-wrap mt-1"):
|
|
177
|
+
ui.label("Missing:").classes("text-xs text-slate-500")
|
|
178
|
+
for cap in station.get("missing", [])[:5]: # Limit to 5
|
|
179
|
+
ui.badge(cap, color="amber").props("outline dense")
|
|
180
|
+
if len(station.get("missing", [])) > 5:
|
|
181
|
+
ui.badge(f"+{len(station['missing']) - 5} more", color="grey").props(
|
|
182
|
+
"outline dense"
|
|
183
|
+
)
|
|
184
|
+
with ui.row().classes("items-center gap-3"):
|
|
185
|
+
ui.badge(f"{station['coverage']}%", color="amber").props("dense")
|
|
186
|
+
ui.button(
|
|
187
|
+
"Mock",
|
|
188
|
+
icon="play_arrow",
|
|
189
|
+
on_click=lambda _, s=station: ui.navigate.to(
|
|
190
|
+
f"/launch?part={part_id}&station={s['id']}&mock=1"
|
|
191
|
+
),
|
|
192
|
+
).props("flat dense outline")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _render_not_found():
|
|
196
|
+
"""Render part not found message."""
|
|
197
|
+
with ui.card().classes("w-full p-6 text-center"):
|
|
198
|
+
ui.label("Part not found.").classes("text-xl text-slate-600")
|
|
199
|
+
ui.link("← Back to Parts", "/parts").classes("text-blue-600 hover:underline")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Profile detail page — view one profile's resolved YAML + extends chain."""
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
from nicegui import ui
|
|
5
|
+
|
|
6
|
+
from testerkit.ui.shared.components import page_layout
|
|
7
|
+
from testerkit.ui.shared.layout import create_layout
|
|
8
|
+
from testerkit.ui.shared.services import discover_profiles, load_profile_config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@ui.page("/profiles/{name}")
|
|
12
|
+
def profile_detail_page(name: str):
|
|
13
|
+
"""One profile — header card + resolved YAML + extends-chain map.
|
|
14
|
+
|
|
15
|
+
The YAML is the merged in-memory ProfileConfig (after extends-chain
|
|
16
|
+
resolution at session start). Read-only — edits land via the
|
|
17
|
+
file system (no in-browser editor yet).
|
|
18
|
+
"""
|
|
19
|
+
create_layout(f"Profile · {name}")
|
|
20
|
+
|
|
21
|
+
profile = load_profile_config(name)
|
|
22
|
+
|
|
23
|
+
with page_layout():
|
|
24
|
+
if profile is None:
|
|
25
|
+
with ui.card().classes("w-full p-6 text-center"):
|
|
26
|
+
ui.label(f"Profile '{name}' not found.").classes("text-xl text-slate-600")
|
|
27
|
+
ui.link("← Back to Profiles", "/profiles").classes("text-blue-600 hover:underline")
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
# Header row
|
|
31
|
+
with ui.row().classes("items-center justify-between w-full"):
|
|
32
|
+
with ui.row().classes("items-center gap-2"):
|
|
33
|
+
ui.icon("layers").classes("text-slate-600")
|
|
34
|
+
ui.label(name).classes("text-lg font-semibold text-slate-700")
|
|
35
|
+
ui.button(
|
|
36
|
+
"Launch Test",
|
|
37
|
+
icon="play_arrow",
|
|
38
|
+
on_click=lambda: ui.navigate.to(f"/launch?test_profile={name}"),
|
|
39
|
+
).props("color=primary")
|
|
40
|
+
|
|
41
|
+
# Summary card — extends, station_type, fixture, facets
|
|
42
|
+
with ui.card().classes("w-full"):
|
|
43
|
+
with ui.card_section():
|
|
44
|
+
ui.label("Summary").classes("font-semibold")
|
|
45
|
+
with ui.card_section(), ui.row().classes("gap-6 flex-wrap"):
|
|
46
|
+
_meta_field("Extends", profile.extends or "—")
|
|
47
|
+
_meta_field("Station type", profile.station_type or "—")
|
|
48
|
+
_meta_field("Fixture", profile.fixture or "—")
|
|
49
|
+
facets_str = ", ".join(f"{k}={v}" for k, v in (profile.facets or {}).items()) or "—"
|
|
50
|
+
_meta_field("Facets", facets_str)
|
|
51
|
+
_meta_field("Tests", str(len(profile.tests or {})))
|
|
52
|
+
|
|
53
|
+
# Extends chain — walk parents recursively for the visual map
|
|
54
|
+
chain = _resolve_extends_chain(name)
|
|
55
|
+
if len(chain) > 1:
|
|
56
|
+
with ui.card().classes("w-full"):
|
|
57
|
+
with ui.card_section():
|
|
58
|
+
ui.label("Inheritance").classes("font-semibold")
|
|
59
|
+
ui.label(
|
|
60
|
+
"Parent profiles are applied first; this profile's "
|
|
61
|
+
"fields override the merged result."
|
|
62
|
+
).classes("text-xs text-slate-500")
|
|
63
|
+
with ui.card_section(), ui.row().classes("items-center gap-2"):
|
|
64
|
+
for i, link in enumerate(chain):
|
|
65
|
+
if i > 0:
|
|
66
|
+
ui.icon("arrow_forward", size="sm").classes("text-slate-400")
|
|
67
|
+
if link == name:
|
|
68
|
+
ui.badge(link, color="primary")
|
|
69
|
+
else:
|
|
70
|
+
ui.link(link, target=f"/profiles/{link}").classes("text-blue-600")
|
|
71
|
+
|
|
72
|
+
# Resolved YAML — single source of truth for what this profile
|
|
73
|
+
# actually configures
|
|
74
|
+
with ui.card().classes("w-full"):
|
|
75
|
+
with ui.card_section():
|
|
76
|
+
ui.label("Resolved YAML").classes("font-semibold")
|
|
77
|
+
ui.label("After the extends chain resolves at session start.").classes(
|
|
78
|
+
"text-xs text-slate-500"
|
|
79
|
+
)
|
|
80
|
+
with ui.card_section():
|
|
81
|
+
yaml_text = yaml.safe_dump(
|
|
82
|
+
profile.model_dump(exclude_none=True),
|
|
83
|
+
sort_keys=False,
|
|
84
|
+
default_flow_style=False,
|
|
85
|
+
)
|
|
86
|
+
ui.code(yaml_text, language="yaml").classes("w-full")
|
|
87
|
+
|
|
88
|
+
with ui.row().classes("mt-2"):
|
|
89
|
+
ui.link("← Back to Profiles", "/profiles").classes("text-blue-600 hover:underline")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _meta_field(label: str, value: str) -> None:
|
|
93
|
+
with ui.column().classes("gap-0"):
|
|
94
|
+
ui.label(label).classes("text-xs text-slate-500 uppercase")
|
|
95
|
+
ui.label(value).classes("text-sm font-medium text-slate-800")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _resolve_extends_chain(name: str) -> list[str]:
|
|
99
|
+
"""Walk the ``extends:`` chain from root → leaf. Stops if a parent
|
|
100
|
+
isn't a known profile (silently — the runtime resolver would raise).
|
|
101
|
+
"""
|
|
102
|
+
profiles = {p.name: p for p in discover_profiles()}
|
|
103
|
+
chain: list[str] = []
|
|
104
|
+
current = name
|
|
105
|
+
seen: set[str] = set()
|
|
106
|
+
while current and current not in seen:
|
|
107
|
+
seen.add(current)
|
|
108
|
+
chain.append(current)
|
|
109
|
+
row = profiles.get(current)
|
|
110
|
+
if not row or not row.extends:
|
|
111
|
+
break
|
|
112
|
+
current = row.extends
|
|
113
|
+
return list(reversed(chain))
|