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,216 @@
|
|
|
1
|
+
"""Correlation ID management for distributed tracing.
|
|
2
|
+
|
|
3
|
+
This module provides correlation ID generation and propagation for
|
|
4
|
+
request tracing across Oscura operations.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.correlation import with_correlation_id, get_correlation_id
|
|
9
|
+
>>> @with_correlation_id()
|
|
10
|
+
... def analyze_trace(data):
|
|
11
|
+
... corr_id = get_correlation_id()
|
|
12
|
+
... print(f"Processing with correlation ID: {corr_id}")
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
- Distributed tracing best practices
|
|
16
|
+
- Thread-local and async-safe context management
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import contextvars
|
|
23
|
+
import functools
|
|
24
|
+
import uuid
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from typing import Any, ParamSpec, TypeVar
|
|
27
|
+
|
|
28
|
+
# Context variable for correlation ID (thread-safe and async-safe)
|
|
29
|
+
_correlation_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
30
|
+
"correlation_id", default=None
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_correlation_id() -> str | None:
|
|
35
|
+
"""Get the current correlation ID for request tracing.
|
|
36
|
+
|
|
37
|
+
Returns the correlation ID from the current context, or None if
|
|
38
|
+
no correlation context is active.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Current correlation ID (UUID string) or None.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> with CorrelationContext():
|
|
45
|
+
... corr_id = get_correlation_id()
|
|
46
|
+
... print(f"Correlation ID: {corr_id}")
|
|
47
|
+
|
|
48
|
+
References:
|
|
49
|
+
LOG-004: Correlation ID Injection
|
|
50
|
+
"""
|
|
51
|
+
return _correlation_id.get()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def set_correlation_id(corr_id: str) -> None:
|
|
55
|
+
"""Set the correlation ID for the current context.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
corr_id: Correlation ID to set (typically a UUID string).
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> set_correlation_id("550e8400-e29b-41d4-a716-446655440000")
|
|
62
|
+
>>> print(get_correlation_id())
|
|
63
|
+
550e8400-e29b-41d4-a716-446655440000
|
|
64
|
+
|
|
65
|
+
References:
|
|
66
|
+
LOG-004: Correlation ID Injection
|
|
67
|
+
"""
|
|
68
|
+
_correlation_id.set(corr_id)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CorrelationContext:
|
|
72
|
+
"""Context manager for correlation ID scoping.
|
|
73
|
+
|
|
74
|
+
Automatically generates a UUID correlation ID if not provided,
|
|
75
|
+
and ensures proper cleanup when exiting the context.
|
|
76
|
+
|
|
77
|
+
Thread-safe and async-safe using contextvars.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
corr_id: Optional correlation ID. If None, generates a new UUID.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> # Auto-generate correlation ID
|
|
84
|
+
>>> with CorrelationContext() as corr_id:
|
|
85
|
+
... print(f"Generated ID: {corr_id}")
|
|
86
|
+
... # All operations here have this correlation ID
|
|
87
|
+
... result = some_analysis()
|
|
88
|
+
|
|
89
|
+
>>> # Use explicit correlation ID
|
|
90
|
+
>>> with CorrelationContext("my-custom-id") as corr_id:
|
|
91
|
+
... print(f"Using ID: {corr_id}")
|
|
92
|
+
|
|
93
|
+
References:
|
|
94
|
+
LOG-004: Correlation ID Injection
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, corr_id: str | None = None):
|
|
98
|
+
"""Initialize correlation context.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
corr_id: Correlation ID to use, or None to auto-generate.
|
|
102
|
+
"""
|
|
103
|
+
self.corr_id = corr_id or str(uuid.uuid4())
|
|
104
|
+
self.token: contextvars.Token[str | None] | None = None
|
|
105
|
+
|
|
106
|
+
def __enter__(self) -> str:
|
|
107
|
+
"""Enter the correlation context.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The correlation ID for this context.
|
|
111
|
+
"""
|
|
112
|
+
self.token = _correlation_id.set(self.corr_id)
|
|
113
|
+
return self.corr_id
|
|
114
|
+
|
|
115
|
+
def __exit__(self, *args: Any) -> None:
|
|
116
|
+
"""Exit the correlation context and restore previous value."""
|
|
117
|
+
if self.token:
|
|
118
|
+
_correlation_id.reset(self.token)
|
|
119
|
+
|
|
120
|
+
async def __aenter__(self) -> str:
|
|
121
|
+
"""Async enter the correlation context.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The correlation ID for this context.
|
|
125
|
+
"""
|
|
126
|
+
self.token = _correlation_id.set(self.corr_id)
|
|
127
|
+
return self.corr_id
|
|
128
|
+
|
|
129
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
130
|
+
"""Async exit the correlation context and restore previous value."""
|
|
131
|
+
if self.token:
|
|
132
|
+
_correlation_id.reset(self.token)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Type variables for generic decorator typing
|
|
136
|
+
P = ParamSpec("P")
|
|
137
|
+
R = TypeVar("R")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def with_correlation_id(
|
|
141
|
+
corr_id: str | None = None,
|
|
142
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
143
|
+
"""Decorator to set correlation ID for a function call.
|
|
144
|
+
|
|
145
|
+
Automatically wraps the function in a CorrelationContext, ensuring
|
|
146
|
+
all operations within the function are traced with the same ID.
|
|
147
|
+
|
|
148
|
+
Supports both synchronous and asynchronous functions.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
corr_id: Correlation ID to use, or None to auto-generate.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Decorator function.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> @with_correlation_id()
|
|
158
|
+
... def analyze_trace(trace_data):
|
|
159
|
+
... logger.info("Starting analysis")
|
|
160
|
+
... # All logs will include the correlation ID
|
|
161
|
+
... result = compute_fft(trace_data)
|
|
162
|
+
... return result
|
|
163
|
+
|
|
164
|
+
>>> @with_correlation_id("batch-job-123")
|
|
165
|
+
... def process_batch(files):
|
|
166
|
+
... for f in files:
|
|
167
|
+
... load_and_analyze(f)
|
|
168
|
+
|
|
169
|
+
>>> @with_correlation_id("async-task")
|
|
170
|
+
... async def async_analysis(data):
|
|
171
|
+
... await some_async_operation()
|
|
172
|
+
... return get_correlation_id()
|
|
173
|
+
|
|
174
|
+
References:
|
|
175
|
+
LOG-004: Correlation ID Injection
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
179
|
+
if asyncio.iscoroutinefunction(func):
|
|
180
|
+
# Async function wrapper
|
|
181
|
+
@functools.wraps(func)
|
|
182
|
+
async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
183
|
+
with CorrelationContext(corr_id):
|
|
184
|
+
# func returns a Coroutine, we need to await it
|
|
185
|
+
coro = func(*args, **kwargs)
|
|
186
|
+
# Type assertion for mypy - we know this is a coroutine
|
|
187
|
+
return await coro # type: ignore[misc, no-any-return]
|
|
188
|
+
|
|
189
|
+
return async_wrapper # type: ignore[return-value]
|
|
190
|
+
else:
|
|
191
|
+
# Sync function wrapper
|
|
192
|
+
@functools.wraps(func)
|
|
193
|
+
def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
194
|
+
with CorrelationContext(corr_id):
|
|
195
|
+
return func(*args, **kwargs)
|
|
196
|
+
|
|
197
|
+
return sync_wrapper # type: ignore[return-value]
|
|
198
|
+
|
|
199
|
+
return decorator
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def generate_correlation_id() -> str:
|
|
203
|
+
"""Generate a new correlation ID (UUID4).
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
New correlation ID as string.
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
>>> corr_id = generate_correlation_id()
|
|
210
|
+
>>> with CorrelationContext(corr_id):
|
|
211
|
+
... process_data()
|
|
212
|
+
|
|
213
|
+
References:
|
|
214
|
+
LOG-004: Correlation ID Injection
|
|
215
|
+
"""
|
|
216
|
+
return str(uuid.uuid4())
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""Cross-domain correlation for analysis results.
|
|
2
|
+
|
|
3
|
+
Enables results from different analysis domains to inform and validate
|
|
4
|
+
each other, improving overall confidence and detecting inconsistencies.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.cross_domain import correlate_results
|
|
9
|
+
>>> from oscura.reporting.config import AnalysisDomain
|
|
10
|
+
>>> results = {
|
|
11
|
+
... AnalysisDomain.SPECTRAL: {'dominant_frequency': 1000.0},
|
|
12
|
+
... AnalysisDomain.TIMING: {'period': 0.001}
|
|
13
|
+
... }
|
|
14
|
+
>>> correlation = correlate_results(results)
|
|
15
|
+
>>> print(f"Coherence: {correlation.overall_coherence:.2f}")
|
|
16
|
+
>>> print(f"Agreements: {correlation.agreements_detected}")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
from oscura.reporting.config import AnalysisDomain
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class CrossDomainInsight:
|
|
34
|
+
"""An insight derived from cross-domain correlation.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
insight_type: Type of insight ("agreement", "conflict", "implication").
|
|
38
|
+
source_domains: Analysis domains that contributed to this insight.
|
|
39
|
+
description: Human-readable description of the insight.
|
|
40
|
+
confidence_impact: How much this affects confidence (-1.0 to +1.0).
|
|
41
|
+
details: Additional details specific to this insight.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
insight_type: str # "agreement", "conflict", "implication"
|
|
45
|
+
source_domains: list[AnalysisDomain]
|
|
46
|
+
description: str
|
|
47
|
+
confidence_impact: float # How much this affects confidence (-1 to +1)
|
|
48
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
def __post_init__(self) -> None:
|
|
51
|
+
"""Validate confidence impact after initialization."""
|
|
52
|
+
if not -1.0 <= self.confidence_impact <= 1.0:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Confidence impact must be in [-1.0, 1.0], got {self.confidence_impact}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class CorrelationResult:
|
|
60
|
+
"""Result of cross-domain correlation analysis.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
insights: List of discovered cross-domain insights.
|
|
64
|
+
confidence_adjustments: Per-domain confidence adjustments.
|
|
65
|
+
conflicts_detected: Number of conflicts found.
|
|
66
|
+
agreements_detected: Number of agreements found.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
insights: list[CrossDomainInsight] = field(default_factory=list)
|
|
70
|
+
confidence_adjustments: dict[str, float] = field(default_factory=dict)
|
|
71
|
+
conflicts_detected: int = 0
|
|
72
|
+
agreements_detected: int = 0
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def overall_coherence(self) -> float:
|
|
76
|
+
"""Calculate overall coherence score (0-1).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Coherence score based on agreement/conflict ratio.
|
|
80
|
+
"""
|
|
81
|
+
if not self.insights:
|
|
82
|
+
return 0.5
|
|
83
|
+
total = self.agreements_detected + self.conflicts_detected
|
|
84
|
+
if total == 0:
|
|
85
|
+
return 0.5
|
|
86
|
+
return self.agreements_detected / total
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Define which domains are semantically related
|
|
90
|
+
DOMAIN_AFFINITY: dict[AnalysisDomain, list[AnalysisDomain]] = {
|
|
91
|
+
AnalysisDomain.DIGITAL: [AnalysisDomain.TIMING, AnalysisDomain.PROTOCOLS],
|
|
92
|
+
AnalysisDomain.TIMING: [AnalysisDomain.DIGITAL, AnalysisDomain.JITTER, AnalysisDomain.SPECTRAL],
|
|
93
|
+
AnalysisDomain.SPECTRAL: [
|
|
94
|
+
AnalysisDomain.JITTER,
|
|
95
|
+
AnalysisDomain.STATISTICS,
|
|
96
|
+
AnalysisDomain.TIMING,
|
|
97
|
+
],
|
|
98
|
+
AnalysisDomain.WAVEFORM: [AnalysisDomain.STATISTICS],
|
|
99
|
+
AnalysisDomain.STATISTICS: [AnalysisDomain.SPECTRAL, AnalysisDomain.WAVEFORM],
|
|
100
|
+
AnalysisDomain.JITTER: [AnalysisDomain.TIMING, AnalysisDomain.EYE, AnalysisDomain.SPECTRAL],
|
|
101
|
+
AnalysisDomain.EYE: [AnalysisDomain.JITTER, AnalysisDomain.SIGNAL_INTEGRITY],
|
|
102
|
+
AnalysisDomain.PATTERNS: [AnalysisDomain.PROTOCOLS, AnalysisDomain.INFERENCE],
|
|
103
|
+
AnalysisDomain.INFERENCE: [AnalysisDomain.PATTERNS, AnalysisDomain.PROTOCOLS],
|
|
104
|
+
AnalysisDomain.PROTOCOLS: [AnalysisDomain.DIGITAL, AnalysisDomain.PATTERNS],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CrossDomainCorrelator:
|
|
109
|
+
"""Correlate results across analysis domains."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, tolerance: float = 0.1):
|
|
112
|
+
"""Initialize correlator.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
tolerance: Tolerance for value comparisons (fraction).
|
|
116
|
+
"""
|
|
117
|
+
self.tolerance = tolerance
|
|
118
|
+
self._correlation_rules = self._build_correlation_rules()
|
|
119
|
+
|
|
120
|
+
def correlate(
|
|
121
|
+
self,
|
|
122
|
+
results: dict[AnalysisDomain, dict[str, Any]],
|
|
123
|
+
) -> CorrelationResult:
|
|
124
|
+
"""Find correlations between domain results.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
results: Dictionary mapping domains to their results.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
CorrelationResult with insights and adjustments.
|
|
131
|
+
"""
|
|
132
|
+
correlation_result = CorrelationResult()
|
|
133
|
+
|
|
134
|
+
# Only correlate domains that have results
|
|
135
|
+
active_domains = [d for d, r in results.items() if r]
|
|
136
|
+
|
|
137
|
+
# Track checked pairs to avoid duplicates
|
|
138
|
+
checked_pairs: set[tuple[AnalysisDomain, AnalysisDomain]] = set()
|
|
139
|
+
|
|
140
|
+
# Check each pair of related domains
|
|
141
|
+
for domain in active_domains:
|
|
142
|
+
related = DOMAIN_AFFINITY.get(domain, [])
|
|
143
|
+
for related_domain in related:
|
|
144
|
+
if related_domain in active_domains:
|
|
145
|
+
# Create canonical pair ordering to avoid duplicates
|
|
146
|
+
pair_tuple: tuple[AnalysisDomain, AnalysisDomain] = tuple(
|
|
147
|
+
sorted([domain, related_domain], key=lambda d: d.value)
|
|
148
|
+
) # type: ignore[assignment]
|
|
149
|
+
if pair_tuple not in checked_pairs:
|
|
150
|
+
checked_pairs.add(pair_tuple)
|
|
151
|
+
insights = self._correlate_pair(
|
|
152
|
+
domain, results[domain], related_domain, results[related_domain]
|
|
153
|
+
)
|
|
154
|
+
correlation_result.insights.extend(insights)
|
|
155
|
+
|
|
156
|
+
# Count agreements and conflicts
|
|
157
|
+
for insight in correlation_result.insights:
|
|
158
|
+
if insight.insight_type == "agreement":
|
|
159
|
+
correlation_result.agreements_detected += 1
|
|
160
|
+
elif insight.insight_type == "conflict":
|
|
161
|
+
correlation_result.conflicts_detected += 1
|
|
162
|
+
|
|
163
|
+
# Calculate confidence adjustments
|
|
164
|
+
correlation_result.confidence_adjustments = self._calculate_adjustments(
|
|
165
|
+
correlation_result.insights
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return correlation_result
|
|
169
|
+
|
|
170
|
+
def _correlate_pair(
|
|
171
|
+
self,
|
|
172
|
+
domain1: AnalysisDomain,
|
|
173
|
+
results1: dict[str, Any],
|
|
174
|
+
domain2: AnalysisDomain,
|
|
175
|
+
results2: dict[str, Any],
|
|
176
|
+
) -> list[CrossDomainInsight]:
|
|
177
|
+
"""Correlate a pair of domains."""
|
|
178
|
+
insights = []
|
|
179
|
+
|
|
180
|
+
# Apply correlation rules
|
|
181
|
+
for rule in self._correlation_rules:
|
|
182
|
+
if rule["domains"] == {domain1, domain2} or rule["domains"] == {domain2, domain1}:
|
|
183
|
+
try:
|
|
184
|
+
insight = rule["check"](results1, results2, domain1, domain2)
|
|
185
|
+
if insight:
|
|
186
|
+
insights.append(insight)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.debug(f"Correlation rule failed: {e}")
|
|
189
|
+
|
|
190
|
+
return insights
|
|
191
|
+
|
|
192
|
+
def _build_correlation_rules(self) -> list[dict[str, Any]]:
|
|
193
|
+
"""Build correlation rules for domain pairs."""
|
|
194
|
+
return [
|
|
195
|
+
{
|
|
196
|
+
"domains": {AnalysisDomain.SPECTRAL, AnalysisDomain.TIMING},
|
|
197
|
+
"check": self._check_frequency_timing_agreement,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"domains": {AnalysisDomain.DIGITAL, AnalysisDomain.TIMING},
|
|
201
|
+
"check": self._check_digital_timing_consistency,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"domains": {AnalysisDomain.JITTER, AnalysisDomain.EYE},
|
|
205
|
+
"check": self._check_jitter_eye_correlation,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"domains": {AnalysisDomain.WAVEFORM, AnalysisDomain.STATISTICS},
|
|
209
|
+
"check": self._check_waveform_stats_consistency,
|
|
210
|
+
},
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
def _check_frequency_timing_agreement(
|
|
214
|
+
self,
|
|
215
|
+
results1: dict[str, Any],
|
|
216
|
+
results2: dict[str, Any],
|
|
217
|
+
domain1: AnalysisDomain,
|
|
218
|
+
domain2: AnalysisDomain,
|
|
219
|
+
) -> CrossDomainInsight | None:
|
|
220
|
+
"""Check if spectral frequency matches timing period."""
|
|
221
|
+
# Extract frequency from spectral results
|
|
222
|
+
spectral_freq = self._extract_value(
|
|
223
|
+
results1 if domain1 == AnalysisDomain.SPECTRAL else results2,
|
|
224
|
+
["dominant_frequency", "peak_frequency", "fundamental"],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Extract period from timing results
|
|
228
|
+
timing_period = self._extract_value(
|
|
229
|
+
results2 if domain2 == AnalysisDomain.TIMING else results1,
|
|
230
|
+
["period", "avg_period", "mean_period"],
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if spectral_freq and timing_period and spectral_freq > 0 and timing_period > 0:
|
|
234
|
+
expected_period = 1.0 / spectral_freq
|
|
235
|
+
ratio = timing_period / expected_period
|
|
236
|
+
|
|
237
|
+
if 0.9 < ratio < 1.1: # Within 10%
|
|
238
|
+
return CrossDomainInsight(
|
|
239
|
+
insight_type="agreement",
|
|
240
|
+
source_domains=[AnalysisDomain.SPECTRAL, AnalysisDomain.TIMING],
|
|
241
|
+
description=(
|
|
242
|
+
f"Spectral frequency ({spectral_freq:.1f} Hz) matches "
|
|
243
|
+
f"timing period ({timing_period:.3e} s)"
|
|
244
|
+
),
|
|
245
|
+
confidence_impact=0.15,
|
|
246
|
+
details={
|
|
247
|
+
"spectral_freq": spectral_freq,
|
|
248
|
+
"timing_period": timing_period,
|
|
249
|
+
"ratio": ratio,
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
elif ratio < 0.5 or ratio > 2.0:
|
|
253
|
+
return CrossDomainInsight(
|
|
254
|
+
insight_type="conflict",
|
|
255
|
+
source_domains=[AnalysisDomain.SPECTRAL, AnalysisDomain.TIMING],
|
|
256
|
+
description=(
|
|
257
|
+
f"Spectral frequency ({spectral_freq:.1f} Hz) conflicts with "
|
|
258
|
+
f"timing period ({timing_period:.3e} s)"
|
|
259
|
+
),
|
|
260
|
+
confidence_impact=-0.2,
|
|
261
|
+
details={
|
|
262
|
+
"spectral_freq": spectral_freq,
|
|
263
|
+
"timing_period": timing_period,
|
|
264
|
+
"ratio": ratio,
|
|
265
|
+
},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
def _check_digital_timing_consistency(
|
|
271
|
+
self,
|
|
272
|
+
results1: dict[str, Any],
|
|
273
|
+
results2: dict[str, Any],
|
|
274
|
+
domain1: AnalysisDomain,
|
|
275
|
+
domain2: AnalysisDomain,
|
|
276
|
+
) -> CrossDomainInsight | None:
|
|
277
|
+
"""Check if digital edge count matches timing analysis."""
|
|
278
|
+
digital_results = results1 if domain1 == AnalysisDomain.DIGITAL else results2
|
|
279
|
+
timing_results = results2 if domain2 == AnalysisDomain.TIMING else results1
|
|
280
|
+
|
|
281
|
+
edge_count = self._extract_value(
|
|
282
|
+
digital_results, ["edge_count", "num_edges", "transitions"]
|
|
283
|
+
)
|
|
284
|
+
timing_edges = self._extract_value(timing_results, ["edge_count", "transitions_detected"])
|
|
285
|
+
|
|
286
|
+
if edge_count and timing_edges:
|
|
287
|
+
if abs(edge_count - timing_edges) <= 2:
|
|
288
|
+
return CrossDomainInsight(
|
|
289
|
+
insight_type="agreement",
|
|
290
|
+
source_domains=[AnalysisDomain.DIGITAL, AnalysisDomain.TIMING],
|
|
291
|
+
description=(f"Edge counts agree: Digital={edge_count}, Timing={timing_edges}"),
|
|
292
|
+
confidence_impact=0.1,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
def _check_jitter_eye_correlation(
|
|
298
|
+
self,
|
|
299
|
+
results1: dict[str, Any],
|
|
300
|
+
results2: dict[str, Any],
|
|
301
|
+
domain1: AnalysisDomain,
|
|
302
|
+
domain2: AnalysisDomain,
|
|
303
|
+
) -> CrossDomainInsight | None:
|
|
304
|
+
"""Check jitter vs eye diagram correlation."""
|
|
305
|
+
jitter_results = results1 if domain1 == AnalysisDomain.JITTER else results2
|
|
306
|
+
eye_results = results2 if domain2 == AnalysisDomain.EYE else results1
|
|
307
|
+
|
|
308
|
+
total_jitter = self._extract_value(jitter_results, ["total_jitter", "tj", "jitter_pp"])
|
|
309
|
+
eye_width = self._extract_value(eye_results, ["eye_width", "horizontal_opening"])
|
|
310
|
+
|
|
311
|
+
# High jitter should correlate with narrow eye
|
|
312
|
+
if total_jitter is not None and eye_width is not None:
|
|
313
|
+
return CrossDomainInsight(
|
|
314
|
+
insight_type="implication",
|
|
315
|
+
source_domains=[AnalysisDomain.JITTER, AnalysisDomain.EYE],
|
|
316
|
+
description=(f"Jitter ({total_jitter:.2e}) affects eye width ({eye_width:.2e})"),
|
|
317
|
+
confidence_impact=0.05,
|
|
318
|
+
details={"jitter": total_jitter, "eye_width": eye_width},
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
def _check_waveform_stats_consistency(
|
|
324
|
+
self,
|
|
325
|
+
results1: dict[str, Any],
|
|
326
|
+
results2: dict[str, Any],
|
|
327
|
+
domain1: AnalysisDomain,
|
|
328
|
+
domain2: AnalysisDomain,
|
|
329
|
+
) -> CrossDomainInsight | None:
|
|
330
|
+
"""Check waveform measurements vs statistical analysis."""
|
|
331
|
+
waveform_results = results1 if domain1 == AnalysisDomain.WAVEFORM else results2
|
|
332
|
+
stats_results = results2 if domain2 == AnalysisDomain.STATISTICS else results1
|
|
333
|
+
|
|
334
|
+
wf_amplitude = self._extract_value(waveform_results, ["amplitude", "vpp", "peak_to_peak"])
|
|
335
|
+
stats_std = self._extract_value(stats_results, ["std", "standard_deviation", "stdev"])
|
|
336
|
+
|
|
337
|
+
if wf_amplitude and stats_std:
|
|
338
|
+
# For periodic signals, amplitude ~ 2.83 * std (for sine wave)
|
|
339
|
+
expected_ratio = wf_amplitude / (2.83 * stats_std) if stats_std > 0 else 0
|
|
340
|
+
|
|
341
|
+
if 0.8 < expected_ratio < 1.2:
|
|
342
|
+
return CrossDomainInsight(
|
|
343
|
+
insight_type="agreement",
|
|
344
|
+
source_domains=[AnalysisDomain.WAVEFORM, AnalysisDomain.STATISTICS],
|
|
345
|
+
description="Waveform amplitude consistent with statistical std dev",
|
|
346
|
+
confidence_impact=0.1,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
def _extract_value(
|
|
352
|
+
self,
|
|
353
|
+
results: dict[str, Any],
|
|
354
|
+
keys: list[str],
|
|
355
|
+
) -> float | None:
|
|
356
|
+
"""Extract a value from results using multiple possible keys."""
|
|
357
|
+
for key in keys:
|
|
358
|
+
# Try direct key
|
|
359
|
+
if key in results:
|
|
360
|
+
val = results[key]
|
|
361
|
+
if isinstance(val, int | float) and not np.isnan(val):
|
|
362
|
+
return float(val)
|
|
363
|
+
|
|
364
|
+
# Try nested keys
|
|
365
|
+
for result_val in results.values():
|
|
366
|
+
if isinstance(result_val, dict) and key in result_val:
|
|
367
|
+
val = result_val[key]
|
|
368
|
+
if isinstance(val, int | float) and not np.isnan(val):
|
|
369
|
+
return float(val)
|
|
370
|
+
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
def _calculate_adjustments(
|
|
374
|
+
self,
|
|
375
|
+
insights: list[CrossDomainInsight],
|
|
376
|
+
) -> dict[str, float]:
|
|
377
|
+
"""Calculate confidence adjustments based on insights."""
|
|
378
|
+
adjustments: dict[str, float] = {}
|
|
379
|
+
|
|
380
|
+
for insight in insights:
|
|
381
|
+
for domain in insight.source_domains:
|
|
382
|
+
domain_key = domain.value
|
|
383
|
+
current = adjustments.get(domain_key, 0.0)
|
|
384
|
+
adjustments[domain_key] = current + insight.confidence_impact
|
|
385
|
+
|
|
386
|
+
# Clamp adjustments to [-0.3, +0.3]
|
|
387
|
+
return {k: max(-0.3, min(0.3, v)) for k, v in adjustments.items()}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def correlate_results(
|
|
391
|
+
results: dict[AnalysisDomain, dict[str, Any]],
|
|
392
|
+
tolerance: float = 0.1,
|
|
393
|
+
) -> CorrelationResult:
|
|
394
|
+
"""Convenience function to correlate domain results.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
results: Dictionary mapping domains to their results.
|
|
398
|
+
tolerance: Tolerance for value comparisons.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
CorrelationResult with insights.
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
>>> from oscura.reporting.config import AnalysisDomain
|
|
405
|
+
>>> results = {
|
|
406
|
+
... AnalysisDomain.SPECTRAL: {'dominant_frequency': 1000.0},
|
|
407
|
+
... AnalysisDomain.TIMING: {'period': 0.001}
|
|
408
|
+
... }
|
|
409
|
+
>>> correlation = correlate_results(results)
|
|
410
|
+
>>> print(f"Insights: {len(correlation.insights)}")
|
|
411
|
+
"""
|
|
412
|
+
correlator = CrossDomainCorrelator(tolerance)
|
|
413
|
+
return correlator.correlate(results)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
__all__ = [
|
|
417
|
+
"DOMAIN_AFFINITY",
|
|
418
|
+
"CorrelationResult",
|
|
419
|
+
"CrossDomainCorrelator",
|
|
420
|
+
"CrossDomainInsight",
|
|
421
|
+
"correlate_results",
|
|
422
|
+
]
|