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,338 @@
|
|
|
1
|
+
"""Result explainability for analysis outputs.
|
|
2
|
+
|
|
3
|
+
Generates human-readable explanations for why analysis results
|
|
4
|
+
are reliable or unreliable.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.quality.explainer import explain_result
|
|
8
|
+
>>> from oscura.quality.scoring import calculate_quality_score
|
|
9
|
+
>>> score = calculate_quality_score(0.9, 0.8, 0.85)
|
|
10
|
+
>>> explanation = explain_result("frequency", 10.5e6, score, "fft")
|
|
11
|
+
>>> print(explanation)
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
- Result explainability for analysis outputs
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from oscura.quality.scoring import AnalysisQualityScore
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ResultExplanation:
|
|
28
|
+
"""Detailed explanation for an analysis result.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
result_name: Name of the result
|
|
32
|
+
result_value: Value of the result
|
|
33
|
+
quality: Quality score for the result
|
|
34
|
+
why_reliable: Reasons why result is reliable
|
|
35
|
+
why_unreliable: Reasons why result may be unreliable
|
|
36
|
+
recommendations: Actionable recommendations
|
|
37
|
+
alternative_methods: Alternative analysis methods
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> explanation = ResultExplanation(
|
|
41
|
+
... result_name="frequency",
|
|
42
|
+
... result_value=10.5e6,
|
|
43
|
+
... quality=score,
|
|
44
|
+
... )
|
|
45
|
+
>>> print(explanation.to_narrative())
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
result_name: str
|
|
49
|
+
result_value: Any
|
|
50
|
+
quality: AnalysisQualityScore
|
|
51
|
+
|
|
52
|
+
# Detailed reasoning
|
|
53
|
+
why_reliable: list[str] = field(default_factory=list)
|
|
54
|
+
why_unreliable: list[str] = field(default_factory=list)
|
|
55
|
+
recommendations: list[str] = field(default_factory=list)
|
|
56
|
+
alternative_methods: list[str] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
def to_narrative(self) -> str:
|
|
59
|
+
"""Generate narrative explanation.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Human-readable narrative explanation string
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> narrative = explanation.to_narrative()
|
|
66
|
+
>>> print(narrative)
|
|
67
|
+
Result: frequency = 1.050e+07
|
|
68
|
+
Confidence: 85.0% (high)
|
|
69
|
+
|
|
70
|
+
Why this result is reliable:
|
|
71
|
+
✓ FFT is mathematically exact for periodic signals
|
|
72
|
+
✓ High quality input data
|
|
73
|
+
"""
|
|
74
|
+
lines = []
|
|
75
|
+
|
|
76
|
+
# Header with result
|
|
77
|
+
lines.append(f"Result: {self.result_name} = {self._format_value(self.result_value)}")
|
|
78
|
+
lines.append(f"Confidence: {self.quality.confidence:.1%} ({self.quality.category.value})")
|
|
79
|
+
lines.append("")
|
|
80
|
+
|
|
81
|
+
# Why reliable
|
|
82
|
+
if self.why_reliable:
|
|
83
|
+
lines.append("Why this result is reliable:")
|
|
84
|
+
for reason in self.why_reliable:
|
|
85
|
+
lines.append(f" ✓ {reason}")
|
|
86
|
+
lines.append("")
|
|
87
|
+
|
|
88
|
+
# Why unreliable
|
|
89
|
+
if self.why_unreliable:
|
|
90
|
+
lines.append("Concerns affecting reliability:")
|
|
91
|
+
for reason in self.why_unreliable:
|
|
92
|
+
lines.append(f" ⚠ {reason}")
|
|
93
|
+
lines.append("")
|
|
94
|
+
|
|
95
|
+
# Recommendations
|
|
96
|
+
if self.recommendations:
|
|
97
|
+
lines.append("Recommendations:")
|
|
98
|
+
for rec in self.recommendations:
|
|
99
|
+
lines.append(f" → {rec}")
|
|
100
|
+
lines.append("")
|
|
101
|
+
|
|
102
|
+
# Alternatives
|
|
103
|
+
if self.alternative_methods:
|
|
104
|
+
lines.append("Alternative approaches:")
|
|
105
|
+
for alt in self.alternative_methods:
|
|
106
|
+
lines.append(f" • {alt}")
|
|
107
|
+
|
|
108
|
+
return "\n".join(lines)
|
|
109
|
+
|
|
110
|
+
def _format_value(self, value: Any) -> str:
|
|
111
|
+
"""Format result value for display.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
value: Value to format
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Formatted string representation
|
|
118
|
+
"""
|
|
119
|
+
if isinstance(value, float):
|
|
120
|
+
if abs(value) < 0.001 or abs(value) > 10000:
|
|
121
|
+
return f"{value:.3e}"
|
|
122
|
+
return f"{value:.4f}"
|
|
123
|
+
return str(value)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ResultExplainer:
|
|
127
|
+
"""Generate explanations for analysis results.
|
|
128
|
+
|
|
129
|
+
This class provides method-specific explanations for common
|
|
130
|
+
analysis techniques used in signal processing.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> explainer = ResultExplainer()
|
|
134
|
+
>>> explanation = explainer.explain(
|
|
135
|
+
... result_name="frequency",
|
|
136
|
+
... result_value=10.5e6,
|
|
137
|
+
... quality=score,
|
|
138
|
+
... method_name="fft",
|
|
139
|
+
... )
|
|
140
|
+
>>> print(explanation.to_narrative())
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
# Method-specific explanation templates
|
|
144
|
+
METHOD_EXPLANATIONS: ClassVar[dict[str, Any]] = {
|
|
145
|
+
"fft": {
|
|
146
|
+
"reliable": [
|
|
147
|
+
"FFT is mathematically exact for periodic signals",
|
|
148
|
+
"Sufficient frequency resolution for analysis",
|
|
149
|
+
],
|
|
150
|
+
"concerns": {
|
|
151
|
+
"low_snr": "Low SNR may cause spurious frequency peaks",
|
|
152
|
+
"short_duration": "Short capture limits frequency resolution",
|
|
153
|
+
"windowing": "Spectral leakage may affect amplitude accuracy",
|
|
154
|
+
},
|
|
155
|
+
"alternatives": ["Welch method for noisy signals", "Zero-padding for interpolation"],
|
|
156
|
+
},
|
|
157
|
+
"edge_detection": {
|
|
158
|
+
"reliable": [
|
|
159
|
+
"Multiple consistent edges detected",
|
|
160
|
+
"Clear transition between logic levels",
|
|
161
|
+
],
|
|
162
|
+
"concerns": {
|
|
163
|
+
"noise": "Noise may cause false edge detections",
|
|
164
|
+
"ringing": "Signal ringing may create multiple edge crossings",
|
|
165
|
+
},
|
|
166
|
+
"alternatives": ["Interpolated edge timing", "Histogram-based threshold"],
|
|
167
|
+
},
|
|
168
|
+
"statistics": {
|
|
169
|
+
"reliable": [
|
|
170
|
+
"Statistical methods are mathematically well-defined",
|
|
171
|
+
"Large sample size provides reliable estimates",
|
|
172
|
+
],
|
|
173
|
+
"concerns": {
|
|
174
|
+
"outliers": "Outliers may skew mean and variance",
|
|
175
|
+
"non_normal": "Non-normal distribution affects some statistics",
|
|
176
|
+
},
|
|
177
|
+
"alternatives": ["Robust statistics (median, MAD)", "Trimmed mean"],
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
def explain(
|
|
182
|
+
self,
|
|
183
|
+
result_name: str,
|
|
184
|
+
result_value: Any,
|
|
185
|
+
quality: AnalysisQualityScore,
|
|
186
|
+
method_name: str | None = None,
|
|
187
|
+
) -> ResultExplanation:
|
|
188
|
+
"""Generate explanation for an analysis result.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
result_name: Name of the result
|
|
192
|
+
result_value: The result value
|
|
193
|
+
quality: Quality score for the result
|
|
194
|
+
method_name: Optional method name for specific explanations
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ResultExplanation with detailed reasoning
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
>>> explanation = explainer.explain(
|
|
201
|
+
... "frequency",
|
|
202
|
+
... 10.5e6,
|
|
203
|
+
... score,
|
|
204
|
+
... "fft"
|
|
205
|
+
... )
|
|
206
|
+
>>> print(explanation.to_narrative())
|
|
207
|
+
"""
|
|
208
|
+
explanation = ResultExplanation(
|
|
209
|
+
result_name=result_name,
|
|
210
|
+
result_value=result_value,
|
|
211
|
+
quality=quality,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Get method-specific templates
|
|
215
|
+
method_key = self._get_method_key(method_name or result_name)
|
|
216
|
+
templates = self.METHOD_EXPLANATIONS.get(method_key, {})
|
|
217
|
+
|
|
218
|
+
# Generate reliability reasons
|
|
219
|
+
if quality.is_reliable:
|
|
220
|
+
explanation.why_reliable = self._generate_reliable_reasons(quality, templates)
|
|
221
|
+
|
|
222
|
+
explanation.why_unreliable = self._generate_unreliable_reasons(quality, templates)
|
|
223
|
+
explanation.recommendations = quality.get_recommendations()
|
|
224
|
+
explanation.alternative_methods = list(templates.get("alternatives", []))
|
|
225
|
+
|
|
226
|
+
return explanation
|
|
227
|
+
|
|
228
|
+
def _get_method_key(self, name: str) -> str:
|
|
229
|
+
"""Extract method key from name.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
name: Method or result name
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Standardized method key
|
|
236
|
+
"""
|
|
237
|
+
name_lower = name.lower()
|
|
238
|
+
if "fft" in name_lower or "spectral" in name_lower:
|
|
239
|
+
return "fft"
|
|
240
|
+
if "edge" in name_lower:
|
|
241
|
+
return "edge_detection"
|
|
242
|
+
if "stat" in name_lower or "mean" in name_lower or "std" in name_lower:
|
|
243
|
+
return "statistics"
|
|
244
|
+
return "generic"
|
|
245
|
+
|
|
246
|
+
def _generate_reliable_reasons(
|
|
247
|
+
self,
|
|
248
|
+
quality: AnalysisQualityScore,
|
|
249
|
+
templates: dict[str, Any],
|
|
250
|
+
) -> list[str]:
|
|
251
|
+
"""Generate reasons why result is reliable.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
quality: Quality score
|
|
255
|
+
templates: Method-specific explanation templates
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
List of reasons
|
|
259
|
+
"""
|
|
260
|
+
reasons = []
|
|
261
|
+
|
|
262
|
+
# Add template reasons
|
|
263
|
+
reasons.extend(templates.get("reliable", []))
|
|
264
|
+
|
|
265
|
+
# Add factor-based reasons
|
|
266
|
+
if quality.data_quality_factor >= 0.8:
|
|
267
|
+
reasons.append("High quality input data")
|
|
268
|
+
if quality.sample_sufficiency >= 0.9:
|
|
269
|
+
reasons.append("Sufficient sample count for analysis")
|
|
270
|
+
if quality.method_reliability >= 0.85:
|
|
271
|
+
reasons.append("Analysis method has high inherent reliability")
|
|
272
|
+
|
|
273
|
+
return reasons[:4] # Limit to top 4
|
|
274
|
+
|
|
275
|
+
def _generate_unreliable_reasons(
|
|
276
|
+
self,
|
|
277
|
+
quality: AnalysisQualityScore,
|
|
278
|
+
templates: dict[str, Any],
|
|
279
|
+
) -> list[str]:
|
|
280
|
+
"""Generate reasons why result may be unreliable.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
quality: Quality score
|
|
284
|
+
templates: Method-specific explanation templates
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
List of concerns
|
|
288
|
+
"""
|
|
289
|
+
reasons = []
|
|
290
|
+
|
|
291
|
+
# Add warnings
|
|
292
|
+
reasons.extend(quality.warnings)
|
|
293
|
+
|
|
294
|
+
# Add factor-based concerns
|
|
295
|
+
if quality.data_quality_factor < 0.5:
|
|
296
|
+
reasons.append("Input data quality is poor")
|
|
297
|
+
if quality.sample_sufficiency < 0.5:
|
|
298
|
+
reasons.append("Insufficient samples for reliable estimate")
|
|
299
|
+
if quality.method_reliability < 0.6:
|
|
300
|
+
reasons.append("Analysis method has lower reliability")
|
|
301
|
+
|
|
302
|
+
return reasons
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def explain_result(
|
|
306
|
+
result_name: str,
|
|
307
|
+
result_value: Any,
|
|
308
|
+
quality: AnalysisQualityScore,
|
|
309
|
+
method_name: str | None = None,
|
|
310
|
+
) -> str:
|
|
311
|
+
"""Convenience function to get result explanation as text.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
result_name: Name of the result
|
|
315
|
+
result_value: The result value
|
|
316
|
+
quality: Quality score
|
|
317
|
+
method_name: Optional method name
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Human-readable explanation string
|
|
321
|
+
|
|
322
|
+
Example:
|
|
323
|
+
>>> explanation = explain_result("frequency", 10.5e6, score, "fft")
|
|
324
|
+
>>> print(explanation)
|
|
325
|
+
Result: frequency = 1.050e+07
|
|
326
|
+
Confidence: 85.0% (high)
|
|
327
|
+
...
|
|
328
|
+
"""
|
|
329
|
+
explainer = ResultExplainer()
|
|
330
|
+
explanation = explainer.explain(result_name, result_value, quality, method_name)
|
|
331
|
+
return explanation.to_narrative()
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
__all__ = [
|
|
335
|
+
"ResultExplainer",
|
|
336
|
+
"ResultExplanation",
|
|
337
|
+
"explain_result",
|
|
338
|
+
]
|