oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Enhanced cancellation support for TraceKit operations.
|
|
2
|
+
|
|
3
|
+
This module provides advanced cancellation features including signal handling,
|
|
4
|
+
cleanup routines, and resume support for long-running operations.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.cancellation import CancellationManager
|
|
9
|
+
>>> manager = CancellationManager()
|
|
10
|
+
>>> with manager.cancellable_operation("Loading data"):
|
|
11
|
+
... # ... long operation ...
|
|
12
|
+
... manager.check_cancelled()
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
- Python threading best practices
|
|
16
|
+
- Signal handling patterns
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import atexit
|
|
22
|
+
import signal
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
from contextlib import contextmanager
|
|
26
|
+
from typing import TYPE_CHECKING, Any
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Callable, Generator
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CancellationManager:
|
|
33
|
+
"""Manager for cancellable operations with cleanup support.
|
|
34
|
+
|
|
35
|
+
: Ctrl+C handling, cleanup, and resume support.
|
|
36
|
+
Provides graceful cancellation with automatic cleanup and signal handling.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cleanup_callback: Optional callback for cleanup on cancellation
|
|
40
|
+
auto_cleanup: Automatically cleanup on exit (default: True)
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> from oscura.core.cancellation import CancellationManager
|
|
44
|
+
>>> def cleanup():
|
|
45
|
+
... print("Cleaning up...")
|
|
46
|
+
>>> manager = CancellationManager(cleanup_callback=cleanup)
|
|
47
|
+
>>> manager.register_signal_handlers()
|
|
48
|
+
>>> # Press Ctrl+C to trigger cancellation
|
|
49
|
+
|
|
50
|
+
References:
|
|
51
|
+
PROG-002: Cancellation Support
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
cleanup_callback: Callable[[], None] | None = None,
|
|
58
|
+
auto_cleanup: bool = True,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Initialize cancellation manager.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
cleanup_callback: Function to call on cancellation
|
|
64
|
+
auto_cleanup: Register cleanup at exit
|
|
65
|
+
"""
|
|
66
|
+
self._cancelled = threading.Event()
|
|
67
|
+
self._cleanup_callback = cleanup_callback
|
|
68
|
+
self._cleanup_functions: list[Callable[[], None]] = []
|
|
69
|
+
self._partial_results: dict[str, Any] = {}
|
|
70
|
+
self._operation_name = ""
|
|
71
|
+
self._start_time = 0.0
|
|
72
|
+
self._signal_handlers_registered = False
|
|
73
|
+
|
|
74
|
+
if auto_cleanup:
|
|
75
|
+
atexit.register(self._cleanup)
|
|
76
|
+
|
|
77
|
+
def register_signal_handlers(self) -> None:
|
|
78
|
+
"""Register signal handlers for Ctrl+C and SIGTERM.
|
|
79
|
+
|
|
80
|
+
: Ctrl+C handling - graceful cancellation.
|
|
81
|
+
Catches interrupt signals and triggers cancellation.
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
>>> manager.register_signal_handlers()
|
|
85
|
+
>>> # Now Ctrl+C will trigger cancellation
|
|
86
|
+
|
|
87
|
+
References:
|
|
88
|
+
PROG-002: Ctrl+C handling
|
|
89
|
+
"""
|
|
90
|
+
if self._signal_handlers_registered:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
def signal_handler(signum: int, frame: Any) -> None:
|
|
94
|
+
self.cancel(f"Received signal {signum}")
|
|
95
|
+
|
|
96
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
97
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
98
|
+
self._signal_handlers_registered = True
|
|
99
|
+
|
|
100
|
+
def cancel(self, reason: str = "Operation cancelled") -> None:
|
|
101
|
+
"""Request cancellation of the operation.
|
|
102
|
+
|
|
103
|
+
: cancel() method on operation handles.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
reason: Reason for cancellation
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> manager.cancel("User requested stop")
|
|
110
|
+
|
|
111
|
+
References:
|
|
112
|
+
PROG-002: cancel() method on operation handles
|
|
113
|
+
"""
|
|
114
|
+
self._cancelled.set()
|
|
115
|
+
self._operation_name = reason
|
|
116
|
+
|
|
117
|
+
def is_cancelled(self) -> bool:
|
|
118
|
+
"""Check if cancellation has been requested.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if operation should be cancelled
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> if manager.is_cancelled():
|
|
125
|
+
... return # Exit early
|
|
126
|
+
|
|
127
|
+
References:
|
|
128
|
+
PROG-002: Cancellation Support
|
|
129
|
+
"""
|
|
130
|
+
return self._cancelled.is_set()
|
|
131
|
+
|
|
132
|
+
def check_cancelled(self) -> None:
|
|
133
|
+
"""Check cancellation status and raise if cancelled.
|
|
134
|
+
|
|
135
|
+
: Graceful cancellation with partial results.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
CancelledException: If cancellation has been requested
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
>>> manager.check_cancelled() # Raises if cancelled
|
|
142
|
+
|
|
143
|
+
References:
|
|
144
|
+
PROG-002: Cancellation Support
|
|
145
|
+
"""
|
|
146
|
+
if self._cancelled.is_set():
|
|
147
|
+
self._cleanup()
|
|
148
|
+
elapsed = time.time() - self._start_time if self._start_time > 0 else 0
|
|
149
|
+
raise CancelledException(
|
|
150
|
+
self._operation_name,
|
|
151
|
+
partial_results=self._partial_results,
|
|
152
|
+
elapsed_time=elapsed,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def add_cleanup(self, cleanup_fn: Callable[[], None]) -> None:
|
|
156
|
+
"""Add a cleanup function to be called on cancellation.
|
|
157
|
+
|
|
158
|
+
: Cleanup on cancellation - no partial files.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
cleanup_fn: Function to call for cleanup
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
>>> def cleanup_temp_files():
|
|
165
|
+
... os.remove("temp.dat")
|
|
166
|
+
>>> manager.add_cleanup(cleanup_temp_files)
|
|
167
|
+
|
|
168
|
+
References:
|
|
169
|
+
PROG-002: Cleanup on cancellation
|
|
170
|
+
"""
|
|
171
|
+
self._cleanup_functions.append(cleanup_fn)
|
|
172
|
+
|
|
173
|
+
def store_partial_result(self, key: str, value: Any) -> None:
|
|
174
|
+
"""Store partial result for retrieval after cancellation.
|
|
175
|
+
|
|
176
|
+
: Partial results available after cancellation.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
key: Result identifier
|
|
180
|
+
value: Partial result value
|
|
181
|
+
|
|
182
|
+
Example:
|
|
183
|
+
>>> manager.store_partial_result("samples_processed", 1000)
|
|
184
|
+
|
|
185
|
+
References:
|
|
186
|
+
PROG-002: Partial results available after cancellation
|
|
187
|
+
"""
|
|
188
|
+
self._partial_results[key] = value
|
|
189
|
+
|
|
190
|
+
def get_partial_results(self) -> dict[str, Any]:
|
|
191
|
+
"""Get partial results collected before cancellation.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Dictionary of partial results
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> try:
|
|
198
|
+
... # ... operation ...
|
|
199
|
+
... except CancelledException as e:
|
|
200
|
+
... results = manager.get_partial_results()
|
|
201
|
+
|
|
202
|
+
References:
|
|
203
|
+
PROG-002: Partial results available after cancellation
|
|
204
|
+
"""
|
|
205
|
+
return self._partial_results.copy()
|
|
206
|
+
|
|
207
|
+
def _cleanup(self) -> None:
|
|
208
|
+
"""Execute all registered cleanup functions.
|
|
209
|
+
|
|
210
|
+
References:
|
|
211
|
+
PROG-002: Cleanup on cancellation
|
|
212
|
+
"""
|
|
213
|
+
# Call user-provided cleanup
|
|
214
|
+
if self._cleanup_callback is not None:
|
|
215
|
+
try:
|
|
216
|
+
self._cleanup_callback()
|
|
217
|
+
except Exception:
|
|
218
|
+
pass # Ignore cleanup errors
|
|
219
|
+
|
|
220
|
+
# Call registered cleanup functions
|
|
221
|
+
for cleanup_fn in self._cleanup_functions:
|
|
222
|
+
try:
|
|
223
|
+
cleanup_fn()
|
|
224
|
+
except Exception:
|
|
225
|
+
pass # Ignore cleanup errors
|
|
226
|
+
|
|
227
|
+
@contextmanager
|
|
228
|
+
def cancellable_operation(
|
|
229
|
+
self,
|
|
230
|
+
name: str = "Operation",
|
|
231
|
+
) -> Generator[CancellationManager, None, None]:
|
|
232
|
+
"""Context manager for cancellable operations.
|
|
233
|
+
|
|
234
|
+
: Graceful cancellation with cleanup.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
name: Operation name for logging
|
|
238
|
+
|
|
239
|
+
Yields:
|
|
240
|
+
CancellationManager instance
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
CancelledException: If operation is cancelled or interrupted.
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> with manager.cancellable_operation("Loading data") as ctx:
|
|
247
|
+
... for i in range(1000):
|
|
248
|
+
... ctx.check_cancelled()
|
|
249
|
+
... # ... process ...
|
|
250
|
+
|
|
251
|
+
References:
|
|
252
|
+
PROG-002: Cancellation Support
|
|
253
|
+
"""
|
|
254
|
+
self._operation_name = name
|
|
255
|
+
self._start_time = time.time()
|
|
256
|
+
try:
|
|
257
|
+
yield self
|
|
258
|
+
except CancelledException:
|
|
259
|
+
raise
|
|
260
|
+
except KeyboardInterrupt:
|
|
261
|
+
self.cancel("Interrupted by user (Ctrl+C)")
|
|
262
|
+
self._cleanup()
|
|
263
|
+
raise CancelledException( # noqa: B904
|
|
264
|
+
f"{name} interrupted by user",
|
|
265
|
+
partial_results=self._partial_results,
|
|
266
|
+
elapsed_time=time.time() - self._start_time,
|
|
267
|
+
)
|
|
268
|
+
finally:
|
|
269
|
+
if self._cancelled.is_set():
|
|
270
|
+
self._cleanup()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class CancelledException(Exception):
|
|
274
|
+
"""Exception raised when operation is cancelled.
|
|
275
|
+
|
|
276
|
+
: Partial results available after cancellation.
|
|
277
|
+
|
|
278
|
+
Attributes:
|
|
279
|
+
message: Cancellation message
|
|
280
|
+
partial_results: Results collected before cancellation
|
|
281
|
+
elapsed_time: Time elapsed before cancellation
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
>>> try:
|
|
285
|
+
... manager.check_cancelled()
|
|
286
|
+
... except CancelledException as e:
|
|
287
|
+
... print(f"Cancelled after {e.elapsed_time:.1f}s")
|
|
288
|
+
... print(f"Partial results: {e.partial_results}")
|
|
289
|
+
|
|
290
|
+
References:
|
|
291
|
+
PROG-002: Partial results available after cancellation
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
def __init__(
|
|
295
|
+
self,
|
|
296
|
+
message: str,
|
|
297
|
+
*,
|
|
298
|
+
partial_results: dict[str, Any] | None = None,
|
|
299
|
+
elapsed_time: float = 0.0,
|
|
300
|
+
) -> None:
|
|
301
|
+
"""Initialize CancelledException.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
message: Cancellation message
|
|
305
|
+
partial_results: Partial results dictionary
|
|
306
|
+
elapsed_time: Elapsed time in seconds
|
|
307
|
+
"""
|
|
308
|
+
self.message = message
|
|
309
|
+
self.partial_results = partial_results or {}
|
|
310
|
+
self.elapsed_time = elapsed_time
|
|
311
|
+
super().__init__(
|
|
312
|
+
f"{message} (elapsed: {elapsed_time:.1f}s, "
|
|
313
|
+
f"partial results: {len(self.partial_results)} items)"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class ResumableOperation:
|
|
318
|
+
"""Support for resumable operations after cancellation.
|
|
319
|
+
|
|
320
|
+
: Resume support where possible.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
checkpoint_callback: Function to save checkpoint state
|
|
324
|
+
restore_callback: Function to restore from checkpoint
|
|
325
|
+
|
|
326
|
+
Example:
|
|
327
|
+
>>> def save_state(state):
|
|
328
|
+
... with open("checkpoint.json", "w") as f:
|
|
329
|
+
... json.dump(state, f)
|
|
330
|
+
>>> def load_state():
|
|
331
|
+
... with open("checkpoint.json") as f:
|
|
332
|
+
... return json.load(f)
|
|
333
|
+
>>> op = ResumableOperation(save_state, load_state)
|
|
334
|
+
|
|
335
|
+
References:
|
|
336
|
+
PROG-002: Resume support where possible
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(
|
|
340
|
+
self,
|
|
341
|
+
checkpoint_callback: Callable[[dict], None], # type: ignore[type-arg]
|
|
342
|
+
restore_callback: Callable[[], dict], # type: ignore[type-arg]
|
|
343
|
+
) -> None:
|
|
344
|
+
"""Initialize resumable operation.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
checkpoint_callback: Function to save state
|
|
348
|
+
restore_callback: Function to restore state
|
|
349
|
+
"""
|
|
350
|
+
self._checkpoint_callback = checkpoint_callback
|
|
351
|
+
self._restore_callback = restore_callback
|
|
352
|
+
self._state: dict[str, Any] = {}
|
|
353
|
+
|
|
354
|
+
def checkpoint(self, state: dict[str, Any]) -> None:
|
|
355
|
+
"""Save operation state for resume.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
state: Current operation state
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
>>> op.checkpoint({"processed": 500, "total": 1000})
|
|
362
|
+
|
|
363
|
+
References:
|
|
364
|
+
PROG-002: Resume support
|
|
365
|
+
"""
|
|
366
|
+
self._state = state
|
|
367
|
+
self._checkpoint_callback(state)
|
|
368
|
+
|
|
369
|
+
def restore(self) -> dict[str, Any]:
|
|
370
|
+
"""Restore operation state from checkpoint.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Restored state dictionary
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
>>> state = op.restore()
|
|
377
|
+
>>> start_index = state.get("processed", 0)
|
|
378
|
+
|
|
379
|
+
References:
|
|
380
|
+
PROG-002: Resume support
|
|
381
|
+
"""
|
|
382
|
+
self._state = self._restore_callback()
|
|
383
|
+
return self._state
|
|
384
|
+
|
|
385
|
+
def has_checkpoint(self) -> bool:
|
|
386
|
+
"""Check if checkpoint exists.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
True if checkpoint is available
|
|
390
|
+
|
|
391
|
+
References:
|
|
392
|
+
PROG-002: Resume support
|
|
393
|
+
"""
|
|
394
|
+
try:
|
|
395
|
+
self._restore_callback()
|
|
396
|
+
return True
|
|
397
|
+
except Exception:
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def confirm_cancellation(
|
|
402
|
+
operation_name: str = "operation",
|
|
403
|
+
*,
|
|
404
|
+
destructive: bool = False,
|
|
405
|
+
) -> bool:
|
|
406
|
+
"""Confirm cancellation for destructive operations.
|
|
407
|
+
|
|
408
|
+
: Cancel confirmation for destructive operations.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
operation_name: Name of operation to cancel
|
|
412
|
+
destructive: Whether operation is destructive
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
True if user confirms cancellation
|
|
416
|
+
|
|
417
|
+
Example:
|
|
418
|
+
>>> if confirm_cancellation("Delete files", destructive=True):
|
|
419
|
+
... # Proceed with cancellation
|
|
420
|
+
|
|
421
|
+
References:
|
|
422
|
+
PROG-002: Cancel confirmation for destructive operations
|
|
423
|
+
"""
|
|
424
|
+
if not destructive:
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
response = input(f"Cancel {operation_name}? This may lose data. [y/N]: ").strip().lower()
|
|
429
|
+
return response in ("y", "yes")
|
|
430
|
+
except (EOFError, KeyboardInterrupt):
|
|
431
|
+
return True # Assume yes on Ctrl+C during prompt
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
__all__ = [
|
|
435
|
+
"CancellationManager",
|
|
436
|
+
"CancelledException",
|
|
437
|
+
"ResumableOperation",
|
|
438
|
+
"confirm_cancellation",
|
|
439
|
+
]
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Universal confidence scoring for auto-detection.
|
|
2
|
+
|
|
3
|
+
This module provides confidence scoring infrastructure used across
|
|
4
|
+
all auto-discovery and analysis functions.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.confidence import ConfidenceScore
|
|
9
|
+
>>> score = ConfidenceScore(value=0.85, factors={'signal_quality': 0.9, 'pattern_match': 0.8})
|
|
10
|
+
>>> print(f"Confidence: {score.value:.2f} ({score.interpretation})")
|
|
11
|
+
Confidence: 0.85 (likely)
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
TraceKit Auto-Discovery Specification
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ConfidenceScore:
|
|
25
|
+
"""Confidence score for auto-detection results.
|
|
26
|
+
|
|
27
|
+
Represents reliability of automated analysis results with
|
|
28
|
+
standardized 0.0-1.0 scale and human-readable interpretation.
|
|
29
|
+
|
|
30
|
+
Confidence scale:
|
|
31
|
+
- 0.9-1.0: High - "almost certain", trust the result
|
|
32
|
+
- 0.7-0.9: Medium - "likely", verify if critical
|
|
33
|
+
- 0.5-0.7: Low - "possible", check alternatives
|
|
34
|
+
- 0.0-0.5: Unreliable - "uncertain", manual analysis recommended
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
value: Confidence value (0.0-1.0, 2 decimal precision).
|
|
38
|
+
factors: Dictionary of contributing factors and their scores.
|
|
39
|
+
explanation: Optional explanation of confidence calculation.
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> score = ConfidenceScore(0.92, factors={'snr': 0.95, 'timing': 0.89})
|
|
43
|
+
>>> print(f"{score.value:.2f} - {score.interpretation}")
|
|
44
|
+
0.92 - almost certain
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
value: float
|
|
48
|
+
factors: dict[str, float] = field(default_factory=dict)
|
|
49
|
+
explanation: str | None = None
|
|
50
|
+
|
|
51
|
+
def __post_init__(self) -> None:
|
|
52
|
+
"""Validate confidence score after initialization."""
|
|
53
|
+
if not 0.0 <= self.value <= 1.0:
|
|
54
|
+
raise ValueError(f"Confidence value must be in [0.0, 1.0], got {self.value}")
|
|
55
|
+
# Round to 2 decimal places
|
|
56
|
+
self.value = round(self.value, 2)
|
|
57
|
+
|
|
58
|
+
# Validate factors
|
|
59
|
+
for name, factor_value in self.factors.items():
|
|
60
|
+
if not 0.0 <= factor_value <= 1.0:
|
|
61
|
+
raise ValueError(f"Factor '{name}' must be in [0.0, 1.0], got {factor_value}")
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def level(self) -> str:
|
|
65
|
+
"""Confidence level classification.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: One of "high", "medium", "low", "unreliable".
|
|
69
|
+
"""
|
|
70
|
+
if self.value >= 0.9:
|
|
71
|
+
return "high"
|
|
72
|
+
elif self.value >= 0.7:
|
|
73
|
+
return "medium"
|
|
74
|
+
elif self.value >= 0.5:
|
|
75
|
+
return "low"
|
|
76
|
+
else:
|
|
77
|
+
return "unreliable"
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def interpretation(self) -> str:
|
|
81
|
+
"""Human-readable interpretation.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Descriptive interpretation string.
|
|
85
|
+
"""
|
|
86
|
+
if self.value >= 0.95:
|
|
87
|
+
return "almost certain"
|
|
88
|
+
elif self.value >= 0.85:
|
|
89
|
+
return "likely"
|
|
90
|
+
elif self.value >= 0.75:
|
|
91
|
+
return "possible"
|
|
92
|
+
elif self.value >= 0.55:
|
|
93
|
+
return "uncertain"
|
|
94
|
+
else:
|
|
95
|
+
return "unlikely"
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def combine(
|
|
99
|
+
scores: list[float],
|
|
100
|
+
weights: list[float] | None = None,
|
|
101
|
+
) -> float:
|
|
102
|
+
"""Combine multiple confidence scores into one.
|
|
103
|
+
|
|
104
|
+
Uses weighted average to combine scores. Equal weights if not specified.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
scores: List of confidence values (0.0-1.0).
|
|
108
|
+
weights: Optional weight for each score (must sum to 1.0).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Combined confidence score (0.0-1.0).
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ValueError: If scores/weights are invalid or don't match.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
>>> scores = [0.9, 0.8, 0.7]
|
|
118
|
+
>>> combined = ConfidenceScore.combine(scores, weights=[0.5, 0.3, 0.2])
|
|
119
|
+
>>> print(f"{combined:.2f}")
|
|
120
|
+
0.83
|
|
121
|
+
"""
|
|
122
|
+
if not scores:
|
|
123
|
+
raise ValueError("Cannot combine empty score list")
|
|
124
|
+
|
|
125
|
+
for score in scores:
|
|
126
|
+
if not 0.0 <= score <= 1.0:
|
|
127
|
+
raise ValueError(f"Score must be in [0.0, 1.0], got {score}")
|
|
128
|
+
|
|
129
|
+
if weights is None:
|
|
130
|
+
# Equal weights
|
|
131
|
+
weights = [1.0 / len(scores)] * len(scores)
|
|
132
|
+
|
|
133
|
+
if len(scores) != len(weights):
|
|
134
|
+
raise ValueError(f"Scores ({len(scores)}) and weights ({len(weights)}) length mismatch")
|
|
135
|
+
|
|
136
|
+
# Normalize weights to sum to 1.0
|
|
137
|
+
weight_sum = sum(weights)
|
|
138
|
+
if weight_sum == 0:
|
|
139
|
+
raise ValueError("Weights must sum to non-zero value")
|
|
140
|
+
|
|
141
|
+
normalized_weights = [w / weight_sum for w in weights]
|
|
142
|
+
|
|
143
|
+
# Weighted average
|
|
144
|
+
combined = sum(s * w for s, w in zip(scores, normalized_weights, strict=False))
|
|
145
|
+
return round(combined, 2)
|
|
146
|
+
|
|
147
|
+
def to_dict(self) -> dict[str, Any]:
|
|
148
|
+
"""Convert to dictionary representation.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Dictionary with confidence details.
|
|
152
|
+
"""
|
|
153
|
+
return {
|
|
154
|
+
"value": self.value,
|
|
155
|
+
"level": self.level,
|
|
156
|
+
"interpretation": self.interpretation,
|
|
157
|
+
"factors": self.factors,
|
|
158
|
+
"explanation": self.explanation,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def __repr__(self) -> str:
|
|
162
|
+
"""String representation."""
|
|
163
|
+
return f"ConfidenceScore({self.value:.2f}, level='{self.level}')"
|
|
164
|
+
|
|
165
|
+
def __float__(self) -> float:
|
|
166
|
+
"""Convert to float (returns value)."""
|
|
167
|
+
return self.value
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def calculate_confidence(
|
|
171
|
+
factors: dict[str, float],
|
|
172
|
+
weights: dict[str, float] | None = None,
|
|
173
|
+
*,
|
|
174
|
+
explanation: str | None = None,
|
|
175
|
+
) -> ConfidenceScore:
|
|
176
|
+
"""Calculate confidence score from multiple factors.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
factors: Dictionary of factor names to values (0.0-1.0).
|
|
180
|
+
weights: Optional weights for each factor (must sum to 1.0).
|
|
181
|
+
explanation: Optional explanation of calculation.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
ConfidenceScore object with combined value.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ValueError: If factors is empty or missing weight for a factor.
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> factors = {'signal_quality': 0.9, 'pattern_match': 0.85, 'timing': 0.8}
|
|
191
|
+
>>> weights = {'signal_quality': 0.4, 'pattern_match': 0.4, 'timing': 0.2}
|
|
192
|
+
>>> score = calculate_confidence(factors, weights)
|
|
193
|
+
>>> print(f"Confidence: {score.value:.2f}")
|
|
194
|
+
Confidence: 0.86
|
|
195
|
+
"""
|
|
196
|
+
if not factors:
|
|
197
|
+
raise ValueError("Cannot calculate confidence from empty factors")
|
|
198
|
+
|
|
199
|
+
if weights is None:
|
|
200
|
+
# Equal weights
|
|
201
|
+
score_values = list(factors.values())
|
|
202
|
+
weight_values = None
|
|
203
|
+
else:
|
|
204
|
+
# Use provided weights
|
|
205
|
+
score_values = []
|
|
206
|
+
weight_values = []
|
|
207
|
+
for name, value in factors.items():
|
|
208
|
+
score_values.append(value)
|
|
209
|
+
if name not in weights:
|
|
210
|
+
raise ValueError(f"Missing weight for factor '{name}'")
|
|
211
|
+
weight_values.append(weights[name])
|
|
212
|
+
|
|
213
|
+
combined_value = ConfidenceScore.combine(score_values, weight_values)
|
|
214
|
+
|
|
215
|
+
return ConfidenceScore(
|
|
216
|
+
value=combined_value,
|
|
217
|
+
factors=factors,
|
|
218
|
+
explanation=explanation,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__all__ = [
|
|
223
|
+
"ConfidenceScore",
|
|
224
|
+
"calculate_confidence",
|
|
225
|
+
]
|