oscura 0.5.0__py3-none-any.whl → 0.6.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/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- 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/__init__.py +1 -22
- 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 +2763 -0
- 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/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- 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 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- 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/README.md +7 -7
- 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 +171 -63
- 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/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- 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/reporting/templates/index.md +13 -13
- 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/autodetect.py +1 -5
- 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 +11 -3
- 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.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- 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/export/wavedrom.py +0 -430
- 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 -338
- 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/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- 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/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /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/{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.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/reporting/standards.py
CHANGED
|
@@ -87,9 +87,22 @@ class FormatStandards:
|
|
|
87
87
|
References:
|
|
88
88
|
REPORT-001: Professional Formatting Standards
|
|
89
89
|
"""
|
|
90
|
+
root_vars = self._generate_css_variables()
|
|
91
|
+
base_styles = self._generate_css_base_styles()
|
|
92
|
+
component_styles = self._generate_css_component_styles()
|
|
93
|
+
print_styles = self._generate_css_print_styles()
|
|
94
|
+
|
|
90
95
|
return f"""
|
|
91
96
|
/* Oscura Professional Report Styles - REPORT-001 */
|
|
92
|
-
|
|
97
|
+
{root_vars}
|
|
98
|
+
{base_styles}
|
|
99
|
+
{component_styles}
|
|
100
|
+
{print_styles}
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def _generate_css_variables(self) -> str:
|
|
104
|
+
"""Generate CSS custom properties."""
|
|
105
|
+
return f""":root {{
|
|
93
106
|
--heading-font: {self.heading_font};
|
|
94
107
|
--body-font: {self.body_font};
|
|
95
108
|
--code-font: {self.code_font};
|
|
@@ -109,158 +122,186 @@ class FormatStandards:
|
|
|
109
122
|
--color-critical-bg: #ffebee;
|
|
110
123
|
--color-warning-bg: #fff3e0;
|
|
111
124
|
--color-info-bg: #e3f2fd;
|
|
112
|
-
}}
|
|
125
|
+
}}"""
|
|
113
126
|
|
|
114
|
-
|
|
127
|
+
def _generate_css_base_styles(self) -> str:
|
|
128
|
+
"""Generate base body and typography CSS."""
|
|
129
|
+
return """
|
|
130
|
+
body {
|
|
115
131
|
font-family: var(--body-font);
|
|
116
132
|
font-size: var(--body-size);
|
|
117
133
|
line-height: var(--line-spacing);
|
|
118
134
|
margin: var(--margin);
|
|
119
135
|
max-width: 8.5in;
|
|
120
136
|
color: #333;
|
|
121
|
-
}
|
|
137
|
+
}
|
|
122
138
|
|
|
123
|
-
h1, h2, h3, h4 {
|
|
139
|
+
h1, h2, h3, h4 {
|
|
124
140
|
font-family: var(--heading-font);
|
|
125
141
|
line-height: 1.2;
|
|
126
142
|
margin-top: 1.5em;
|
|
127
143
|
margin-bottom: 0.5em;
|
|
128
|
-
}
|
|
144
|
+
}
|
|
129
145
|
|
|
130
|
-
h1 {
|
|
131
|
-
h2 {
|
|
132
|
-
h3 {
|
|
146
|
+
h1 { font-size: var(--h1-size); }
|
|
147
|
+
h2 { font-size: var(--h2-size); }
|
|
148
|
+
h3 { font-size: var(--h3-size); }
|
|
133
149
|
|
|
134
|
-
.report-title {
|
|
150
|
+
.report-title {
|
|
135
151
|
font-size: var(--title-size);
|
|
136
152
|
text-align: center;
|
|
137
153
|
margin-bottom: 2em;
|
|
138
|
-
}
|
|
154
|
+
}
|
|
139
155
|
|
|
140
|
-
code, pre {
|
|
156
|
+
code, pre {
|
|
141
157
|
font-family: var(--code-font);
|
|
142
158
|
font-size: 0.9em;
|
|
143
159
|
background-color: #f5f5f5;
|
|
144
160
|
padding: 2px 4px;
|
|
145
161
|
border-radius: 3px;
|
|
146
|
-
}
|
|
162
|
+
}"""
|
|
163
|
+
|
|
164
|
+
def _generate_css_component_styles(self) -> str:
|
|
165
|
+
"""Generate component CSS (tables, severity, etc)."""
|
|
166
|
+
tables = self._generate_css_tables()
|
|
167
|
+
indicators = self._generate_css_indicators()
|
|
168
|
+
callouts = self._generate_css_callouts()
|
|
169
|
+
summary = self._generate_css_executive_summary()
|
|
170
|
+
watermark = self._generate_css_watermark()
|
|
147
171
|
|
|
172
|
+
return f"{tables}\n{indicators}\n{callouts}\n{summary}\n{watermark}"
|
|
173
|
+
|
|
174
|
+
def _generate_css_tables(self) -> str:
|
|
175
|
+
"""Generate table CSS."""
|
|
176
|
+
return """
|
|
148
177
|
/* Table styles */
|
|
149
|
-
table {
|
|
178
|
+
table {
|
|
150
179
|
border-collapse: collapse;
|
|
151
180
|
width: 100%;
|
|
152
181
|
margin: 1em 0;
|
|
153
|
-
}
|
|
182
|
+
}
|
|
154
183
|
|
|
155
|
-
th, td {
|
|
184
|
+
th, td {
|
|
156
185
|
border: 1px solid #ddd;
|
|
157
186
|
padding: 8px;
|
|
158
187
|
text-align: left;
|
|
159
|
-
}
|
|
188
|
+
}
|
|
160
189
|
|
|
161
|
-
th {
|
|
190
|
+
th {
|
|
162
191
|
background-color: #f2f2f2;
|
|
163
192
|
font-weight: bold;
|
|
164
|
-
}
|
|
193
|
+
}
|
|
165
194
|
|
|
166
|
-
tr:nth-child(even) {
|
|
195
|
+
tr:nth-child(even) {
|
|
167
196
|
background-color: #f9f9f9;
|
|
168
|
-
}
|
|
197
|
+
}"""
|
|
169
198
|
|
|
199
|
+
def _generate_css_indicators(self) -> str:
|
|
200
|
+
"""Generate pass/fail and severity indicator CSS."""
|
|
201
|
+
return """
|
|
170
202
|
/* Pass/Fail indicators (REPORT-002) */
|
|
171
|
-
.pass {
|
|
203
|
+
.pass {
|
|
172
204
|
color: var(--color-pass);
|
|
173
|
-
}
|
|
205
|
+
}
|
|
174
206
|
|
|
175
|
-
.fail {
|
|
207
|
+
.fail {
|
|
176
208
|
color: var(--color-fail);
|
|
177
|
-
}
|
|
209
|
+
}
|
|
178
210
|
|
|
179
|
-
.warning {
|
|
211
|
+
.warning {
|
|
180
212
|
color: var(--color-warning);
|
|
181
|
-
}
|
|
213
|
+
}
|
|
182
214
|
|
|
183
215
|
/* Severity indicators (REPORT-002) */
|
|
184
|
-
.severity-critical {
|
|
216
|
+
.severity-critical {
|
|
185
217
|
background-color: var(--color-critical-bg);
|
|
186
218
|
border-left: 4px solid var(--color-fail);
|
|
187
219
|
padding: 10px;
|
|
188
220
|
margin: 10px 0;
|
|
189
|
-
}
|
|
221
|
+
}
|
|
190
222
|
|
|
191
|
-
.severity-warning {
|
|
223
|
+
.severity-warning {
|
|
192
224
|
background-color: var(--color-warning-bg);
|
|
193
225
|
border-left: 4px solid var(--color-warning);
|
|
194
226
|
padding: 10px;
|
|
195
227
|
margin: 10px 0;
|
|
196
|
-
}
|
|
228
|
+
}
|
|
197
229
|
|
|
198
|
-
.severity-info {
|
|
230
|
+
.severity-info {
|
|
199
231
|
background-color: var(--color-info-bg);
|
|
200
232
|
border-left: 4px solid var(--color-info);
|
|
201
233
|
padding: 10px;
|
|
202
234
|
margin: 10px 0;
|
|
203
|
-
}
|
|
235
|
+
}"""
|
|
204
236
|
|
|
237
|
+
def _generate_css_callouts(self) -> str:
|
|
238
|
+
"""Generate callout box CSS."""
|
|
239
|
+
return """
|
|
205
240
|
/* Callout box (REPORT-002) */
|
|
206
|
-
.callout {
|
|
241
|
+
.callout {
|
|
207
242
|
border: 1px solid #ddd;
|
|
208
243
|
border-radius: 4px;
|
|
209
244
|
padding: 15px;
|
|
210
245
|
margin: 15px 0;
|
|
211
246
|
background-color: #fafafa;
|
|
212
|
-
}
|
|
247
|
+
}
|
|
213
248
|
|
|
214
|
-
.callout.key-finding {
|
|
249
|
+
.callout.key-finding {
|
|
215
250
|
border-color: var(--color-info);
|
|
216
251
|
background-color: var(--color-info-bg);
|
|
217
|
-
}
|
|
252
|
+
}
|
|
218
253
|
|
|
219
254
|
/* Highlighting for out-of-spec values */
|
|
220
|
-
.out-of-spec {
|
|
255
|
+
.out-of-spec {
|
|
221
256
|
background-color: rgba(255, 235, 59, 0.15);
|
|
222
257
|
padding: 2px 4px;
|
|
223
258
|
border-radius: 2px;
|
|
224
|
-
}
|
|
259
|
+
}"""
|
|
225
260
|
|
|
261
|
+
def _generate_css_executive_summary(self) -> str:
|
|
262
|
+
"""Generate executive summary CSS."""
|
|
263
|
+
return """
|
|
226
264
|
/* Executive summary styles (REPORT-004) */
|
|
227
|
-
.executive-summary {
|
|
265
|
+
.executive-summary {
|
|
228
266
|
background-color: #f5f5f5;
|
|
229
267
|
padding: 20px;
|
|
230
268
|
margin: 20px 0;
|
|
231
269
|
border-radius: 4px;
|
|
232
|
-
}
|
|
270
|
+
}
|
|
233
271
|
|
|
234
|
-
.executive-summary h2 {
|
|
272
|
+
.executive-summary h2 {
|
|
235
273
|
margin-top: 0;
|
|
236
|
-
}
|
|
274
|
+
}
|
|
237
275
|
|
|
238
|
-
.key-findings {
|
|
276
|
+
.key-findings {
|
|
239
277
|
list-style-type: none;
|
|
240
278
|
padding-left: 0;
|
|
241
|
-
}
|
|
279
|
+
}
|
|
242
280
|
|
|
243
|
-
.key-findings li {
|
|
281
|
+
.key-findings li {
|
|
244
282
|
padding: 5px 0;
|
|
245
283
|
padding-left: 25px;
|
|
246
284
|
position: relative;
|
|
247
|
-
}
|
|
285
|
+
}
|
|
248
286
|
|
|
249
|
-
.key-findings li::before {
|
|
287
|
+
.key-findings li::before {
|
|
250
288
|
content: "";
|
|
251
289
|
position: absolute;
|
|
252
290
|
left: 0;
|
|
253
291
|
top: 8px;
|
|
254
292
|
width: 16px;
|
|
255
293
|
height: 16px;
|
|
256
|
-
}
|
|
294
|
+
}
|
|
257
295
|
|
|
258
|
-
.key-findings li.critical::before {
|
|
296
|
+
.key-findings li.critical::before {
|
|
259
297
|
content: "!";
|
|
260
298
|
color: var(--color-fail);
|
|
261
299
|
font-weight: bold;
|
|
262
|
-
}
|
|
300
|
+
}"""
|
|
263
301
|
|
|
302
|
+
def _generate_css_watermark(self) -> str:
|
|
303
|
+
"""Generate watermark CSS."""
|
|
304
|
+
return f"""
|
|
264
305
|
/* Watermark */
|
|
265
306
|
.watermark {{
|
|
266
307
|
position: fixed;
|
|
@@ -271,21 +312,23 @@ tr:nth-child(even) {{
|
|
|
271
312
|
color: rgba(0, 0, 0, {self.watermark_opacity});
|
|
272
313
|
pointer-events: none;
|
|
273
314
|
z-index: 1000;
|
|
274
|
-
}}
|
|
315
|
+
}}"""
|
|
275
316
|
|
|
317
|
+
def _generate_css_print_styles(self) -> str:
|
|
318
|
+
"""Generate print media query CSS."""
|
|
319
|
+
return """
|
|
276
320
|
/* Print styles */
|
|
277
|
-
@media print {
|
|
278
|
-
body {
|
|
321
|
+
@media print {
|
|
322
|
+
body {
|
|
279
323
|
margin: 0;
|
|
280
|
-
}
|
|
281
|
-
.page-break {
|
|
324
|
+
}
|
|
325
|
+
.page-break {
|
|
282
326
|
page-break-before: always;
|
|
283
|
-
}
|
|
284
|
-
.no-print {
|
|
327
|
+
}
|
|
328
|
+
.no-print {
|
|
285
329
|
display: none;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
"""
|
|
330
|
+
}
|
|
331
|
+
}"""
|
|
289
332
|
|
|
290
333
|
|
|
291
334
|
@dataclass
|
|
@@ -215,57 +215,16 @@ def _format_frequency(freq_hz: float) -> str:
|
|
|
215
215
|
return f"{freq_hz:.1f} Hz"
|
|
216
216
|
|
|
217
217
|
|
|
218
|
-
def
|
|
218
|
+
def _build_findings(
|
|
219
|
+
signal_type: str,
|
|
220
|
+
type_confidence: float,
|
|
221
|
+
quality_level: str,
|
|
222
|
+
quality_issues: list[str],
|
|
219
223
|
trace: WaveformTrace,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
detail_level: str = "summary",
|
|
223
|
-
max_words: int = 200,
|
|
224
|
-
include_sections: list[str] | None = None,
|
|
225
|
-
) -> Summary:
|
|
226
|
-
"""Generate natural language summary of signal analysis.
|
|
227
|
-
|
|
228
|
-
Creates a plain-English description of the signal and analysis results,
|
|
229
|
-
avoiding technical jargon and explaining findings in accessible terms.
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
trace: Waveform to summarize.
|
|
233
|
-
context: Optional analysis context (characterization, anomalies, etc.).
|
|
234
|
-
detail_level: Summary detail level ("summary", "intermediate", "expert").
|
|
235
|
-
max_words: Maximum word count for summary text.
|
|
236
|
-
include_sections: Sections to include (default: all).
|
|
237
|
-
|
|
238
|
-
Returns:
|
|
239
|
-
Summary object with natural language description.
|
|
240
|
-
|
|
241
|
-
Example:
|
|
242
|
-
>>> trace = load("uart_signal.wfm")
|
|
243
|
-
>>> summary = generate_summary(trace)
|
|
244
|
-
>>> print(summary.text)
|
|
245
|
-
This is a digital signal with two voltage levels...
|
|
246
|
-
|
|
247
|
-
References:
|
|
248
|
-
DISC-003: Natural Language Summaries
|
|
249
|
-
"""
|
|
250
|
-
context = context or {}
|
|
251
|
-
include_sections = include_sections or ["overview", "findings", "recommendations"]
|
|
252
|
-
|
|
253
|
-
# Characterize signal type
|
|
254
|
-
signal_type, type_confidence = _characterize_signal_type(trace)
|
|
255
|
-
|
|
256
|
-
# Assess quality
|
|
257
|
-
quality_level, quality_issues = _assess_quality(trace)
|
|
258
|
-
|
|
259
|
-
# Build overview
|
|
260
|
-
sample_rate = trace.metadata.sample_rate
|
|
261
|
-
duration_ms = len(trace.data) / sample_rate * 1000
|
|
262
|
-
|
|
263
|
-
overview = f"This is a {signal_type} signal captured at {_format_frequency(sample_rate)} sample rate for {duration_ms:.1f} milliseconds."
|
|
264
|
-
|
|
265
|
-
# Build findings
|
|
224
|
+
) -> list[Finding]:
|
|
225
|
+
"""Build findings list from analysis results."""
|
|
266
226
|
findings = []
|
|
267
227
|
|
|
268
|
-
# Signal type finding
|
|
269
228
|
findings.append(
|
|
270
229
|
Finding(
|
|
271
230
|
title="Signal Type",
|
|
@@ -275,7 +234,6 @@ def generate_summary(
|
|
|
275
234
|
)
|
|
276
235
|
)
|
|
277
236
|
|
|
278
|
-
# Quality finding
|
|
279
237
|
quality_desc = f"Signal quality is {quality_level}"
|
|
280
238
|
if quality_issues:
|
|
281
239
|
quality_desc += f" with {len(quality_issues)} issue(s) noted"
|
|
@@ -289,7 +247,6 @@ def generate_summary(
|
|
|
289
247
|
)
|
|
290
248
|
)
|
|
291
249
|
|
|
292
|
-
# Voltage levels
|
|
293
250
|
v_min = float(np.min(trace.data))
|
|
294
251
|
v_max = float(np.max(trace.data))
|
|
295
252
|
v_range = v_max - v_min
|
|
@@ -303,7 +260,11 @@ def generate_summary(
|
|
|
303
260
|
)
|
|
304
261
|
)
|
|
305
262
|
|
|
306
|
-
|
|
263
|
+
return findings
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _build_recommendations(signal_type: str, quality_issues: list[str]) -> list[str]:
|
|
267
|
+
"""Build recommendations from analysis results."""
|
|
307
268
|
recommendations = []
|
|
308
269
|
|
|
309
270
|
if "very short" in str(quality_issues).lower():
|
|
@@ -322,14 +283,24 @@ def generate_summary(
|
|
|
322
283
|
elif signal_type in ["analog", "periodic analog"] and not recommendations:
|
|
323
284
|
recommendations.append("Consider spectral analysis to identify frequency components")
|
|
324
285
|
|
|
325
|
-
|
|
286
|
+
return recommendations
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _build_summary_text(
|
|
290
|
+
overview: str,
|
|
291
|
+
findings: list[Finding],
|
|
292
|
+
recommendations: list[str],
|
|
293
|
+
include_sections: list[str],
|
|
294
|
+
max_words: int,
|
|
295
|
+
) -> str:
|
|
296
|
+
"""Build complete summary text from components."""
|
|
326
297
|
summary_parts = []
|
|
327
298
|
|
|
328
299
|
if "overview" in include_sections:
|
|
329
300
|
summary_parts.append(overview)
|
|
330
301
|
|
|
331
302
|
if "findings" in include_sections and findings:
|
|
332
|
-
key_findings = findings[:3]
|
|
303
|
+
key_findings = findings[:3]
|
|
333
304
|
findings_text = " ".join(
|
|
334
305
|
[f"{finding.title}: {finding.description}." for finding in key_findings]
|
|
335
306
|
)
|
|
@@ -341,13 +312,62 @@ def generate_summary(
|
|
|
341
312
|
|
|
342
313
|
full_text = " ".join(summary_parts)
|
|
343
314
|
|
|
344
|
-
# Truncate to max_words if needed
|
|
345
315
|
words = full_text.split()
|
|
346
316
|
if len(words) > max_words:
|
|
347
317
|
words = words[:max_words]
|
|
348
318
|
full_text = " ".join(words) + "..."
|
|
349
319
|
|
|
350
|
-
|
|
320
|
+
return full_text
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def generate_summary(
|
|
324
|
+
trace: WaveformTrace,
|
|
325
|
+
*,
|
|
326
|
+
context: dict[str, Any] | None = None,
|
|
327
|
+
detail_level: str = "summary",
|
|
328
|
+
max_words: int = 200,
|
|
329
|
+
include_sections: list[str] | None = None,
|
|
330
|
+
) -> Summary:
|
|
331
|
+
"""Generate natural language summary of signal analysis.
|
|
332
|
+
|
|
333
|
+
Creates a plain-English description of the signal and analysis results,
|
|
334
|
+
avoiding technical jargon and explaining findings in accessible terms.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
trace: Waveform to summarize.
|
|
338
|
+
context: Optional analysis context (characterization, anomalies, etc.).
|
|
339
|
+
detail_level: Summary detail level ("summary", "intermediate", "expert").
|
|
340
|
+
max_words: Maximum word count for summary text.
|
|
341
|
+
include_sections: Sections to include (default: all).
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Summary object with natural language description.
|
|
345
|
+
|
|
346
|
+
Example:
|
|
347
|
+
>>> trace = load("uart_signal.wfm")
|
|
348
|
+
>>> summary = generate_summary(trace)
|
|
349
|
+
>>> print(summary.text)
|
|
350
|
+
This is a digital signal with two voltage levels...
|
|
351
|
+
|
|
352
|
+
References:
|
|
353
|
+
DISC-003: Natural Language Summaries
|
|
354
|
+
"""
|
|
355
|
+
context = context or {}
|
|
356
|
+
include_sections = include_sections or ["overview", "findings", "recommendations"]
|
|
357
|
+
|
|
358
|
+
signal_type, type_confidence = _characterize_signal_type(trace)
|
|
359
|
+
quality_level, quality_issues = _assess_quality(trace)
|
|
360
|
+
|
|
361
|
+
sample_rate = trace.metadata.sample_rate
|
|
362
|
+
duration_ms = len(trace.data) / sample_rate * 1000
|
|
363
|
+
overview = f"This is a {signal_type} signal captured at {_format_frequency(sample_rate)} sample rate for {duration_ms:.1f} milliseconds."
|
|
364
|
+
|
|
365
|
+
findings = _build_findings(signal_type, type_confidence, quality_level, quality_issues, trace)
|
|
366
|
+
recommendations = _build_recommendations(signal_type, quality_issues)
|
|
367
|
+
|
|
368
|
+
full_text = _build_summary_text(
|
|
369
|
+
overview, findings, recommendations, include_sections, max_words
|
|
370
|
+
)
|
|
351
371
|
word_count = len(full_text.split())
|
|
352
372
|
grade_level = _estimate_grade_level(full_text)
|
|
353
373
|
|
oscura/reporting/tables.py
CHANGED
|
@@ -21,6 +21,138 @@ if TYPE_CHECKING:
|
|
|
21
21
|
from numpy.typing import NDArray
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def _build_table_headers(show_spec: bool, show_margin: bool, show_status: bool) -> list[str]:
|
|
25
|
+
"""Build table headers based on display options.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
show_spec: Include specification column.
|
|
29
|
+
show_margin: Include margin column.
|
|
30
|
+
show_status: Include status column.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of header strings.
|
|
34
|
+
"""
|
|
35
|
+
headers = ["Parameter", "Value"]
|
|
36
|
+
if show_spec:
|
|
37
|
+
headers.append("Specification")
|
|
38
|
+
if show_margin:
|
|
39
|
+
headers.append("Margin")
|
|
40
|
+
if show_status:
|
|
41
|
+
headers.append("Status")
|
|
42
|
+
return headers
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _format_value_cell(value: Any, unit: str, formatter: NumberFormatter) -> str:
|
|
46
|
+
"""Format measurement value cell.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
value: Measurement value.
|
|
50
|
+
unit: Unit string.
|
|
51
|
+
formatter: Number formatter instance.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Formatted value string.
|
|
55
|
+
"""
|
|
56
|
+
if value is None:
|
|
57
|
+
return "N/A"
|
|
58
|
+
return formatter.format(value, unit)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _format_spec_cell(spec: Any, spec_type: str, unit: str, formatter: NumberFormatter) -> str:
|
|
62
|
+
"""Format specification cell with comparison operator.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
spec: Specification value.
|
|
66
|
+
spec_type: Spec type ("max", "min", or "exact").
|
|
67
|
+
unit: Unit string.
|
|
68
|
+
formatter: Number formatter instance.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Formatted specification string.
|
|
72
|
+
"""
|
|
73
|
+
if spec is None:
|
|
74
|
+
return "-"
|
|
75
|
+
|
|
76
|
+
prefix = "<" if spec_type == "max" else ">" if spec_type == "min" else "="
|
|
77
|
+
return f"{prefix}{formatter.format(spec, unit)}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _calculate_margin(value: float, spec: float, spec_type: str) -> str:
|
|
81
|
+
"""Calculate margin percentage between value and spec.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
value: Measured value.
|
|
85
|
+
spec: Specification limit.
|
|
86
|
+
spec_type: Specification type ("max" or "min").
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Formatted margin percentage string.
|
|
90
|
+
"""
|
|
91
|
+
if spec == 0:
|
|
92
|
+
return "-"
|
|
93
|
+
|
|
94
|
+
if spec_type == "max":
|
|
95
|
+
margin = (spec - value) / spec * 100
|
|
96
|
+
else:
|
|
97
|
+
margin = (value - spec) / spec * 100
|
|
98
|
+
|
|
99
|
+
return f"{margin:.1f}%"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _build_measurement_row(
|
|
103
|
+
name: str,
|
|
104
|
+
meas: dict[str, Any],
|
|
105
|
+
formatter: NumberFormatter,
|
|
106
|
+
show_spec: bool,
|
|
107
|
+
show_margin: bool,
|
|
108
|
+
show_status: bool,
|
|
109
|
+
) -> list[str]:
|
|
110
|
+
"""Build single measurement table row.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
name: Measurement parameter name.
|
|
114
|
+
meas: Measurement data dictionary.
|
|
115
|
+
formatter: Number formatter instance.
|
|
116
|
+
show_spec: Include specification column.
|
|
117
|
+
show_margin: Include margin column.
|
|
118
|
+
show_status: Include status column.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of cell values for table row.
|
|
122
|
+
"""
|
|
123
|
+
row = [name]
|
|
124
|
+
value = meas.get("value")
|
|
125
|
+
unit = meas.get("unit", "")
|
|
126
|
+
|
|
127
|
+
# Value column
|
|
128
|
+
row.append(_format_value_cell(value, unit, formatter))
|
|
129
|
+
|
|
130
|
+
# Specification column
|
|
131
|
+
if show_spec:
|
|
132
|
+
spec = meas.get("spec")
|
|
133
|
+
spec_type = meas.get("spec_type", "max")
|
|
134
|
+
row.append(_format_spec_cell(spec, spec_type, unit, formatter))
|
|
135
|
+
|
|
136
|
+
# Margin column
|
|
137
|
+
if show_margin:
|
|
138
|
+
spec = meas.get("spec")
|
|
139
|
+
if value is not None and spec is not None:
|
|
140
|
+
spec_type = meas.get("spec_type", "max")
|
|
141
|
+
row.append(_calculate_margin(value, spec, spec_type))
|
|
142
|
+
else:
|
|
143
|
+
row.append("-")
|
|
144
|
+
|
|
145
|
+
# Status column
|
|
146
|
+
if show_status:
|
|
147
|
+
passed = meas.get("passed", True)
|
|
148
|
+
if value is None:
|
|
149
|
+
row.append("N/A")
|
|
150
|
+
else:
|
|
151
|
+
row.append("✓ PASS" if passed else "✗ FAIL")
|
|
152
|
+
|
|
153
|
+
return row
|
|
154
|
+
|
|
155
|
+
|
|
24
156
|
def create_measurement_table(
|
|
25
157
|
measurements: dict[str, Any],
|
|
26
158
|
*,
|
|
@@ -56,62 +188,14 @@ def create_measurement_table(
|
|
|
56
188
|
References:
|
|
57
189
|
REPORT-004, REPORT-006
|
|
58
190
|
"""
|
|
59
|
-
|
|
60
|
-
headers = ["Parameter", "Value"]
|
|
61
|
-
if show_spec:
|
|
62
|
-
headers.append("Specification")
|
|
63
|
-
if show_margin:
|
|
64
|
-
headers.append("Margin")
|
|
65
|
-
if show_status:
|
|
66
|
-
headers.append("Status")
|
|
67
|
-
|
|
68
|
-
# Build table rows
|
|
69
|
-
rows = []
|
|
191
|
+
headers = _build_table_headers(show_spec, show_margin, show_status)
|
|
70
192
|
formatter = NumberFormatter(sig_figs=3)
|
|
71
193
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
unit = meas.get("unit", "")
|
|
78
|
-
if value is not None:
|
|
79
|
-
row.append(formatter.format(value, unit))
|
|
80
|
-
else:
|
|
81
|
-
row.append("N/A")
|
|
82
|
-
|
|
83
|
-
# Specification
|
|
84
|
-
if show_spec:
|
|
85
|
-
spec = meas.get("spec")
|
|
86
|
-
spec_type = meas.get("spec_type", "max")
|
|
87
|
-
if spec is not None:
|
|
88
|
-
prefix = "<" if spec_type == "max" else ">" if spec_type == "min" else "="
|
|
89
|
-
row.append(f"{prefix}{formatter.format(spec, unit)}")
|
|
90
|
-
else:
|
|
91
|
-
row.append("-")
|
|
92
|
-
|
|
93
|
-
# Margin
|
|
94
|
-
if show_margin:
|
|
95
|
-
spec = meas.get("spec")
|
|
96
|
-
if value is not None and spec is not None and spec != 0:
|
|
97
|
-
spec_type = meas.get("spec_type", "max")
|
|
98
|
-
if spec_type == "max":
|
|
99
|
-
margin = (spec - value) / spec * 100
|
|
100
|
-
else:
|
|
101
|
-
margin = (value - spec) / spec * 100
|
|
102
|
-
row.append(f"{margin:.1f}%")
|
|
103
|
-
else:
|
|
104
|
-
row.append("-")
|
|
105
|
-
|
|
106
|
-
# Status
|
|
107
|
-
if show_status:
|
|
108
|
-
passed = meas.get("passed", True)
|
|
109
|
-
if value is None:
|
|
110
|
-
row.append("N/A")
|
|
111
|
-
else:
|
|
112
|
-
row.append("✓ PASS" if passed else "✗ FAIL")
|
|
113
|
-
|
|
114
|
-
rows.append(row)
|
|
194
|
+
# Build rows
|
|
195
|
+
rows = [
|
|
196
|
+
_build_measurement_row(name, meas, formatter, show_spec, show_margin, show_status)
|
|
197
|
+
for name, meas in measurements.items()
|
|
198
|
+
]
|
|
115
199
|
|
|
116
200
|
# Sort if requested
|
|
117
201
|
if sort_by and sort_by in headers:
|