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