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.
Files changed (513) 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/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -61,6 +61,12 @@ from oscura.reporting.core_formats import ( # core_formats/ directory
61
61
  from oscura.reporting.engine import (
62
62
  AnalysisEngine,
63
63
  )
64
+ from oscura.reporting.enhanced_reports import (
65
+ EnhancedReportGenerator,
66
+ )
67
+ from oscura.reporting.enhanced_reports import (
68
+ ReportConfig as EnhancedReportConfig,
69
+ )
64
70
  from oscura.reporting.export import (
65
71
  batch_export_formats,
66
72
  export_multiple_reports,
@@ -138,10 +144,6 @@ from oscura.reporting.template_system import (
138
144
  list_templates,
139
145
  load_template,
140
146
  )
141
- from oscura.reporting.vintage_logic_report import (
142
- VintageLogicReport,
143
- generate_vintage_logic_report,
144
- )
145
147
 
146
148
  __all__ = [
147
149
  # Comprehensive Analysis Report API (CAR-001 through CAR-007)
@@ -162,6 +164,9 @@ __all__ = [
162
164
  "ColorScheme",
163
165
  "DataOutputConfig",
164
166
  "DomainConfig",
167
+ # Enhanced Reports (Feature 6)
168
+ "EnhancedReportConfig",
169
+ "EnhancedReportGenerator",
165
170
  "ExecutiveSummary",
166
171
  # Summary Generation
167
172
  "Finding",
@@ -191,8 +196,6 @@ __all__ = [
191
196
  "TemplateEngine",
192
197
  "TemplateSection",
193
198
  "UnsupportedFormatError",
194
- # Vintage Logic Reporting
195
- "VintageLogicReport",
196
199
  "VisualEmphasis",
197
200
  "aggregate_batch_measurements",
198
201
  "analyze",
@@ -241,7 +244,6 @@ __all__ = [
241
244
  "generate_presentation_from_report",
242
245
  "generate_report",
243
246
  "generate_summary",
244
- "generate_vintage_logic_report",
245
247
  "get_available_analyses",
246
248
  "get_axis_scaling",
247
249
  "list_templates",
@@ -49,8 +49,7 @@ def analyze(
49
49
  Args:
50
50
  input_path: Path to input data file (any supported format).
51
51
  data: In-memory data (Trace, bytes, list of packets).
52
- output_dir: Base directory for output. Default: input file's directory
53
- or current directory for in-memory data.
52
+ output_dir: Base directory for output.
54
53
  config: Analysis configuration. Default: analyze all applicable domains.
55
54
  progress_callback: Called with progress updates during analysis.
56
55
 
@@ -62,122 +61,288 @@ def analyze(
62
61
  ValueError: If neither or both input_path and data are provided.
63
62
 
64
63
  Examples:
65
- # From file
66
- result = analyze("capture.wfm")
67
- print(result.output_dir) # 20260101_120000_capture_analysis/
64
+ >>> result = analyze("capture.wfm")
65
+ >>> result = analyze(data=my_trace, output_dir="/reports")
66
+ >>> config = AnalysisConfig(domains=[AnalysisDomain.SPECTRAL])
67
+ >>> result = analyze("capture.wfm", config=config)
68
+ """
69
+ config = config or AnalysisConfig()
70
+ _validate_inputs(input_path, data)
71
+ start_time = time.time()
68
72
 
69
- # From in-memory data
70
- result = analyze(data=my_waveform_trace, output_dir="/reports")
73
+ # Setup and load data
74
+ output_manager, input_name, input_type, loaded_data, resolved_path = _setup_analysis(
75
+ input_path, data, output_dir, progress_callback, start_time
76
+ )
71
77
 
72
- # With configuration
73
- config = AnalysisConfig(domains=[AnalysisDomain.SPECTRAL])
74
- result = analyze("capture.wfm", config=config)
78
+ # Run analysis pipeline
79
+ engine_result, plot_paths, saved_paths = _run_analysis_pipeline(
80
+ config,
81
+ resolved_path,
82
+ loaded_data,
83
+ input_name,
84
+ input_type,
85
+ output_manager,
86
+ progress_callback,
87
+ start_time,
88
+ )
75
89
 
76
- # With progress callback
77
- def on_progress(info):
78
- print(f"{info.domain}: {info.percent}%")
79
- result = analyze("capture.wfm", progress_callback=on_progress)
80
- """
81
- # Validate inputs
90
+ # Build and finalize result
91
+ result = _finalize_result(
92
+ output_manager,
93
+ resolved_path,
94
+ input_type,
95
+ engine_result,
96
+ saved_paths,
97
+ plot_paths,
98
+ config,
99
+ progress_callback,
100
+ start_time,
101
+ )
102
+
103
+ logger.info(f"Analysis complete. Output: {result.output_dir}")
104
+ return result
105
+
106
+
107
+ def _run_analysis_pipeline(
108
+ config: AnalysisConfig,
109
+ resolved_path: Path | None,
110
+ loaded_data: Any,
111
+ input_name: str,
112
+ input_type: InputType,
113
+ output_manager: OutputManager,
114
+ progress_callback: Callable[[ProgressInfo], None] | None,
115
+ start_time: float,
116
+ ) -> tuple[dict[str, Any], list[Path], dict[str, Any]]:
117
+ """Run complete analysis pipeline."""
118
+ engine_result = _run_analysis_engine(
119
+ config, resolved_path, loaded_data, input_name, input_type, progress_callback
120
+ )
121
+ plot_paths = _generate_plots(
122
+ config, engine_result, output_manager, progress_callback, start_time
123
+ )
124
+ saved_paths = _save_all_outputs(
125
+ output_manager,
126
+ input_name,
127
+ input_type,
128
+ resolved_path,
129
+ datetime.now(),
130
+ engine_result,
131
+ config,
132
+ start_time,
133
+ )
134
+ return engine_result, plot_paths, saved_paths
135
+
136
+
137
+ def _finalize_result(
138
+ output_manager: OutputManager,
139
+ resolved_path: Path | None,
140
+ input_type: InputType,
141
+ engine_result: dict[str, Any],
142
+ saved_paths: dict[str, Any],
143
+ plot_paths: list[Path],
144
+ config: AnalysisConfig,
145
+ progress_callback: Callable[[ProgressInfo], None] | None,
146
+ start_time: float,
147
+ ) -> AnalysisResult:
148
+ """Build and finalize analysis result."""
149
+ partial_result = _build_result(
150
+ output_manager,
151
+ resolved_path,
152
+ input_type,
153
+ engine_result,
154
+ saved_paths["summary_json"],
155
+ saved_paths["summary_yaml"],
156
+ saved_paths["metadata_json"],
157
+ saved_paths["config_yaml"],
158
+ saved_paths["domain_dirs"],
159
+ plot_paths,
160
+ saved_paths["error_log"],
161
+ start_time,
162
+ )
163
+ index_paths = _generate_index(
164
+ output_manager, partial_result, config, progress_callback, start_time
165
+ )
166
+ result = _build_final_result(partial_result, index_paths, time.time() - start_time)
167
+
168
+ _report_progress(
169
+ progress_callback,
170
+ "complete",
171
+ None,
172
+ None,
173
+ 100.0,
174
+ f"Analysis complete: {result.successful_analyses}/{result.total_analyses} successful",
175
+ time.time() - start_time,
176
+ )
177
+ return result
178
+
179
+
180
+ def _setup_analysis(
181
+ input_path: str | Path | None,
182
+ data: Trace | bytes | list[Any] | None,
183
+ output_dir: str | Path | None,
184
+ progress_callback: Callable[[ProgressInfo], None] | None,
185
+ start_time: float,
186
+ ) -> tuple[OutputManager, str, InputType, Any, Path | None]:
187
+ """Setup analysis environment and load data."""
188
+ input_name, input_type, loaded_data, resolved_path = _prepare_input(input_path, data)
189
+ base_dir = _determine_output_dir(resolved_path, output_dir)
190
+ output_manager = OutputManager(base_dir, input_name, datetime.now())
191
+ output_manager.create()
192
+ _report_progress(
193
+ progress_callback,
194
+ "initializing",
195
+ None,
196
+ None,
197
+ 0.0,
198
+ "Initializing analysis",
199
+ time.time() - start_time,
200
+ )
201
+ return output_manager, input_name, input_type, loaded_data, resolved_path
202
+
203
+
204
+ def _save_all_outputs(
205
+ output_manager: OutputManager,
206
+ input_name: str,
207
+ input_type: InputType,
208
+ resolved_path: Path | None,
209
+ timestamp: datetime,
210
+ engine_result: Any,
211
+ config: AnalysisConfig,
212
+ start_time: float,
213
+ ) -> dict[str, Any]:
214
+ """Save all analysis outputs and return paths."""
215
+ summary_json, summary_yaml = _save_summary(
216
+ output_manager,
217
+ input_name,
218
+ input_type,
219
+ resolved_path,
220
+ timestamp,
221
+ engine_result,
222
+ config,
223
+ start_time,
224
+ )
225
+ return {
226
+ "summary_json": summary_json,
227
+ "summary_yaml": summary_yaml,
228
+ "metadata_json": _save_metadata(
229
+ output_manager, resolved_path, input_type, timestamp, engine_result, start_time
230
+ ),
231
+ "config_yaml": _save_config(output_manager, config, input_type),
232
+ "domain_dirs": _save_domain_results(output_manager, engine_result),
233
+ "error_log": _save_errors(output_manager, engine_result),
234
+ }
235
+
236
+
237
+ def _validate_inputs(input_path: str | Path | None, data: Trace | bytes | list[Any] | None) -> None:
238
+ """Validate that exactly one input source is provided."""
82
239
  if input_path is None and data is None:
83
240
  raise ValueError("Either input_path or data must be provided")
84
241
  if input_path is not None and data is not None:
85
242
  raise ValueError("Provide input_path OR data, not both")
86
243
 
87
- # Use default config if not provided
88
- if config is None:
89
- config = AnalysisConfig()
90
244
 
91
- # Track timing
92
- start_time = time.time()
245
+ def _prepare_input(
246
+ input_path: str | Path | None, data: Trace | bytes | list[Any] | None
247
+ ) -> tuple[str, InputType, Any, Path | None]:
248
+ """Prepare input data and determine type.
93
249
 
94
- # Determine input name and type
250
+ Returns:
251
+ Tuple of (input_name, input_type, loaded_data, resolved_path)
252
+ """
95
253
  if input_path is not None:
96
- input_path = Path(input_path)
97
- if not input_path.exists():
98
- raise FileNotFoundError(f"Input file not found: {input_path}")
99
- input_name = input_path.stem
100
- input_type = _detect_input_type_from_file(input_path)
101
- loaded_data = _load_input_file(input_path, input_type)
254
+ path = Path(input_path)
255
+ if not path.exists():
256
+ raise FileNotFoundError(f"Input file not found: {path}")
257
+ input_name = path.stem
258
+ input_type = _detect_input_type_from_file(path)
259
+ loaded_data = _load_input_file(path, input_type)
260
+ return input_name, input_type, loaded_data, path
102
261
  else:
103
262
  input_name = "memory_data"
104
263
  input_type = _detect_input_type_from_data(data)
105
264
  loaded_data = data
265
+ return input_name, input_type, loaded_data, None
106
266
 
107
- # Determine output directory
108
- if output_dir is None:
109
- if input_path is not None:
110
- base_dir = input_path.parent
111
- else:
112
- base_dir = Path.cwd()
113
- else:
114
- base_dir = Path(output_dir)
115
-
116
- # Create output manager with timestamp
117
- timestamp = datetime.now()
118
- output_manager = OutputManager(base_dir, input_name, timestamp)
119
- output_manager.create()
120
-
121
- # Report progress: starting
122
- _report_progress(
123
- progress_callback,
124
- phase="initializing",
125
- domain=None,
126
- function=None,
127
- percent=0.0,
128
- message="Initializing analysis",
129
- elapsed=time.time() - start_time,
130
- )
131
267
 
132
- # Determine applicable domains
268
+ def _determine_output_dir(input_path: Path | None, output_dir: str | Path | None) -> Path:
269
+ """Determine output directory based on input path or override."""
270
+ if output_dir is not None:
271
+ return Path(output_dir)
272
+ if input_path is not None:
273
+ return input_path.parent
274
+ return Path.cwd()
275
+
276
+
277
+ def _run_analysis_engine(
278
+ config: AnalysisConfig,
279
+ input_path: Path | None,
280
+ loaded_data: Any,
281
+ input_name: str,
282
+ input_type: InputType,
283
+ progress_callback: Callable[[ProgressInfo], None] | None,
284
+ ) -> dict[str, Any]:
285
+ """Run analysis engine on data."""
133
286
  applicable_domains = get_available_analyses(input_type)
134
287
  enabled_domains = [d for d in applicable_domains if config.is_domain_enabled(d)]
135
288
 
136
289
  logger.info(f"Running analysis on {input_name} ({input_type.value})")
137
290
  logger.info(f"Enabled domains: {[d.value for d in enabled_domains]}")
138
291
 
139
- # Execute analysis engine
140
292
  from oscura.reporting.engine import AnalysisEngine
141
293
 
142
294
  engine = AnalysisEngine(config)
143
- engine_result = engine.run(
295
+ return engine.run(
144
296
  input_path=input_path,
145
297
  data=loaded_data,
146
298
  progress_callback=progress_callback,
147
299
  )
148
300
 
149
- # Generate plots
150
- plot_paths: list[Path] = []
151
- if config.generate_plots:
152
- _report_progress(
153
- progress_callback,
154
- phase="plotting",
155
- domain=None,
156
- function=None,
157
- percent=70.0,
158
- message="Generating visualizations",
159
- elapsed=time.time() - start_time,
160
- )
161
301
 
162
- from oscura.reporting.plots import PlotGenerator
302
+ def _generate_plots(
303
+ config: AnalysisConfig,
304
+ engine_result: dict[str, Any],
305
+ output_manager: OutputManager,
306
+ progress_callback: Callable[[ProgressInfo], None] | None,
307
+ start_time: float,
308
+ ) -> list[Path]:
309
+ """Generate plots if configured."""
310
+ plot_paths: list[Path] = []
163
311
 
164
- plot_gen = PlotGenerator(config)
165
- for domain, results in engine_result["results"].items():
166
- domain_plots = plot_gen.generate_plots(domain, results, output_manager)
167
- plot_paths.extend(domain_plots)
312
+ if not config.generate_plots:
313
+ return plot_paths
168
314
 
169
- # Save data outputs
170
315
  _report_progress(
171
316
  progress_callback,
172
- phase="saving",
173
- domain=None,
174
- function=None,
175
- percent=85.0,
176
- message="Saving analysis results",
177
- elapsed=time.time() - start_time,
317
+ "plotting",
318
+ None,
319
+ None,
320
+ 70.0,
321
+ "Generating visualizations",
322
+ time.time() - start_time,
178
323
  )
179
324
 
180
- # Save summary data
325
+ from oscura.reporting.plots import PlotGenerator
326
+
327
+ plot_gen = PlotGenerator(config)
328
+ for domain, results in engine_result["results"].items():
329
+ domain_plots = plot_gen.generate_plots(domain, results, output_manager)
330
+ plot_paths.extend(domain_plots)
331
+
332
+ return plot_paths
333
+
334
+
335
+ def _save_summary(
336
+ output_manager: OutputManager,
337
+ input_name: str,
338
+ input_type: InputType,
339
+ input_path: Path | None,
340
+ timestamp: datetime,
341
+ engine_result: dict[str, Any],
342
+ config: AnalysisConfig,
343
+ start_time: float,
344
+ ) -> tuple[Path, Path | None]:
345
+ """Save summary data."""
181
346
  summary_data = {
182
347
  "input": {
183
348
  "name": input_name,
@@ -195,7 +360,18 @@ def analyze(
195
360
  if "yaml" in config.output_formats:
196
361
  summary_yaml = output_manager.save_yaml("summary", summary_data)
197
362
 
198
- # Save metadata
363
+ return summary_json, summary_yaml
364
+
365
+
366
+ def _save_metadata(
367
+ output_manager: OutputManager,
368
+ input_path: Path | None,
369
+ input_type: InputType,
370
+ timestamp: datetime,
371
+ engine_result: dict[str, Any],
372
+ start_time: float,
373
+ ) -> Path:
374
+ """Save metadata."""
199
375
  metadata = {
200
376
  "oscura_version": _get_version(),
201
377
  "analysis_version": "2.0",
@@ -208,9 +384,16 @@ def analyze(
208
384
  "failed": engine_result["stats"]["failed_analyses"],
209
385
  "skipped": engine_result["stats"].get("skipped_analyses", 0),
210
386
  }
211
- metadata_json = output_manager.save_json("metadata", metadata)
387
+ return output_manager.save_json("metadata", metadata)
388
+
389
+
390
+ def _save_config(
391
+ output_manager: OutputManager, config: AnalysisConfig, input_type: InputType
392
+ ) -> Path:
393
+ """Save configuration."""
394
+ applicable_domains = get_available_analyses(input_type)
395
+ enabled_domains = [d for d in applicable_domains if config.is_domain_enabled(d)]
212
396
 
213
- # Save configuration
214
397
  config_data = {
215
398
  "domains": [d.value for d in enabled_domains],
216
399
  "generate_plots": config.generate_plots,
@@ -219,34 +402,57 @@ def analyze(
219
402
  "output_formats": config.output_formats,
220
403
  "index_formats": config.index_formats,
221
404
  }
222
- config_yaml = output_manager.save_yaml("config", config_data)
405
+ return output_manager.save_yaml("config", config_data)
223
406
 
224
- # Save domain results
407
+
408
+ def _save_domain_results(
409
+ output_manager: OutputManager, engine_result: dict[str, Any]
410
+ ) -> dict[AnalysisDomain, Path]:
411
+ """Save domain-specific results."""
225
412
  domain_dirs: dict[AnalysisDomain, Path] = {}
226
413
  for domain, results in engine_result["results"].items():
227
414
  domain_dir = output_manager.create_domain_dir(domain)
228
415
  domain_dirs[domain] = domain_dir
229
416
  output_manager.save_json("results", results, subdir=domain.value)
417
+ return domain_dirs
418
+
230
419
 
231
- # Save errors if any
232
- error_log: Path | None = None
420
+ def _save_errors(output_manager: OutputManager, engine_result: dict[str, Any]) -> Path | None:
421
+ """Save error log if errors occurred."""
233
422
  errors: list[AnalysisError] = engine_result["errors"]
234
- if errors:
235
- error_list = [
236
- {
237
- "domain": e.domain.value,
238
- "function": e.function,
239
- "error_type": e.error_type,
240
- "error_message": e.error_message,
241
- "duration_ms": e.duration_ms,
242
- }
243
- for e in errors
244
- ]
245
- error_data = {"errors": error_list, "count": len(error_list)}
246
- error_log = output_manager.save_json("failed_analyses", error_data, subdir="errors")
247
-
248
- # Build AnalysisResult for index generation
249
- partial_result = AnalysisResult(
423
+ if not errors:
424
+ return None
425
+
426
+ error_list = [
427
+ {
428
+ "domain": e.domain.value,
429
+ "function": e.function,
430
+ "error_type": e.error_type,
431
+ "error_message": e.error_message,
432
+ "duration_ms": e.duration_ms,
433
+ }
434
+ for e in errors
435
+ ]
436
+ error_data = {"errors": error_list, "count": len(error_list)}
437
+ return output_manager.save_json("failed_analyses", error_data, subdir="errors")
438
+
439
+
440
+ def _build_result(
441
+ output_manager: OutputManager,
442
+ input_path: Path | None,
443
+ input_type: InputType,
444
+ engine_result: dict[str, Any],
445
+ summary_json: Path,
446
+ summary_yaml: Path | None,
447
+ metadata_json: Path,
448
+ config_yaml: Path,
449
+ domain_dirs: dict[AnalysisDomain, Path],
450
+ plot_paths: list[Path],
451
+ error_log: Path | None,
452
+ start_time: float,
453
+ ) -> AnalysisResult:
454
+ """Build partial AnalysisResult for index generation."""
455
+ return AnalysisResult(
250
456
  output_dir=output_manager.root,
251
457
  index_html=None,
252
458
  index_md=None,
@@ -266,63 +472,63 @@ def analyze(
266
472
  skipped_analyses=engine_result["stats"].get("skipped_analyses", 0),
267
473
  duration_seconds=time.time() - start_time,
268
474
  domain_summaries=engine_result["results"],
269
- errors=errors,
475
+ errors=engine_result["errors"],
270
476
  )
271
477
 
272
- # Generate index files
478
+
479
+ def _generate_index(
480
+ output_manager: OutputManager,
481
+ partial_result: AnalysisResult,
482
+ config: AnalysisConfig,
483
+ progress_callback: Callable[[ProgressInfo], None] | None,
484
+ start_time: float,
485
+ ) -> dict[str, Path]:
486
+ """Generate index files."""
273
487
  _report_progress(
274
488
  progress_callback,
275
- phase="indexing",
276
- domain=None,
277
- function=None,
278
- percent=95.0,
279
- message="Generating index files",
280
- elapsed=time.time() - start_time,
489
+ "indexing",
490
+ None,
491
+ None,
492
+ 95.0,
493
+ "Generating index files",
494
+ time.time() - start_time,
281
495
  )
282
496
 
283
497
  from oscura.reporting.index import IndexGenerator
284
498
 
285
499
  index_gen = IndexGenerator(output_manager)
286
- index_paths = index_gen.generate(partial_result, config.index_formats)
500
+ return index_gen.generate(partial_result, config.index_formats)
287
501
 
288
- # Complete result
289
- result = AnalysisResult(
290
- output_dir=output_manager.root,
502
+
503
+ def _build_final_result(
504
+ partial_result: AnalysisResult,
505
+ index_paths: dict[str, Path],
506
+ duration: float,
507
+ ) -> AnalysisResult:
508
+ """Build final AnalysisResult with index paths."""
509
+ return AnalysisResult(
510
+ output_dir=partial_result.output_dir,
291
511
  index_html=index_paths.get("html"),
292
512
  index_md=index_paths.get("md"),
293
513
  index_pdf=index_paths.get("pdf"),
294
- summary_json=summary_json,
295
- summary_yaml=summary_yaml,
296
- metadata_json=metadata_json,
297
- config_yaml=config_yaml,
298
- domain_dirs=domain_dirs,
299
- plot_paths=plot_paths,
300
- error_log=error_log,
301
- input_file=str(input_path) if input_path else None,
302
- input_type=input_type,
303
- total_analyses=engine_result["stats"]["total_analyses"],
304
- successful_analyses=engine_result["stats"]["successful_analyses"],
305
- failed_analyses=engine_result["stats"]["failed_analyses"],
306
- skipped_analyses=engine_result["stats"].get("skipped_analyses", 0),
307
- duration_seconds=time.time() - start_time,
308
- domain_summaries=engine_result["results"],
309
- errors=errors,
310
- )
311
-
312
- # Report completion
313
- _report_progress(
314
- progress_callback,
315
- phase="complete",
316
- domain=None,
317
- function=None,
318
- percent=100.0,
319
- message=f"Analysis complete: {result.successful_analyses}/{result.total_analyses} successful",
320
- elapsed=time.time() - start_time,
514
+ summary_json=partial_result.summary_json,
515
+ summary_yaml=partial_result.summary_yaml,
516
+ metadata_json=partial_result.metadata_json,
517
+ config_yaml=partial_result.config_yaml,
518
+ domain_dirs=partial_result.domain_dirs,
519
+ plot_paths=partial_result.plot_paths,
520
+ error_log=partial_result.error_log,
521
+ input_file=partial_result.input_file,
522
+ input_type=partial_result.input_type,
523
+ total_analyses=partial_result.total_analyses,
524
+ successful_analyses=partial_result.successful_analyses,
525
+ failed_analyses=partial_result.failed_analyses,
526
+ skipped_analyses=partial_result.skipped_analyses,
527
+ duration_seconds=duration,
528
+ domain_summaries=partial_result.domain_summaries,
529
+ errors=partial_result.errors,
321
530
  )
322
531
 
323
- logger.info(f"Analysis complete. Output: {result.output_dir}")
324
- return result
325
-
326
532
 
327
533
  def _detect_input_type_from_file(path: Path) -> InputType:
328
534
  """Detect input type from file extension."""
@@ -398,7 +604,7 @@ def _load_input_file(path: Path, input_type: InputType) -> Any:
398
604
 
399
605
  return load_pcap(path)
400
606
  elif input_type == InputType.SPARAMS:
401
- from oscura.analyzers.signal_integrity.sparams import load_touchstone
607
+ from oscura.loaders.touchstone import load_touchstone
402
608
 
403
609
  return load_touchstone(path)
404
610
  else: