oscura 0.5.1__py3-none-any.whl → 0.7.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 +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +183 -67
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +1 -1
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -291
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -316,25 +316,72 @@ class ArgumentPreparer:
|
|
|
316
316
|
kwargs: dict[str, Any] = {}
|
|
317
317
|
data_length = len(raw_data) if hasattr(raw_data, "__len__") else 0
|
|
318
318
|
|
|
319
|
+
# Add window_size if needed
|
|
320
|
+
kwargs.update(self._add_window_size_param(params, sig, data_length, kwargs))
|
|
321
|
+
|
|
322
|
+
# Add min_width if needed
|
|
323
|
+
kwargs.update(self._add_min_width_param(params, sig, sample_rate, kwargs))
|
|
324
|
+
|
|
325
|
+
# Add max_width if needed
|
|
326
|
+
kwargs.update(self._add_max_width_param(params, sig, data_length, sample_rate, kwargs))
|
|
327
|
+
|
|
328
|
+
# Add threshold if needed
|
|
329
|
+
kwargs.update(self._add_threshold_param(params, sig, raw_data, kwargs))
|
|
330
|
+
|
|
331
|
+
# Add window_duration if needed
|
|
332
|
+
kwargs.update(
|
|
333
|
+
self._add_window_duration_param(params, sig, data_length, sample_rate, kwargs)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return kwargs
|
|
337
|
+
|
|
338
|
+
def _add_window_size_param(
|
|
339
|
+
self, params: list[str], sig: inspect.Signature, data_length: int, kwargs: dict[str, Any]
|
|
340
|
+
) -> dict[str, Any]:
|
|
341
|
+
"""Add window_size parameter."""
|
|
342
|
+
result = {}
|
|
319
343
|
if "window_size" in params:
|
|
320
344
|
if self._param_needs_value(sig, "window_size") and "window_size" not in kwargs:
|
|
321
345
|
window_size: Any = max(10, data_length // 10)
|
|
322
|
-
|
|
323
|
-
logger.debug(f"Using auto-detected window_size: {
|
|
346
|
+
result["window_size"] = window_size
|
|
347
|
+
logger.debug(f"Using auto-detected window_size: {window_size}")
|
|
348
|
+
return result
|
|
324
349
|
|
|
350
|
+
def _add_min_width_param(
|
|
351
|
+
self, params: list[str], sig: inspect.Signature, sample_rate: float, kwargs: dict[str, Any]
|
|
352
|
+
) -> dict[str, Any]:
|
|
353
|
+
"""Add min_width parameter."""
|
|
354
|
+
result = {}
|
|
325
355
|
if "min_width" in params:
|
|
326
356
|
if self._param_needs_value(sig, "min_width") and "min_width" not in kwargs:
|
|
327
357
|
min_width: Any = max(1e-9, 10.0 / sample_rate)
|
|
328
|
-
|
|
329
|
-
logger.debug(f"Using auto-detected min_width: {
|
|
358
|
+
result["min_width"] = min_width
|
|
359
|
+
logger.debug(f"Using auto-detected min_width: {min_width:.2e}s")
|
|
360
|
+
return result
|
|
330
361
|
|
|
362
|
+
def _add_max_width_param(
|
|
363
|
+
self,
|
|
364
|
+
params: list[str],
|
|
365
|
+
sig: inspect.Signature,
|
|
366
|
+
data_length: int,
|
|
367
|
+
sample_rate: float,
|
|
368
|
+
kwargs: dict[str, Any],
|
|
369
|
+
) -> dict[str, Any]:
|
|
370
|
+
"""Add max_width parameter."""
|
|
371
|
+
result = {}
|
|
331
372
|
if "max_width" in params:
|
|
332
373
|
if self._param_needs_value(sig, "max_width") and "max_width" not in kwargs:
|
|
333
374
|
total_duration = data_length / sample_rate if data_length > 0 else 1e-3
|
|
334
375
|
max_width: Any = min(1e-3, total_duration)
|
|
335
|
-
|
|
336
|
-
logger.debug(f"Using auto-detected max_width: {
|
|
376
|
+
result["max_width"] = max_width
|
|
377
|
+
logger.debug(f"Using auto-detected max_width: {max_width:.2e}s")
|
|
378
|
+
return result
|
|
337
379
|
|
|
380
|
+
def _add_threshold_param(
|
|
381
|
+
self, params: list[str], sig: inspect.Signature, raw_data: Any, kwargs: dict[str, Any]
|
|
382
|
+
) -> dict[str, Any]:
|
|
383
|
+
"""Add threshold parameter."""
|
|
384
|
+
result = {}
|
|
338
385
|
if "threshold" in params and "threshold" not in kwargs:
|
|
339
386
|
param_info = sig.parameters.get("threshold")
|
|
340
387
|
has_default = (
|
|
@@ -344,21 +391,29 @@ class ArgumentPreparer:
|
|
|
344
391
|
try:
|
|
345
392
|
if isinstance(raw_data, np.ndarray) and raw_data.size > 0:
|
|
346
393
|
threshold: Any = float(np.median(raw_data))
|
|
347
|
-
|
|
348
|
-
logger.debug(f"Using auto-detected threshold: {
|
|
394
|
+
result["threshold"] = threshold
|
|
395
|
+
logger.debug(f"Using auto-detected threshold: {threshold:.3f}")
|
|
349
396
|
except Exception as e:
|
|
350
397
|
logger.debug(f"Could not auto-detect threshold: {e}")
|
|
398
|
+
return result
|
|
351
399
|
|
|
400
|
+
def _add_window_duration_param(
|
|
401
|
+
self,
|
|
402
|
+
params: list[str],
|
|
403
|
+
sig: inspect.Signature,
|
|
404
|
+
data_length: int,
|
|
405
|
+
sample_rate: float,
|
|
406
|
+
kwargs: dict[str, Any],
|
|
407
|
+
) -> dict[str, Any]:
|
|
408
|
+
"""Add window_duration parameter."""
|
|
409
|
+
result = {}
|
|
352
410
|
if "window_duration" in params:
|
|
353
411
|
if self._param_needs_value(sig, "window_duration") and "window_duration" not in kwargs:
|
|
354
412
|
total_duration = data_length / sample_rate if data_length > 0 else 1.0
|
|
355
413
|
window_duration: Any = min(1.0, total_duration / 10.0)
|
|
356
|
-
|
|
357
|
-
logger.debug(
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
return kwargs
|
|
414
|
+
result["window_duration"] = window_duration
|
|
415
|
+
logger.debug(f"Using auto-detected window_duration: {window_duration:.3f}s")
|
|
416
|
+
return result
|
|
362
417
|
|
|
363
418
|
def _param_needs_value(self, sig: inspect.Signature, param_name: str) -> bool:
|
|
364
419
|
"""Check if parameter needs a value (no default or default is None)."""
|
oscura/reporting/auto_report.py
CHANGED
|
@@ -102,7 +102,9 @@ class Report:
|
|
|
102
102
|
"""
|
|
103
103
|
self.output_path = path
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
# Use list + join for O(n) string building instead of O(n²) +=
|
|
106
|
+
html_parts = [
|
|
107
|
+
f"""<!DOCTYPE html>
|
|
106
108
|
<html>
|
|
107
109
|
<head>
|
|
108
110
|
<meta charset="UTF-8">
|
|
@@ -155,26 +157,33 @@ class Report:
|
|
|
155
157
|
<p><strong>Date:</strong> {self.metadata.date}</p>
|
|
156
158
|
<p><strong>Author:</strong> {self.metadata.author}</p>
|
|
157
159
|
"""
|
|
160
|
+
]
|
|
161
|
+
|
|
158
162
|
if self.metadata.project:
|
|
159
|
-
|
|
163
|
+
html_parts.append(f" <p><strong>Project:</strong> {self.metadata.project}</p>\n")
|
|
160
164
|
|
|
161
165
|
if self.metadata.tags:
|
|
162
|
-
|
|
166
|
+
html_parts.append(
|
|
163
167
|
f" <p><strong>Tags:</strong> {', '.join(self.metadata.tags)}</p>\n"
|
|
164
168
|
)
|
|
165
169
|
|
|
166
|
-
|
|
170
|
+
html_parts.append(" </div>\n\n")
|
|
167
171
|
|
|
172
|
+
# Build sections with list.append instead of += in loop
|
|
168
173
|
for section in self.sections:
|
|
169
174
|
if section in self.content:
|
|
170
175
|
section_title = section.replace("_", " ").title()
|
|
171
|
-
|
|
176
|
+
html_parts.append(
|
|
177
|
+
f""" <div class="section">
|
|
172
178
|
<h2>{section_title}</h2>
|
|
173
179
|
<p>{self.content[section]}</p>
|
|
174
180
|
</div>
|
|
175
181
|
"""
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
html_parts.append("</body>\n</html>")
|
|
185
|
+
html_content = "".join(html_parts)
|
|
186
|
+
|
|
178
187
|
with open(path, "w") as f:
|
|
179
188
|
f.write(html_content)
|
|
180
189
|
|
|
@@ -188,23 +197,30 @@ class Report:
|
|
|
188
197
|
"""
|
|
189
198
|
self.output_path = path
|
|
190
199
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
# Use list + join for O(n) string building instead of O(n²) +=
|
|
201
|
+
md_parts = [
|
|
202
|
+
f"# {self.metadata.title}\n\n",
|
|
203
|
+
f"**Date:** {self.metadata.date} \n",
|
|
204
|
+
f"**Author:** {self.metadata.author} \n",
|
|
205
|
+
]
|
|
194
206
|
|
|
195
207
|
if self.metadata.project:
|
|
196
|
-
|
|
208
|
+
md_parts.append(f"**Project:** {self.metadata.project} \n")
|
|
197
209
|
|
|
198
210
|
if self.metadata.tags:
|
|
199
|
-
|
|
211
|
+
md_parts.append(f"**Tags:** {', '.join(self.metadata.tags)} \n")
|
|
200
212
|
|
|
201
|
-
|
|
213
|
+
md_parts.append("\n---\n\n")
|
|
202
214
|
|
|
215
|
+
# Build sections with list.append instead of += in loop
|
|
203
216
|
for section in self.sections:
|
|
204
217
|
if section in self.content:
|
|
205
218
|
section_title = section.replace("_", " ").title()
|
|
206
|
-
|
|
207
|
-
|
|
219
|
+
md_parts.append(f"## {section_title}\n\n")
|
|
220
|
+
md_parts.append(self.content[section])
|
|
221
|
+
md_parts.append("\n\n")
|
|
222
|
+
|
|
223
|
+
md_content = "".join(md_parts)
|
|
208
224
|
|
|
209
225
|
with open(path, "w") as f:
|
|
210
226
|
f.write(md_content)
|
|
@@ -397,6 +413,67 @@ def _generate_detailed_results(trace: WaveformTrace, context: dict[str, Any]) ->
|
|
|
397
413
|
return results
|
|
398
414
|
|
|
399
415
|
|
|
416
|
+
def _generate_section_content(
|
|
417
|
+
sections: list[str], trace: WaveformTrace, context: dict[str, Any]
|
|
418
|
+
) -> dict[str, str]:
|
|
419
|
+
"""Generate content for requested sections.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
sections: List of section names to generate
|
|
423
|
+
trace: Waveform trace
|
|
424
|
+
context: Analysis context
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Dictionary mapping section names to content
|
|
428
|
+
"""
|
|
429
|
+
content = {}
|
|
430
|
+
|
|
431
|
+
if "executive_summary" in sections or "summary" in sections:
|
|
432
|
+
content["executive_summary"] = _generate_executive_summary(trace, context)
|
|
433
|
+
|
|
434
|
+
if "key_findings" in sections or "findings" in sections:
|
|
435
|
+
content["key_findings"] = _generate_key_findings(trace, context)
|
|
436
|
+
|
|
437
|
+
if "methodology" in sections:
|
|
438
|
+
content["methodology"] = _generate_methodology(trace, context)
|
|
439
|
+
|
|
440
|
+
if "detailed_results" in sections or "results" in sections:
|
|
441
|
+
content["detailed_results"] = _generate_detailed_results(trace, context)
|
|
442
|
+
|
|
443
|
+
if "recommendations" in sections:
|
|
444
|
+
content["recommendations"] = (
|
|
445
|
+
"Recommendations based on analysis:\n\n"
|
|
446
|
+
"1. Signal quality is acceptable for analysis\n"
|
|
447
|
+
"2. Consider additional captures for verification\n"
|
|
448
|
+
"3. Review anomalies if present\n"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
return content
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _determine_plot_types(trace: WaveformTrace, options: dict[str, Any]) -> list[str]:
|
|
455
|
+
"""Determine which plots to include in report.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
trace: Waveform trace
|
|
459
|
+
options: Report options
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
List of plot type names
|
|
463
|
+
"""
|
|
464
|
+
configured_plot_types = options.get("plot_types", [])
|
|
465
|
+
if configured_plot_types:
|
|
466
|
+
# Cast to list[str] - we know it contains strings from config
|
|
467
|
+
return [str(p) for p in configured_plot_types]
|
|
468
|
+
|
|
469
|
+
# Auto-select based on signal characteristics
|
|
470
|
+
plot_types = ["time_domain_waveform"]
|
|
471
|
+
if len(trace.data) > 100:
|
|
472
|
+
plot_types.append("fft_spectrum")
|
|
473
|
+
|
|
474
|
+
return plot_types
|
|
475
|
+
|
|
476
|
+
|
|
400
477
|
def generate_report(
|
|
401
478
|
trace: WaveformTrace,
|
|
402
479
|
*,
|
|
@@ -437,55 +514,15 @@ def generate_report(
|
|
|
437
514
|
context = context or {}
|
|
438
515
|
options = options or {}
|
|
439
516
|
|
|
440
|
-
|
|
441
|
-
default_sections = [
|
|
442
|
-
"executive_summary",
|
|
443
|
-
"key_findings",
|
|
444
|
-
"methodology",
|
|
445
|
-
"detailed_results",
|
|
446
|
-
]
|
|
447
|
-
|
|
517
|
+
default_sections = ["executive_summary", "key_findings", "methodology", "detailed_results"]
|
|
448
518
|
sections = options.get("select_sections", default_sections)
|
|
449
519
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if "executive_summary" in sections or "summary" in sections:
|
|
454
|
-
content["executive_summary"] = _generate_executive_summary(trace, context)
|
|
455
|
-
|
|
456
|
-
if "key_findings" in sections or "findings" in sections:
|
|
457
|
-
content["key_findings"] = _generate_key_findings(trace, context)
|
|
458
|
-
|
|
459
|
-
if "methodology" in sections:
|
|
460
|
-
content["methodology"] = _generate_methodology(trace, context)
|
|
461
|
-
|
|
462
|
-
if "detailed_results" in sections or "results" in sections:
|
|
463
|
-
content["detailed_results"] = _generate_detailed_results(trace, context)
|
|
464
|
-
|
|
465
|
-
if "recommendations" in sections:
|
|
466
|
-
content["recommendations"] = (
|
|
467
|
-
"Recommendations based on analysis:\n\n"
|
|
468
|
-
"1. Signal quality is acceptable for analysis\n"
|
|
469
|
-
"2. Consider additional captures for verification\n"
|
|
470
|
-
"3. Review anomalies if present\n"
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
# Determine plot types to include
|
|
474
|
-
plot_types = options.get("plot_types", [])
|
|
475
|
-
if not plot_types:
|
|
476
|
-
# Auto-select based on signal characteristics
|
|
477
|
-
plot_types = ["time_domain_waveform"]
|
|
478
|
-
|
|
479
|
-
# Add spectral if signal looks periodic
|
|
480
|
-
if len(trace.data) > 100:
|
|
481
|
-
plot_types.append("fft_spectrum")
|
|
520
|
+
content = _generate_section_content(sections, trace, context)
|
|
521
|
+
plot_types = _determine_plot_types(trace, options)
|
|
482
522
|
|
|
483
|
-
# Estimate page count
|
|
484
|
-
page_count = 1
|
|
485
|
-
page_count += len(sections) # One page per section
|
|
486
|
-
page_count += (len(plot_types) + 1) // 2 # 2 plots per page
|
|
523
|
+
# Estimate page count
|
|
524
|
+
page_count = 1 + len(sections) + (len(plot_types) + 1) // 2
|
|
487
525
|
|
|
488
|
-
# Create report object
|
|
489
526
|
report = Report(
|
|
490
527
|
sections=list(sections),
|
|
491
528
|
plots=plot_types,
|
|
@@ -493,7 +530,6 @@ def generate_report(
|
|
|
493
530
|
content=content,
|
|
494
531
|
)
|
|
495
532
|
|
|
496
|
-
# Set custom metadata if provided
|
|
497
533
|
if "custom_header" in options:
|
|
498
534
|
report.metadata.title = options["custom_header"]
|
|
499
535
|
|
oscura/reporting/batch.py
CHANGED
|
@@ -114,8 +114,6 @@ def batch_report(
|
|
|
114
114
|
References:
|
|
115
115
|
RPT-003: Batch Report Generation
|
|
116
116
|
"""
|
|
117
|
-
import oscura as osc
|
|
118
|
-
from oscura.reporting.template_system import load_template
|
|
119
117
|
|
|
120
118
|
output_path = Path(output_dir)
|
|
121
119
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -124,81 +122,156 @@ def batch_report(
|
|
|
124
122
|
batch_results: list[dict[str, Any]] = []
|
|
125
123
|
|
|
126
124
|
# Load template
|
|
125
|
+
report_template = _load_report_template(template)
|
|
126
|
+
|
|
127
|
+
# Get DUT ID extractor
|
|
128
|
+
extractor = dut_id_extractor or _default_dut_id_extractor
|
|
129
|
+
|
|
130
|
+
# Process each file
|
|
131
|
+
batch_results = _process_files(
|
|
132
|
+
files,
|
|
133
|
+
extractor,
|
|
134
|
+
analyzer,
|
|
135
|
+
result,
|
|
136
|
+
output_path,
|
|
137
|
+
report_template,
|
|
138
|
+
output_format,
|
|
139
|
+
file_pattern,
|
|
140
|
+
generate_individual,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Generate summary report if requested
|
|
144
|
+
if generate_summary and batch_results:
|
|
145
|
+
_create_summary_report(batch_results, output_path, summary_filename, output_format, result)
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _load_report_template(template: str) -> Any:
|
|
151
|
+
"""Load report template with fallback to default."""
|
|
152
|
+
from oscura.reporting.template_system import load_template
|
|
153
|
+
|
|
127
154
|
try:
|
|
128
|
-
|
|
155
|
+
return load_template(template)
|
|
129
156
|
except ValueError:
|
|
130
157
|
logger.warning(f"Template '{template}' not found, using 'default'")
|
|
131
|
-
|
|
158
|
+
return load_template("default")
|
|
132
159
|
|
|
133
|
-
# Default DUT ID extractor
|
|
134
|
-
if dut_id_extractor is None:
|
|
135
160
|
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
def _default_dut_id_extractor(path: Path) -> str:
|
|
162
|
+
"""Extract DUT ID from file path (default: use stem)."""
|
|
163
|
+
return Path(path).stem
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _process_files(
|
|
167
|
+
files: list[str | Path],
|
|
168
|
+
dut_id_extractor: Callable[[Path], str],
|
|
169
|
+
analyzer: Callable[[Any], dict[str, Any]] | None,
|
|
170
|
+
result: BatchReportResult,
|
|
171
|
+
output_path: Path,
|
|
172
|
+
report_template: Any,
|
|
173
|
+
output_format: str,
|
|
174
|
+
file_pattern: str,
|
|
175
|
+
generate_individual: bool,
|
|
176
|
+
) -> list[dict[str, Any]]:
|
|
177
|
+
"""Process all files and generate individual reports."""
|
|
178
|
+
|
|
179
|
+
batch_results: list[dict[str, Any]] = []
|
|
138
180
|
|
|
139
|
-
# Process each file
|
|
140
181
|
for file_path in files:
|
|
141
182
|
file_path = Path(file_path)
|
|
142
183
|
dut_id = dut_id_extractor(file_path)
|
|
143
184
|
|
|
144
|
-
|
|
145
|
-
# Load trace
|
|
146
|
-
trace = osc.load(str(file_path))
|
|
185
|
+
dut_result = _process_single_file(file_path, dut_id, analyzer, result)
|
|
147
186
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
dut_result = analyzer(trace)
|
|
151
|
-
else:
|
|
152
|
-
# Default analysis
|
|
153
|
-
dut_result = _default_analysis(trace)
|
|
187
|
+
if dut_result is not None:
|
|
188
|
+
batch_results.append(dut_result)
|
|
154
189
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
190
|
+
if generate_individual:
|
|
191
|
+
_save_individual_report(
|
|
192
|
+
dut_result,
|
|
193
|
+
dut_id,
|
|
194
|
+
output_path,
|
|
195
|
+
file_pattern,
|
|
196
|
+
output_format,
|
|
197
|
+
report_template,
|
|
198
|
+
result,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return batch_results
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _process_single_file(
|
|
205
|
+
file_path: Path,
|
|
206
|
+
dut_id: str,
|
|
207
|
+
analyzer: Callable[[Any], dict[str, Any]] | None,
|
|
208
|
+
result: BatchReportResult,
|
|
209
|
+
) -> dict[str, Any] | None:
|
|
210
|
+
"""Process a single DUT file and update result statistics."""
|
|
211
|
+
import oscura as osc
|
|
158
212
|
|
|
159
|
-
|
|
213
|
+
try:
|
|
214
|
+
trace = osc.load(str(file_path))
|
|
215
|
+
dut_result = analyzer(trace) if analyzer else _default_analysis(trace)
|
|
160
216
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
result.passed_duts += 1
|
|
164
|
-
else:
|
|
165
|
-
result.failed_duts += 1
|
|
217
|
+
dut_result["dut_id"] = dut_id
|
|
218
|
+
dut_result["source_file"] = str(file_path)
|
|
166
219
|
|
|
167
|
-
|
|
220
|
+
# Update pass/fail counts
|
|
221
|
+
if dut_result.get("pass_count", 0) == dut_result.get("total_count", 0):
|
|
222
|
+
result.passed_duts += 1
|
|
223
|
+
else:
|
|
224
|
+
result.failed_duts += 1
|
|
168
225
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ext = output_format.lower()
|
|
172
|
-
individual_filename = file_pattern.format(dut_id=dut_id, ext=ext)
|
|
173
|
-
individual_path = output_path / individual_filename
|
|
174
|
-
|
|
175
|
-
try:
|
|
176
|
-
_generate_individual_report(
|
|
177
|
-
dut_result, individual_path, report_template, output_format
|
|
178
|
-
)
|
|
179
|
-
result.individual_report_paths.append(individual_path)
|
|
180
|
-
except Exception as e:
|
|
181
|
-
logger.error(f"Failed to generate report for {dut_id}: {e}")
|
|
182
|
-
result.errors.append((dut_id, str(e)))
|
|
183
|
-
|
|
184
|
-
except Exception as e:
|
|
185
|
-
logger.error(f"Failed to process {file_path}: {e}")
|
|
186
|
-
result.errors.append((str(file_path), str(e)))
|
|
226
|
+
result.total_duts += 1
|
|
227
|
+
return dut_result
|
|
187
228
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error(f"Failed to process {file_path}: {e}")
|
|
231
|
+
result.errors.append((str(file_path), str(e)))
|
|
232
|
+
return None
|
|
192
233
|
|
|
193
|
-
try:
|
|
194
|
-
summary_report = generate_batch_report(batch_results)
|
|
195
|
-
_save_report(summary_report, summary_path, output_format)
|
|
196
|
-
result.summary_report_path = summary_path
|
|
197
|
-
except Exception as e:
|
|
198
|
-
logger.error(f"Failed to generate summary report: {e}")
|
|
199
|
-
result.errors.append(("summary", str(e)))
|
|
200
234
|
|
|
201
|
-
|
|
235
|
+
def _save_individual_report(
|
|
236
|
+
dut_result: dict[str, Any],
|
|
237
|
+
dut_id: str,
|
|
238
|
+
output_path: Path,
|
|
239
|
+
file_pattern: str,
|
|
240
|
+
output_format: str,
|
|
241
|
+
report_template: Any,
|
|
242
|
+
result: BatchReportResult,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Save individual DUT report."""
|
|
245
|
+
ext = output_format.lower()
|
|
246
|
+
individual_filename = file_pattern.format(dut_id=dut_id, ext=ext)
|
|
247
|
+
individual_path = output_path / individual_filename
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
_generate_individual_report(dut_result, individual_path, report_template, output_format)
|
|
251
|
+
result.individual_report_paths.append(individual_path)
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(f"Failed to generate report for {dut_id}: {e}")
|
|
254
|
+
result.errors.append((dut_id, str(e)))
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _create_summary_report(
|
|
258
|
+
batch_results: list[dict[str, Any]],
|
|
259
|
+
output_path: Path,
|
|
260
|
+
summary_filename: str,
|
|
261
|
+
output_format: str,
|
|
262
|
+
result: BatchReportResult,
|
|
263
|
+
) -> None:
|
|
264
|
+
"""Create and save summary report."""
|
|
265
|
+
ext = output_format.lower()
|
|
266
|
+
summary_path = output_path / summary_filename.format(ext=ext)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
summary_report = generate_batch_report(batch_results)
|
|
270
|
+
_save_report(summary_report, summary_path, output_format)
|
|
271
|
+
result.summary_report_path = summary_path
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"Failed to generate summary report: {e}")
|
|
274
|
+
result.errors.append(("summary", str(e)))
|
|
202
275
|
|
|
203
276
|
|
|
204
277
|
def _default_analysis(trace: Any) -> dict[str, Any]:
|