oscura 0.0.1__py3-none-any.whl → 0.1.1__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.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
"""Analysis quality scoring for Oscura.
|
|
2
|
+
|
|
3
|
+
This module provides quality scoring and reliability categorization for
|
|
4
|
+
analysis results, enabling users to assess confidence in automated findings.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.quality.scoring import AnalysisQualityScore, ReliabilityCategory
|
|
9
|
+
>>> score = AnalysisQualityScore(
|
|
10
|
+
... confidence=0.85,
|
|
11
|
+
... category=ReliabilityCategory.HIGH,
|
|
12
|
+
... data_quality_factor=0.9,
|
|
13
|
+
... sample_sufficiency=0.8,
|
|
14
|
+
... method_reliability=0.85,
|
|
15
|
+
... )
|
|
16
|
+
>>> print(score.explain())
|
|
17
|
+
>>> recommendations = score.get_recommendations()
|
|
18
|
+
|
|
19
|
+
References:
|
|
20
|
+
- Quality scoring for automated analysis results
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from enum import Enum
|
|
28
|
+
from typing import TYPE_CHECKING, Any
|
|
29
|
+
|
|
30
|
+
import numpy as np
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from numpy.typing import NDArray
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ReliabilityCategory(Enum):
|
|
39
|
+
"""Reliability categories for analysis results.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
HIGH: Result is highly reliable (confidence >= 0.8)
|
|
43
|
+
MEDIUM: Result has moderate reliability (0.6 <= confidence < 0.8)
|
|
44
|
+
LOW: Result has low reliability (0.4 <= confidence < 0.6)
|
|
45
|
+
UNRELIABLE: Result is unreliable (confidence < 0.4)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
HIGH = "high"
|
|
49
|
+
MEDIUM = "medium"
|
|
50
|
+
LOW = "low"
|
|
51
|
+
UNRELIABLE = "unreliable"
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_confidence(cls, confidence: float) -> ReliabilityCategory:
|
|
55
|
+
"""Get category from confidence score.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
confidence: Confidence value in range [0, 1]
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Appropriate ReliabilityCategory
|
|
62
|
+
"""
|
|
63
|
+
if confidence >= 0.8:
|
|
64
|
+
return cls.HIGH
|
|
65
|
+
elif confidence >= 0.6:
|
|
66
|
+
return cls.MEDIUM
|
|
67
|
+
elif confidence >= 0.4:
|
|
68
|
+
return cls.LOW
|
|
69
|
+
else:
|
|
70
|
+
return cls.UNRELIABLE
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class AnalysisQualityScore:
|
|
75
|
+
"""Quality score for an analysis result.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
confidence: Overall confidence in result (0-1)
|
|
79
|
+
category: Reliability category
|
|
80
|
+
data_quality_factor: Quality of input data (0-1)
|
|
81
|
+
sample_sufficiency: Sufficiency of sample count (0-1)
|
|
82
|
+
method_reliability: Inherent reliability of method (0-1)
|
|
83
|
+
factors: Additional contributing factors
|
|
84
|
+
warnings: Quality warnings
|
|
85
|
+
metadata: Additional metadata
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> score = AnalysisQualityScore(
|
|
89
|
+
... confidence=0.85,
|
|
90
|
+
... category=ReliabilityCategory.HIGH,
|
|
91
|
+
... data_quality_factor=0.9,
|
|
92
|
+
... sample_sufficiency=0.8,
|
|
93
|
+
... method_reliability=0.85,
|
|
94
|
+
... )
|
|
95
|
+
>>> if score.is_reliable:
|
|
96
|
+
... print("Result is reliable")
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
confidence: float
|
|
100
|
+
category: ReliabilityCategory
|
|
101
|
+
data_quality_factor: float
|
|
102
|
+
sample_sufficiency: float
|
|
103
|
+
method_reliability: float
|
|
104
|
+
factors: dict[str, float] = field(default_factory=dict)
|
|
105
|
+
warnings: list[str] = field(default_factory=list)
|
|
106
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
107
|
+
|
|
108
|
+
def __post_init__(self) -> None:
|
|
109
|
+
"""Validate score values."""
|
|
110
|
+
# Ensure confidence is in valid range
|
|
111
|
+
if not 0 <= self.confidence <= 1:
|
|
112
|
+
raise ValueError(f"Confidence must be in [0, 1], got {self.confidence}")
|
|
113
|
+
|
|
114
|
+
# Validate factors
|
|
115
|
+
for name, value in [
|
|
116
|
+
("data_quality_factor", self.data_quality_factor),
|
|
117
|
+
("sample_sufficiency", self.sample_sufficiency),
|
|
118
|
+
("method_reliability", self.method_reliability),
|
|
119
|
+
]:
|
|
120
|
+
if not 0 <= value <= 1:
|
|
121
|
+
raise ValueError(f"{name} must be in [0, 1], got {value}")
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def is_reliable(self) -> bool:
|
|
125
|
+
"""Check if result is reliable (medium confidence or higher).
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if category is HIGH or MEDIUM
|
|
129
|
+
"""
|
|
130
|
+
return self.category in (ReliabilityCategory.HIGH, ReliabilityCategory.MEDIUM)
|
|
131
|
+
|
|
132
|
+
def explain(self, include_factors: bool = True) -> str:
|
|
133
|
+
"""Generate human-readable explanation of the quality score.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
include_factors: Whether to include factor breakdown
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Human-readable explanation string
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
>>> print(score.explain())
|
|
143
|
+
✓ High confidence result (85.0%)
|
|
144
|
+
|
|
145
|
+
Contributing factors:
|
|
146
|
+
✓ Data Quality Factor: 90.0%
|
|
147
|
+
✓ Sample Sufficiency: 80.0%
|
|
148
|
+
✓ Method Reliability: 85.0%
|
|
149
|
+
"""
|
|
150
|
+
lines = []
|
|
151
|
+
|
|
152
|
+
# Overall assessment
|
|
153
|
+
if self.category == ReliabilityCategory.HIGH:
|
|
154
|
+
lines.append(f"✓ High confidence result ({self.confidence:.1%})")
|
|
155
|
+
elif self.category == ReliabilityCategory.MEDIUM:
|
|
156
|
+
lines.append(f"◐ Medium confidence result ({self.confidence:.1%})")
|
|
157
|
+
elif self.category == ReliabilityCategory.LOW:
|
|
158
|
+
lines.append(f"◯ Low confidence result ({self.confidence:.1%})")
|
|
159
|
+
else:
|
|
160
|
+
lines.append(f"✗ Unreliable result ({self.confidence:.1%})")
|
|
161
|
+
|
|
162
|
+
# Factor breakdown
|
|
163
|
+
if include_factors and self.factors:
|
|
164
|
+
lines.append("\nContributing factors:")
|
|
165
|
+
for factor_name, factor_value in sorted(self.factors.items()):
|
|
166
|
+
status = "✓" if factor_value >= 0.7 else "◐" if factor_value >= 0.4 else "✗"
|
|
167
|
+
lines.append(
|
|
168
|
+
f" {status} {factor_name.replace('_', ' ').title()}: {factor_value:.1%}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Warnings
|
|
172
|
+
if self.warnings:
|
|
173
|
+
lines.append("\nWarnings:")
|
|
174
|
+
for warning in self.warnings:
|
|
175
|
+
lines.append(f" ⚠ {warning}")
|
|
176
|
+
|
|
177
|
+
return "\n".join(lines)
|
|
178
|
+
|
|
179
|
+
def get_recommendations(self) -> list[str]:
|
|
180
|
+
"""Get actionable recommendations to improve result quality.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of recommendation strings
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
>>> recommendations = score.get_recommendations()
|
|
187
|
+
>>> for rec in recommendations:
|
|
188
|
+
... print(rec)
|
|
189
|
+
Consider improving input signal quality (filtering, averaging)
|
|
190
|
+
"""
|
|
191
|
+
recommendations = []
|
|
192
|
+
|
|
193
|
+
if self.data_quality_factor < 0.5:
|
|
194
|
+
recommendations.append("Consider improving input signal quality (filtering, averaging)")
|
|
195
|
+
|
|
196
|
+
if self.sample_sufficiency < 0.5:
|
|
197
|
+
recommendations.append("Capture more data points for reliable analysis")
|
|
198
|
+
|
|
199
|
+
if "snr" in str(self.warnings).lower():
|
|
200
|
+
recommendations.append("Use a bandpass filter to improve SNR")
|
|
201
|
+
|
|
202
|
+
if "clipping" in str(self.warnings).lower():
|
|
203
|
+
recommendations.append("Adjust input gain to avoid signal clipping")
|
|
204
|
+
|
|
205
|
+
if not recommendations:
|
|
206
|
+
recommendations.append("Result quality is acceptable")
|
|
207
|
+
|
|
208
|
+
return recommendations
|
|
209
|
+
|
|
210
|
+
def to_dict(self) -> dict[str, Any]:
|
|
211
|
+
"""Convert to dictionary for serialization.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dictionary representation of quality score.
|
|
215
|
+
"""
|
|
216
|
+
return {
|
|
217
|
+
"confidence": self.confidence,
|
|
218
|
+
"category": self.category.value,
|
|
219
|
+
"is_reliable": self.is_reliable,
|
|
220
|
+
"data_quality_factor": self.data_quality_factor,
|
|
221
|
+
"method_reliability": self.method_reliability,
|
|
222
|
+
"sample_sufficiency": self.sample_sufficiency,
|
|
223
|
+
"factors": self.factors,
|
|
224
|
+
"warnings": self.warnings,
|
|
225
|
+
"metadata": self.metadata,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def calculate_quality_score(
|
|
230
|
+
data_quality_factor: float,
|
|
231
|
+
sample_sufficiency: float,
|
|
232
|
+
method_reliability: float,
|
|
233
|
+
*,
|
|
234
|
+
weights: tuple[float, float, float] | None = None,
|
|
235
|
+
warnings: list[str] | None = None,
|
|
236
|
+
factors: dict[str, float] | None = None,
|
|
237
|
+
metadata: dict[str, Any] | None = None,
|
|
238
|
+
) -> AnalysisQualityScore:
|
|
239
|
+
"""Calculate overall quality score from component factors.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
data_quality_factor: Quality of input data (0-1)
|
|
243
|
+
sample_sufficiency: Sufficiency of sample count (0-1)
|
|
244
|
+
method_reliability: Inherent reliability of method (0-1)
|
|
245
|
+
weights: Optional custom weights (data, sample, method), defaults to (0.4, 0.3, 0.3)
|
|
246
|
+
warnings: Optional quality warnings
|
|
247
|
+
factors: Optional additional factors
|
|
248
|
+
metadata: Optional metadata
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
AnalysisQualityScore with computed confidence
|
|
252
|
+
|
|
253
|
+
Example:
|
|
254
|
+
>>> score = calculate_quality_score(
|
|
255
|
+
... data_quality_factor=0.9,
|
|
256
|
+
... sample_sufficiency=0.8,
|
|
257
|
+
... method_reliability=0.85,
|
|
258
|
+
... )
|
|
259
|
+
>>> print(f"Confidence: {score.confidence:.1%}")
|
|
260
|
+
"""
|
|
261
|
+
if weights is None:
|
|
262
|
+
weights = (0.4, 0.3, 0.3)
|
|
263
|
+
|
|
264
|
+
w_data, w_sample, w_method = weights
|
|
265
|
+
|
|
266
|
+
# Calculate weighted confidence
|
|
267
|
+
confidence = (
|
|
268
|
+
w_data * data_quality_factor + w_sample * sample_sufficiency + w_method * method_reliability
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Determine category
|
|
272
|
+
category = ReliabilityCategory.from_confidence(confidence)
|
|
273
|
+
|
|
274
|
+
# Build factors dictionary
|
|
275
|
+
all_factors = {
|
|
276
|
+
"data_quality_factor": data_quality_factor,
|
|
277
|
+
"sample_sufficiency": sample_sufficiency,
|
|
278
|
+
"method_reliability": method_reliability,
|
|
279
|
+
}
|
|
280
|
+
if factors:
|
|
281
|
+
all_factors.update(factors)
|
|
282
|
+
|
|
283
|
+
return AnalysisQualityScore(
|
|
284
|
+
confidence=confidence,
|
|
285
|
+
category=category,
|
|
286
|
+
data_quality_factor=data_quality_factor,
|
|
287
|
+
sample_sufficiency=sample_sufficiency,
|
|
288
|
+
method_reliability=method_reliability,
|
|
289
|
+
factors=all_factors,
|
|
290
|
+
warnings=warnings or [],
|
|
291
|
+
metadata=metadata or {},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@dataclass
|
|
296
|
+
class DataQualityMetrics:
|
|
297
|
+
"""Metrics describing input data quality.
|
|
298
|
+
|
|
299
|
+
QUAL-002: Data quality assessment
|
|
300
|
+
|
|
301
|
+
Attributes:
|
|
302
|
+
snr_db: Signal-to-noise ratio in decibels
|
|
303
|
+
sample_count: Number of samples in the data
|
|
304
|
+
has_clipping: Whether the signal shows clipping
|
|
305
|
+
has_saturation: Whether the signal shows saturation
|
|
306
|
+
noise_floor: Estimated noise floor level
|
|
307
|
+
completeness: Fraction of non-NaN values (0-1)
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
snr_db: float | None = None
|
|
311
|
+
sample_count: int = 0
|
|
312
|
+
has_clipping: bool = False
|
|
313
|
+
has_saturation: bool = False
|
|
314
|
+
noise_floor: float | None = None
|
|
315
|
+
completeness: float = 1.0 # Fraction of non-NaN values
|
|
316
|
+
|
|
317
|
+
def to_factor(self) -> float:
|
|
318
|
+
"""Convert metrics to single quality factor (0-1).
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Quality factor between 0 and 1.
|
|
322
|
+
"""
|
|
323
|
+
factors = []
|
|
324
|
+
|
|
325
|
+
# SNR contribution
|
|
326
|
+
if self.snr_db is not None:
|
|
327
|
+
snr_factor = min(1.0, max(0.0, self.snr_db / 40.0))
|
|
328
|
+
factors.append(snr_factor)
|
|
329
|
+
|
|
330
|
+
# Sample count contribution (diminishing returns after 1000)
|
|
331
|
+
sample_factor = min(1.0, np.log10(max(1, self.sample_count)) / 4.0)
|
|
332
|
+
factors.append(sample_factor)
|
|
333
|
+
|
|
334
|
+
# Clipping/saturation penalties
|
|
335
|
+
if self.has_clipping:
|
|
336
|
+
factors.append(0.7)
|
|
337
|
+
if self.has_saturation:
|
|
338
|
+
factors.append(0.6)
|
|
339
|
+
|
|
340
|
+
# Completeness
|
|
341
|
+
factors.append(self.completeness)
|
|
342
|
+
|
|
343
|
+
return float(np.mean(factors)) if factors else 0.5
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Method reliability scores (based on algorithm characteristics)
|
|
347
|
+
#: Method reliability tracking
|
|
348
|
+
METHOD_RELIABILITY: dict[str, float] = {
|
|
349
|
+
# High reliability methods
|
|
350
|
+
"fft": 0.95,
|
|
351
|
+
"welch": 0.90,
|
|
352
|
+
"autocorrelation": 0.85,
|
|
353
|
+
"histogram": 0.95,
|
|
354
|
+
"statistics": 0.95,
|
|
355
|
+
# Medium reliability methods
|
|
356
|
+
"edge_detection": 0.80,
|
|
357
|
+
"zero_crossing": 0.75,
|
|
358
|
+
"peak_detection": 0.70,
|
|
359
|
+
"pattern_matching": 0.75,
|
|
360
|
+
# Lower reliability methods (heuristic-based)
|
|
361
|
+
"protocol_inference": 0.60,
|
|
362
|
+
"signal_classification": 0.65,
|
|
363
|
+
"anomaly_detection": 0.60,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def assess_data_quality(
|
|
368
|
+
data: NDArray[np.float64], sample_rate: float | None = None
|
|
369
|
+
) -> DataQualityMetrics:
|
|
370
|
+
"""Assess quality of input data.
|
|
371
|
+
|
|
372
|
+
QUAL-002: Data quality assessment
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
data: Input data array
|
|
376
|
+
sample_rate: Sample rate in Hz (optional)
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
DataQualityMetrics with quality assessment
|
|
380
|
+
"""
|
|
381
|
+
metrics = DataQualityMetrics()
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
# Sample count
|
|
385
|
+
metrics.sample_count = len(data)
|
|
386
|
+
|
|
387
|
+
# Check for NaN/Inf
|
|
388
|
+
valid_mask = np.isfinite(data)
|
|
389
|
+
metrics.completeness = float(np.mean(valid_mask))
|
|
390
|
+
|
|
391
|
+
if metrics.completeness < 0.01:
|
|
392
|
+
return metrics
|
|
393
|
+
|
|
394
|
+
valid_data = data[valid_mask]
|
|
395
|
+
|
|
396
|
+
# Check for clipping (values at min/max bounds)
|
|
397
|
+
data_range = np.ptp(valid_data)
|
|
398
|
+
if data_range > 0:
|
|
399
|
+
min_count = np.sum(valid_data == np.min(valid_data))
|
|
400
|
+
max_count = np.sum(valid_data == np.max(valid_data))
|
|
401
|
+
clip_threshold = 0.01 * len(valid_data)
|
|
402
|
+
metrics.has_clipping = min_count > clip_threshold or max_count > clip_threshold
|
|
403
|
+
|
|
404
|
+
# Estimate SNR using signal variance vs noise floor
|
|
405
|
+
# Use median absolute deviation for robust noise estimation
|
|
406
|
+
median = np.median(valid_data)
|
|
407
|
+
mad = np.median(np.abs(valid_data - median)) * 1.4826
|
|
408
|
+
metrics.noise_floor = float(mad)
|
|
409
|
+
|
|
410
|
+
signal_power = float(np.var(valid_data))
|
|
411
|
+
noise_power = mad**2
|
|
412
|
+
|
|
413
|
+
if noise_power > 0:
|
|
414
|
+
snr_linear = signal_power / noise_power
|
|
415
|
+
metrics.snr_db = float(10 * np.log10(max(1e-10, snr_linear)))
|
|
416
|
+
|
|
417
|
+
except Exception as e:
|
|
418
|
+
logger.debug(f"Error assessing data quality: {e}")
|
|
419
|
+
|
|
420
|
+
return metrics
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def score_analysis_result(
|
|
424
|
+
result: Any,
|
|
425
|
+
method_name: str,
|
|
426
|
+
data: NDArray[np.float64] | None = None,
|
|
427
|
+
data_quality: DataQualityMetrics | None = None,
|
|
428
|
+
min_samples: int = 10,
|
|
429
|
+
) -> AnalysisQualityScore:
|
|
430
|
+
"""Score the quality of an analysis result.
|
|
431
|
+
|
|
432
|
+
QUAL-001: Quality scoring foundation
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
result: The analysis result to score
|
|
436
|
+
method_name: Name of the analysis method
|
|
437
|
+
data: Input data (for quality assessment)
|
|
438
|
+
data_quality: Pre-computed data quality metrics
|
|
439
|
+
min_samples: Minimum samples for reliable result
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
AnalysisQualityScore with confidence and factors
|
|
443
|
+
"""
|
|
444
|
+
factors = {}
|
|
445
|
+
warnings = []
|
|
446
|
+
|
|
447
|
+
# Get data quality
|
|
448
|
+
if data_quality is None and data is not None:
|
|
449
|
+
data_quality = assess_data_quality(data)
|
|
450
|
+
|
|
451
|
+
# Data quality factor
|
|
452
|
+
if data_quality is not None:
|
|
453
|
+
data_factor = data_quality.to_factor()
|
|
454
|
+
factors["data_quality"] = data_factor
|
|
455
|
+
|
|
456
|
+
if data_quality.has_clipping:
|
|
457
|
+
warnings.append("Input data shows clipping")
|
|
458
|
+
if data_quality.snr_db is not None and data_quality.snr_db < 20:
|
|
459
|
+
warnings.append(f"Low SNR ({data_quality.snr_db:.1f} dB)")
|
|
460
|
+
else:
|
|
461
|
+
data_factor = 0.5
|
|
462
|
+
factors["data_quality"] = data_factor
|
|
463
|
+
|
|
464
|
+
# Method reliability
|
|
465
|
+
method_key = method_name.lower().split(".")[-1].replace("_", "")
|
|
466
|
+
method_reliability = METHOD_RELIABILITY.get(method_key, 0.7)
|
|
467
|
+
|
|
468
|
+
# Check for partial matches
|
|
469
|
+
for key, reliability in METHOD_RELIABILITY.items():
|
|
470
|
+
if key in method_name.lower():
|
|
471
|
+
method_reliability = reliability
|
|
472
|
+
break
|
|
473
|
+
|
|
474
|
+
factors["method_reliability"] = method_reliability
|
|
475
|
+
|
|
476
|
+
# Sample sufficiency
|
|
477
|
+
if data_quality is not None:
|
|
478
|
+
sample_sufficiency = min(1.0, data_quality.sample_count / (min_samples * 10))
|
|
479
|
+
if data_quality.sample_count < min_samples:
|
|
480
|
+
warnings.append(f"Insufficient samples ({data_quality.sample_count} < {min_samples})")
|
|
481
|
+
else:
|
|
482
|
+
sample_sufficiency = 0.5
|
|
483
|
+
factors["sample_sufficiency"] = sample_sufficiency
|
|
484
|
+
|
|
485
|
+
# Result-specific scoring
|
|
486
|
+
result_factor = _score_result_value(result)
|
|
487
|
+
factors["result_validity"] = result_factor
|
|
488
|
+
|
|
489
|
+
# Combine factors
|
|
490
|
+
confidence = (
|
|
491
|
+
data_factor * 0.3
|
|
492
|
+
+ method_reliability * 0.25
|
|
493
|
+
+ sample_sufficiency * 0.25
|
|
494
|
+
+ result_factor * 0.2
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Determine category from confidence
|
|
498
|
+
category = ReliabilityCategory.from_confidence(confidence)
|
|
499
|
+
|
|
500
|
+
return AnalysisQualityScore(
|
|
501
|
+
confidence=confidence,
|
|
502
|
+
category=category,
|
|
503
|
+
data_quality_factor=data_factor,
|
|
504
|
+
method_reliability=method_reliability,
|
|
505
|
+
sample_sufficiency=sample_sufficiency,
|
|
506
|
+
factors=factors,
|
|
507
|
+
warnings=warnings,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _score_result_value(result: Any) -> float:
|
|
512
|
+
"""Score result validity based on value characteristics.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
result: Analysis result to score.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
Validity score between 0 and 1.
|
|
519
|
+
"""
|
|
520
|
+
if result is None:
|
|
521
|
+
return 0.0
|
|
522
|
+
|
|
523
|
+
# Handle numeric results
|
|
524
|
+
if isinstance(result, int | float):
|
|
525
|
+
if np.isnan(result) or np.isinf(result):
|
|
526
|
+
return 0.0
|
|
527
|
+
return 1.0
|
|
528
|
+
|
|
529
|
+
# Handle array results
|
|
530
|
+
if isinstance(result, np.ndarray):
|
|
531
|
+
valid_ratio = np.mean(np.isfinite(result))
|
|
532
|
+
return float(valid_ratio)
|
|
533
|
+
|
|
534
|
+
# Handle dict results
|
|
535
|
+
if isinstance(result, dict):
|
|
536
|
+
if not result:
|
|
537
|
+
return 0.3
|
|
538
|
+
return 1.0
|
|
539
|
+
|
|
540
|
+
# Handle list results
|
|
541
|
+
if isinstance(result, list):
|
|
542
|
+
if not result:
|
|
543
|
+
return 0.3
|
|
544
|
+
return 1.0
|
|
545
|
+
|
|
546
|
+
return 0.7 # Default for other types
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def combine_quality_scores(
|
|
550
|
+
scores: list[AnalysisQualityScore],
|
|
551
|
+
weights: list[float] | None = None,
|
|
552
|
+
) -> AnalysisQualityScore:
|
|
553
|
+
"""Combine multiple quality scores into one.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
scores: List of quality scores to combine
|
|
557
|
+
weights: Optional weights for each score
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Combined quality score
|
|
561
|
+
"""
|
|
562
|
+
if not scores:
|
|
563
|
+
return AnalysisQualityScore(
|
|
564
|
+
confidence=0.0,
|
|
565
|
+
category=ReliabilityCategory.UNRELIABLE,
|
|
566
|
+
data_quality_factor=0.0,
|
|
567
|
+
method_reliability=0.0,
|
|
568
|
+
sample_sufficiency=0.0,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
if weights is None:
|
|
572
|
+
weights = [1.0] * len(scores)
|
|
573
|
+
|
|
574
|
+
total_weight = sum(weights)
|
|
575
|
+
|
|
576
|
+
combined_confidence = (
|
|
577
|
+
sum(s.confidence * w for s, w in zip(scores, weights, strict=True)) / total_weight
|
|
578
|
+
)
|
|
579
|
+
combined_data = (
|
|
580
|
+
sum(s.data_quality_factor * w for s, w in zip(scores, weights, strict=True)) / total_weight
|
|
581
|
+
)
|
|
582
|
+
combined_method = (
|
|
583
|
+
sum(s.method_reliability * w for s, w in zip(scores, weights, strict=True)) / total_weight
|
|
584
|
+
)
|
|
585
|
+
combined_samples = (
|
|
586
|
+
sum(s.sample_sufficiency * w for s, w in zip(scores, weights, strict=True)) / total_weight
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
# Aggregate warnings
|
|
590
|
+
all_warnings = []
|
|
591
|
+
for score in scores:
|
|
592
|
+
all_warnings.extend(score.warnings)
|
|
593
|
+
|
|
594
|
+
# Determine category
|
|
595
|
+
category = ReliabilityCategory.from_confidence(combined_confidence)
|
|
596
|
+
|
|
597
|
+
return AnalysisQualityScore(
|
|
598
|
+
confidence=combined_confidence,
|
|
599
|
+
category=category,
|
|
600
|
+
data_quality_factor=combined_data,
|
|
601
|
+
method_reliability=combined_method,
|
|
602
|
+
sample_sufficiency=combined_samples,
|
|
603
|
+
warnings=list(set(all_warnings)),
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
__all__ = [
|
|
608
|
+
"METHOD_RELIABILITY",
|
|
609
|
+
"AnalysisQualityScore",
|
|
610
|
+
"DataQualityMetrics",
|
|
611
|
+
"ReliabilityCategory",
|
|
612
|
+
"assess_data_quality",
|
|
613
|
+
"calculate_quality_score",
|
|
614
|
+
"combine_quality_scores",
|
|
615
|
+
"score_analysis_result",
|
|
616
|
+
]
|