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
@@ -1,289 +0,0 @@
1
- """Trace annotation support.
2
-
3
- This module provides annotation capabilities for marking points of interest
4
- in signal traces.
5
-
6
-
7
- Example:
8
- >>> layer = AnnotationLayer("Debug Markers")
9
- >>> layer.add(Annotation(time=1.5e-6, text="Glitch detected"))
10
- >>> layer.add(Annotation(time_range=(2e-6, 3e-6), text="Data packet"))
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- from dataclasses import dataclass, field
16
- from datetime import datetime
17
- from enum import Enum
18
- from typing import Any
19
-
20
-
21
- class AnnotationType(Enum):
22
- """Types of annotations."""
23
-
24
- POINT = "point" # Single time point
25
- RANGE = "range" # Time range
26
- VERTICAL = "vertical" # Vertical line
27
- HORIZONTAL = "horizontal" # Horizontal line
28
- REGION = "region" # 2D region (time + amplitude)
29
- TEXT = "text" # Free-floating text
30
-
31
-
32
- @dataclass
33
- class Annotation:
34
- """Single annotation on a trace.
35
-
36
- Attributes:
37
- text: Annotation text/label
38
- time: Time point (for point annotations)
39
- time_range: (start, end) time range
40
- amplitude: Amplitude value (for horizontal lines)
41
- amplitude_range: (min, max) amplitude range
42
- annotation_type: Type of annotation
43
- color: Display color (hex or name)
44
- style: Line style ('solid', 'dashed', 'dotted')
45
- visible: Whether annotation is visible
46
- created_at: Creation timestamp
47
- metadata: Additional metadata
48
- """
49
-
50
- text: str
51
- time: float | None = None
52
- time_range: tuple[float, float] | None = None
53
- amplitude: float | None = None
54
- amplitude_range: tuple[float, float] | None = None
55
- annotation_type: AnnotationType = AnnotationType.POINT
56
- color: str = "#FF6B6B"
57
- style: str = "solid"
58
- visible: bool = True
59
- created_at: datetime = field(default_factory=datetime.now)
60
- metadata: dict[str, Any] = field(default_factory=dict)
61
-
62
- def __post_init__(self) -> None:
63
- """Infer annotation type from provided parameters."""
64
- if self.annotation_type == AnnotationType.POINT:
65
- if self.time_range is not None:
66
- self.annotation_type = AnnotationType.RANGE
67
- elif self.amplitude is not None and self.time is None:
68
- self.annotation_type = AnnotationType.HORIZONTAL
69
- elif self.amplitude_range is not None and self.time_range is not None:
70
- self.annotation_type = AnnotationType.REGION # type: ignore[unreachable]
71
-
72
- @property
73
- def start_time(self) -> float | None:
74
- """Get start time for range annotations."""
75
- if self.time_range:
76
- return self.time_range[0]
77
- return self.time
78
-
79
- @property
80
- def end_time(self) -> float | None:
81
- """Get end time for range annotations."""
82
- if self.time_range:
83
- return self.time_range[1]
84
- return self.time
85
-
86
- def to_dict(self) -> dict[str, Any]:
87
- """Convert to dictionary for serialization."""
88
- return {
89
- "text": self.text,
90
- "time": self.time,
91
- "time_range": self.time_range,
92
- "amplitude": self.amplitude,
93
- "amplitude_range": self.amplitude_range,
94
- "annotation_type": self.annotation_type.value,
95
- "color": self.color,
96
- "style": self.style,
97
- "visible": self.visible,
98
- "created_at": self.created_at.isoformat(),
99
- "metadata": self.metadata,
100
- }
101
-
102
- @classmethod
103
- def from_dict(cls, data: dict[str, Any]) -> Annotation:
104
- """Create from dictionary."""
105
- data = data.copy()
106
- data["annotation_type"] = AnnotationType(data.get("annotation_type", "point"))
107
- if "created_at" in data and isinstance(data["created_at"], str):
108
- data["created_at"] = datetime.fromisoformat(data["created_at"])
109
- return cls(**data)
110
-
111
-
112
- @dataclass
113
- class AnnotationLayer:
114
- """Collection of related annotations.
115
-
116
- Attributes:
117
- name: Layer name
118
- annotations: List of annotations
119
- visible: Whether layer is visible
120
- locked: Whether layer is locked (read-only)
121
- color: Default color for new annotations
122
- description: Layer description
123
- """
124
-
125
- name: str
126
- annotations: list[Annotation] = field(default_factory=list)
127
- visible: bool = True
128
- locked: bool = False
129
- color: str = "#FF6B6B"
130
- description: str = ""
131
-
132
- def add(
133
- self,
134
- annotation: Annotation | None = None,
135
- *,
136
- text: str = "",
137
- time: float | None = None,
138
- time_range: tuple[float, float] | None = None,
139
- **kwargs: Any,
140
- ) -> Annotation:
141
- """Add annotation to layer.
142
-
143
- Args:
144
- annotation: Pre-built Annotation object.
145
- text: Annotation text (if not using pre-built).
146
- time: Time point.
147
- time_range: Time range.
148
- **kwargs: Additional Annotation parameters.
149
-
150
- Returns:
151
- Added annotation.
152
-
153
- Raises:
154
- ValueError: If layer is locked.
155
- """
156
- if self.locked:
157
- raise ValueError(f"Layer '{self.name}' is locked")
158
-
159
- if annotation is None:
160
- annotation = Annotation(
161
- text=text,
162
- time=time,
163
- time_range=time_range,
164
- color=kwargs.pop("color", self.color),
165
- **kwargs,
166
- )
167
-
168
- self.annotations.append(annotation)
169
- return annotation
170
-
171
- def remove(self, annotation: Annotation) -> bool:
172
- """Remove annotation from layer.
173
-
174
- Args:
175
- annotation: Annotation to remove.
176
-
177
- Returns:
178
- True if removed, False if not found.
179
-
180
- Raises:
181
- ValueError: If layer is locked.
182
- """
183
- if self.locked:
184
- raise ValueError(f"Layer '{self.name}' is locked")
185
-
186
- try:
187
- self.annotations.remove(annotation)
188
- return True
189
- except ValueError:
190
- return False
191
-
192
- def find_at_time(
193
- self,
194
- time: float,
195
- tolerance: float = 0.0,
196
- ) -> list[Annotation]:
197
- """Find annotations at or near a specific time.
198
-
199
- Args:
200
- time: Time to search.
201
- tolerance: Time tolerance for matching.
202
-
203
- Returns:
204
- List of matching annotations.
205
- """
206
- matches = []
207
- for ann in self.annotations:
208
- if ann.time is not None:
209
- if abs(ann.time - time) <= tolerance:
210
- matches.append(ann)
211
- elif ann.time_range is not None and (
212
- ann.time_range[0] - tolerance <= time <= ann.time_range[1] + tolerance
213
- ):
214
- matches.append(ann)
215
- return matches
216
-
217
- def find_in_range(
218
- self,
219
- start_time: float,
220
- end_time: float,
221
- ) -> list[Annotation]:
222
- """Find annotations within a time range.
223
-
224
- Args:
225
- start_time: Range start.
226
- end_time: Range end.
227
-
228
- Returns:
229
- List of annotations within range.
230
- """
231
- matches = []
232
- for ann in self.annotations:
233
- ann_start = ann.start_time
234
- ann_end = ann.end_time
235
-
236
- if ann_start is not None and (
237
- start_time <= ann_start <= end_time
238
- or (ann_end is not None and ann_start <= end_time and ann_end >= start_time)
239
- ):
240
- matches.append(ann)
241
-
242
- return matches
243
-
244
- def clear(self) -> int:
245
- """Remove all annotations.
246
-
247
- Returns:
248
- Number of annotations removed.
249
-
250
- Raises:
251
- ValueError: If layer is locked.
252
- """
253
- if self.locked:
254
- raise ValueError(f"Layer '{self.name}' is locked")
255
-
256
- count = len(self.annotations)
257
- self.annotations.clear()
258
- return count
259
-
260
- def to_dict(self) -> dict[str, Any]:
261
- """Convert to dictionary for serialization."""
262
- return {
263
- "name": self.name,
264
- "annotations": [a.to_dict() for a in self.annotations],
265
- "visible": self.visible,
266
- "locked": self.locked,
267
- "color": self.color,
268
- "description": self.description,
269
- }
270
-
271
- @classmethod
272
- def from_dict(cls, data: dict[str, Any]) -> AnnotationLayer:
273
- """Create from dictionary."""
274
- annotations = [Annotation.from_dict(a) for a in data.get("annotations", [])]
275
- return cls(
276
- name=data["name"],
277
- annotations=annotations,
278
- visible=data.get("visible", True),
279
- locked=data.get("locked", False),
280
- color=data.get("color", "#FF6B6B"),
281
- description=data.get("description", ""),
282
- )
283
-
284
-
285
- __all__ = [
286
- "Annotation",
287
- "AnnotationLayer",
288
- "AnnotationType",
289
- ]
oscura/session/history.py DELETED
@@ -1,313 +0,0 @@
1
- """Operation history tracking.
2
-
3
- This module provides operation history tracking for analysis sessions.
4
-
5
-
6
- Example:
7
- >>> history = OperationHistory()
8
- >>> history.record('load', {'file': 'capture.wfm'})
9
- >>> history.record('measure_rise_time', {'result': 1.5e-9})
10
- >>> print(history.to_script())
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- from dataclasses import dataclass, field
16
- from datetime import datetime
17
- from typing import Any
18
-
19
-
20
- @dataclass
21
- class HistoryEntry:
22
- """Single history entry recording an operation.
23
-
24
- Attributes:
25
- operation: Operation name (function/method called)
26
- parameters: Input parameters
27
- result: Operation result (summary)
28
- timestamp: When operation was performed
29
- duration_ms: Operation duration in milliseconds
30
- success: Whether operation succeeded
31
- error_message: Error message if failed
32
- metadata: Additional metadata
33
- """
34
-
35
- operation: str
36
- parameters: dict[str, Any] = field(default_factory=dict)
37
- result: Any = None
38
- timestamp: datetime = field(default_factory=datetime.now)
39
- duration_ms: float = 0.0
40
- success: bool = True
41
- error_message: str | None = None
42
- metadata: dict[str, Any] = field(default_factory=dict)
43
-
44
- def to_dict(self) -> dict[str, Any]:
45
- """Convert to dictionary for serialization."""
46
- return {
47
- "operation": self.operation,
48
- "parameters": self.parameters,
49
- "result": self._serialize_result(self.result),
50
- "timestamp": self.timestamp.isoformat(),
51
- "duration_ms": self.duration_ms,
52
- "success": self.success,
53
- "error_message": self.error_message,
54
- "metadata": self.metadata,
55
- }
56
-
57
- @staticmethod
58
- def _serialize_result(result: Any) -> Any:
59
- """Serialize result for JSON storage."""
60
- if result is None:
61
- return None
62
- if isinstance(result, str | int | float | bool):
63
- return result
64
- if isinstance(result, dict):
65
- return {k: HistoryEntry._serialize_result(v) for k, v in result.items()}
66
- if isinstance(result, list | tuple):
67
- return [HistoryEntry._serialize_result(v) for v in result]
68
- # For complex objects, store string representation
69
- return str(result)
70
-
71
- @classmethod
72
- def from_dict(cls, data: dict[str, Any]) -> HistoryEntry:
73
- """Create from dictionary."""
74
- data = data.copy()
75
- if "timestamp" in data and isinstance(data["timestamp"], str):
76
- data["timestamp"] = datetime.fromisoformat(data["timestamp"])
77
- return cls(**data)
78
-
79
- def to_code(self) -> str:
80
- """Generate Python code to replay this operation.
81
-
82
- Returns:
83
- Python code string.
84
- """
85
- # Format parameters
86
- params = []
87
- for k, v in self.parameters.items():
88
- if isinstance(v, str):
89
- params.append(f'{k}="{v}"')
90
- else:
91
- params.append(f"{k}={v!r}")
92
-
93
- param_str = ", ".join(params)
94
- return f"osc.{self.operation}({param_str})"
95
-
96
-
97
- @dataclass
98
- class OperationHistory:
99
- """History of analysis operations.
100
-
101
- Supports recording, replaying, and exporting operation history.
102
-
103
- Attributes:
104
- entries: List of history entries
105
- max_entries: Maximum entries to keep (0 = unlimited)
106
- auto_record: Whether to automatically record operations
107
- """
108
-
109
- entries: list[HistoryEntry] = field(default_factory=list)
110
- max_entries: int = 0
111
- auto_record: bool = True
112
- _current_session_start: datetime = field(default_factory=datetime.now)
113
-
114
- def record(
115
- self,
116
- operation: str,
117
- parameters: dict[str, Any] | None = None,
118
- result: Any = None,
119
- duration_ms: float = 0.0,
120
- success: bool = True,
121
- error_message: str | None = None,
122
- **metadata: Any,
123
- ) -> HistoryEntry:
124
- """Record an operation.
125
-
126
- Args:
127
- operation: Operation name.
128
- parameters: Input parameters.
129
- result: Operation result.
130
- duration_ms: Duration in milliseconds.
131
- success: Whether operation succeeded.
132
- error_message: Error message if failed.
133
- **metadata: Additional metadata.
134
-
135
- Returns:
136
- Created history entry.
137
- """
138
- entry = HistoryEntry(
139
- operation=operation,
140
- parameters=parameters or {},
141
- result=result,
142
- duration_ms=duration_ms,
143
- success=success,
144
- error_message=error_message,
145
- metadata=metadata,
146
- )
147
-
148
- self.entries.append(entry)
149
-
150
- # Trim if exceeded max entries
151
- if self.max_entries > 0 and len(self.entries) > self.max_entries:
152
- self.entries = self.entries[-self.max_entries :]
153
-
154
- return entry
155
-
156
- def undo(self) -> HistoryEntry | None:
157
- """Remove and return the last entry.
158
-
159
- Returns:
160
- Removed entry, or None if empty.
161
- """
162
- if self.entries:
163
- return self.entries.pop()
164
- return None
165
-
166
- def clear(self) -> int:
167
- """Clear all history.
168
-
169
- Returns:
170
- Number of entries cleared.
171
- """
172
- count = len(self.entries)
173
- self.entries.clear()
174
- return count
175
-
176
- def find(
177
- self,
178
- operation: str | None = None,
179
- success_only: bool = False,
180
- since: datetime | None = None,
181
- ) -> list[HistoryEntry]:
182
- """Find entries matching criteria.
183
-
184
- Args:
185
- operation: Filter by operation name.
186
- success_only: Only return successful operations.
187
- since: Only return entries after this time.
188
-
189
- Returns:
190
- Matching entries.
191
- """
192
- results = []
193
- for entry in self.entries:
194
- if operation and entry.operation != operation:
195
- continue
196
- if success_only and not entry.success:
197
- continue
198
- if since and entry.timestamp < since:
199
- continue
200
- results.append(entry)
201
- return results
202
-
203
- def to_script(
204
- self,
205
- include_imports: bool = True,
206
- include_comments: bool = True,
207
- ) -> str:
208
- """Export history as Python script.
209
-
210
- Args:
211
- include_imports: Include import statements.
212
- include_comments: Include timestamp comments.
213
-
214
- Returns:
215
- Python script string.
216
-
217
- Example:
218
- >>> script = history.to_script()
219
- >>> print(script)
220
- # Generated by Oscura
221
- import oscura as osc
222
- osc.load("capture.wfm")
223
- result = osc.measure_rise_time()
224
- """
225
- lines = []
226
-
227
- if include_imports:
228
- lines.extend(
229
- [
230
- "#!/usr/bin/env python3",
231
- '"""Oscura analysis script.',
232
- "",
233
- f"Generated: {datetime.now().isoformat()}",
234
- '"""',
235
- "",
236
- "import oscura as osc",
237
- "",
238
- ]
239
- )
240
-
241
- for entry in self.entries:
242
- if not entry.success:
243
- continue
244
-
245
- if include_comments:
246
- lines.append(f"# {entry.timestamp.strftime('%H:%M:%S')} - {entry.operation}")
247
-
248
- lines.append(entry.to_code())
249
- lines.append("")
250
-
251
- return "\n".join(lines)
252
-
253
- def summary(self) -> dict[str, Any]:
254
- """Get history summary statistics.
255
-
256
- Returns:
257
- Dictionary with summary statistics.
258
- """
259
- if not self.entries:
260
- return {
261
- "total_operations": 0,
262
- "successful": 0,
263
- "failed": 0,
264
- "total_duration_ms": 0,
265
- "unique_operations": 0,
266
- }
267
-
268
- successful = sum(1 for e in self.entries if e.success)
269
- failed = len(self.entries) - successful
270
- total_duration = sum(e.duration_ms for e in self.entries)
271
- unique_ops = len({e.operation for e in self.entries})
272
-
273
- # Operation frequency
274
- op_counts: dict[str, int] = {}
275
- for entry in self.entries:
276
- op_counts[entry.operation] = op_counts.get(entry.operation, 0) + 1
277
-
278
- return {
279
- "total_operations": len(self.entries),
280
- "successful": successful,
281
- "failed": failed,
282
- "total_duration_ms": total_duration,
283
- "unique_operations": unique_ops,
284
- "operation_counts": op_counts,
285
- "session_start": self._current_session_start.isoformat(),
286
- "last_operation": self.entries[-1].timestamp.isoformat() if self.entries else None,
287
- }
288
-
289
- def to_dict(self) -> dict[str, Any]:
290
- """Convert to dictionary for serialization."""
291
- return {
292
- "entries": [e.to_dict() for e in self.entries],
293
- "max_entries": self.max_entries,
294
- "session_start": self._current_session_start.isoformat(),
295
- }
296
-
297
- @classmethod
298
- def from_dict(cls, data: dict[str, Any]) -> OperationHistory:
299
- """Create from dictionary."""
300
- entries = [HistoryEntry.from_dict(e) for e in data.get("entries", [])]
301
- history = cls(
302
- entries=entries,
303
- max_entries=data.get("max_entries", 0),
304
- )
305
- if "session_start" in data:
306
- history._current_session_start = datetime.fromisoformat(data["session_start"])
307
- return history
308
-
309
-
310
- __all__ = [
311
- "HistoryEntry",
312
- "OperationHistory",
313
- ]