oscura 0.0.1__py3-none-any.whl → 0.1.1__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 (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,216 @@
1
+ """Correlation ID management for distributed tracing.
2
+
3
+ This module provides correlation ID generation and propagation for
4
+ request tracing across Oscura operations.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.core.correlation import with_correlation_id, get_correlation_id
9
+ >>> @with_correlation_id()
10
+ ... def analyze_trace(data):
11
+ ... corr_id = get_correlation_id()
12
+ ... print(f"Processing with correlation ID: {corr_id}")
13
+
14
+ References:
15
+ - Distributed tracing best practices
16
+ - Thread-local and async-safe context management
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import contextvars
23
+ import functools
24
+ import uuid
25
+ from collections.abc import Callable
26
+ from typing import Any, ParamSpec, TypeVar
27
+
28
+ # Context variable for correlation ID (thread-safe and async-safe)
29
+ _correlation_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
30
+ "correlation_id", default=None
31
+ )
32
+
33
+
34
+ def get_correlation_id() -> str | None:
35
+ """Get the current correlation ID for request tracing.
36
+
37
+ Returns the correlation ID from the current context, or None if
38
+ no correlation context is active.
39
+
40
+ Returns:
41
+ Current correlation ID (UUID string) or None.
42
+
43
+ Example:
44
+ >>> with CorrelationContext():
45
+ ... corr_id = get_correlation_id()
46
+ ... print(f"Correlation ID: {corr_id}")
47
+
48
+ References:
49
+ LOG-004: Correlation ID Injection
50
+ """
51
+ return _correlation_id.get()
52
+
53
+
54
+ def set_correlation_id(corr_id: str) -> None:
55
+ """Set the correlation ID for the current context.
56
+
57
+ Args:
58
+ corr_id: Correlation ID to set (typically a UUID string).
59
+
60
+ Example:
61
+ >>> set_correlation_id("550e8400-e29b-41d4-a716-446655440000")
62
+ >>> print(get_correlation_id())
63
+ 550e8400-e29b-41d4-a716-446655440000
64
+
65
+ References:
66
+ LOG-004: Correlation ID Injection
67
+ """
68
+ _correlation_id.set(corr_id)
69
+
70
+
71
+ class CorrelationContext:
72
+ """Context manager for correlation ID scoping.
73
+
74
+ Automatically generates a UUID correlation ID if not provided,
75
+ and ensures proper cleanup when exiting the context.
76
+
77
+ Thread-safe and async-safe using contextvars.
78
+
79
+ Args:
80
+ corr_id: Optional correlation ID. If None, generates a new UUID.
81
+
82
+ Example:
83
+ >>> # Auto-generate correlation ID
84
+ >>> with CorrelationContext() as corr_id:
85
+ ... print(f"Generated ID: {corr_id}")
86
+ ... # All operations here have this correlation ID
87
+ ... result = some_analysis()
88
+
89
+ >>> # Use explicit correlation ID
90
+ >>> with CorrelationContext("my-custom-id") as corr_id:
91
+ ... print(f"Using ID: {corr_id}")
92
+
93
+ References:
94
+ LOG-004: Correlation ID Injection
95
+ """
96
+
97
+ def __init__(self, corr_id: str | None = None):
98
+ """Initialize correlation context.
99
+
100
+ Args:
101
+ corr_id: Correlation ID to use, or None to auto-generate.
102
+ """
103
+ self.corr_id = corr_id or str(uuid.uuid4())
104
+ self.token: contextvars.Token[str | None] | None = None
105
+
106
+ def __enter__(self) -> str:
107
+ """Enter the correlation context.
108
+
109
+ Returns:
110
+ The correlation ID for this context.
111
+ """
112
+ self.token = _correlation_id.set(self.corr_id)
113
+ return self.corr_id
114
+
115
+ def __exit__(self, *args: Any) -> None:
116
+ """Exit the correlation context and restore previous value."""
117
+ if self.token:
118
+ _correlation_id.reset(self.token)
119
+
120
+ async def __aenter__(self) -> str:
121
+ """Async enter the correlation context.
122
+
123
+ Returns:
124
+ The correlation ID for this context.
125
+ """
126
+ self.token = _correlation_id.set(self.corr_id)
127
+ return self.corr_id
128
+
129
+ async def __aexit__(self, *args: Any) -> None:
130
+ """Async exit the correlation context and restore previous value."""
131
+ if self.token:
132
+ _correlation_id.reset(self.token)
133
+
134
+
135
+ # Type variables for generic decorator typing
136
+ P = ParamSpec("P")
137
+ R = TypeVar("R")
138
+
139
+
140
+ def with_correlation_id(
141
+ corr_id: str | None = None,
142
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
143
+ """Decorator to set correlation ID for a function call.
144
+
145
+ Automatically wraps the function in a CorrelationContext, ensuring
146
+ all operations within the function are traced with the same ID.
147
+
148
+ Supports both synchronous and asynchronous functions.
149
+
150
+ Args:
151
+ corr_id: Correlation ID to use, or None to auto-generate.
152
+
153
+ Returns:
154
+ Decorator function.
155
+
156
+ Example:
157
+ >>> @with_correlation_id()
158
+ ... def analyze_trace(trace_data):
159
+ ... logger.info("Starting analysis")
160
+ ... # All logs will include the correlation ID
161
+ ... result = compute_fft(trace_data)
162
+ ... return result
163
+
164
+ >>> @with_correlation_id("batch-job-123")
165
+ ... def process_batch(files):
166
+ ... for f in files:
167
+ ... load_and_analyze(f)
168
+
169
+ >>> @with_correlation_id("async-task")
170
+ ... async def async_analysis(data):
171
+ ... await some_async_operation()
172
+ ... return get_correlation_id()
173
+
174
+ References:
175
+ LOG-004: Correlation ID Injection
176
+ """
177
+
178
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
179
+ if asyncio.iscoroutinefunction(func):
180
+ # Async function wrapper
181
+ @functools.wraps(func)
182
+ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
183
+ with CorrelationContext(corr_id):
184
+ # func returns a Coroutine, we need to await it
185
+ coro = func(*args, **kwargs)
186
+ # Type assertion for mypy - we know this is a coroutine
187
+ return await coro # type: ignore[misc, no-any-return]
188
+
189
+ return async_wrapper # type: ignore[return-value]
190
+ else:
191
+ # Sync function wrapper
192
+ @functools.wraps(func)
193
+ def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
194
+ with CorrelationContext(corr_id):
195
+ return func(*args, **kwargs)
196
+
197
+ return sync_wrapper # type: ignore[return-value]
198
+
199
+ return decorator
200
+
201
+
202
+ def generate_correlation_id() -> str:
203
+ """Generate a new correlation ID (UUID4).
204
+
205
+ Returns:
206
+ New correlation ID as string.
207
+
208
+ Example:
209
+ >>> corr_id = generate_correlation_id()
210
+ >>> with CorrelationContext(corr_id):
211
+ ... process_data()
212
+
213
+ References:
214
+ LOG-004: Correlation ID Injection
215
+ """
216
+ return str(uuid.uuid4())
@@ -0,0 +1,422 @@
1
+ """Cross-domain correlation for analysis results.
2
+
3
+ Enables results from different analysis domains to inform and validate
4
+ each other, improving overall confidence and detecting inconsistencies.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.core.cross_domain import correlate_results
9
+ >>> from oscura.reporting.config import AnalysisDomain
10
+ >>> results = {
11
+ ... AnalysisDomain.SPECTRAL: {'dominant_frequency': 1000.0},
12
+ ... AnalysisDomain.TIMING: {'period': 0.001}
13
+ ... }
14
+ >>> correlation = correlate_results(results)
15
+ >>> print(f"Coherence: {correlation.overall_coherence:.2f}")
16
+ >>> print(f"Agreements: {correlation.agreements_detected}")
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ from dataclasses import dataclass, field
23
+ from typing import Any
24
+
25
+ import numpy as np
26
+
27
+ from oscura.reporting.config import AnalysisDomain
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @dataclass
33
+ class CrossDomainInsight:
34
+ """An insight derived from cross-domain correlation.
35
+
36
+ Attributes:
37
+ insight_type: Type of insight ("agreement", "conflict", "implication").
38
+ source_domains: Analysis domains that contributed to this insight.
39
+ description: Human-readable description of the insight.
40
+ confidence_impact: How much this affects confidence (-1.0 to +1.0).
41
+ details: Additional details specific to this insight.
42
+ """
43
+
44
+ insight_type: str # "agreement", "conflict", "implication"
45
+ source_domains: list[AnalysisDomain]
46
+ description: str
47
+ confidence_impact: float # How much this affects confidence (-1 to +1)
48
+ details: dict[str, Any] = field(default_factory=dict)
49
+
50
+ def __post_init__(self) -> None:
51
+ """Validate confidence impact after initialization."""
52
+ if not -1.0 <= self.confidence_impact <= 1.0:
53
+ raise ValueError(
54
+ f"Confidence impact must be in [-1.0, 1.0], got {self.confidence_impact}"
55
+ )
56
+
57
+
58
+ @dataclass
59
+ class CorrelationResult:
60
+ """Result of cross-domain correlation analysis.
61
+
62
+ Attributes:
63
+ insights: List of discovered cross-domain insights.
64
+ confidence_adjustments: Per-domain confidence adjustments.
65
+ conflicts_detected: Number of conflicts found.
66
+ agreements_detected: Number of agreements found.
67
+ """
68
+
69
+ insights: list[CrossDomainInsight] = field(default_factory=list)
70
+ confidence_adjustments: dict[str, float] = field(default_factory=dict)
71
+ conflicts_detected: int = 0
72
+ agreements_detected: int = 0
73
+
74
+ @property
75
+ def overall_coherence(self) -> float:
76
+ """Calculate overall coherence score (0-1).
77
+
78
+ Returns:
79
+ Coherence score based on agreement/conflict ratio.
80
+ """
81
+ if not self.insights:
82
+ return 0.5
83
+ total = self.agreements_detected + self.conflicts_detected
84
+ if total == 0:
85
+ return 0.5
86
+ return self.agreements_detected / total
87
+
88
+
89
+ # Define which domains are semantically related
90
+ DOMAIN_AFFINITY: dict[AnalysisDomain, list[AnalysisDomain]] = {
91
+ AnalysisDomain.DIGITAL: [AnalysisDomain.TIMING, AnalysisDomain.PROTOCOLS],
92
+ AnalysisDomain.TIMING: [AnalysisDomain.DIGITAL, AnalysisDomain.JITTER, AnalysisDomain.SPECTRAL],
93
+ AnalysisDomain.SPECTRAL: [
94
+ AnalysisDomain.JITTER,
95
+ AnalysisDomain.STATISTICS,
96
+ AnalysisDomain.TIMING,
97
+ ],
98
+ AnalysisDomain.WAVEFORM: [AnalysisDomain.STATISTICS],
99
+ AnalysisDomain.STATISTICS: [AnalysisDomain.SPECTRAL, AnalysisDomain.WAVEFORM],
100
+ AnalysisDomain.JITTER: [AnalysisDomain.TIMING, AnalysisDomain.EYE, AnalysisDomain.SPECTRAL],
101
+ AnalysisDomain.EYE: [AnalysisDomain.JITTER, AnalysisDomain.SIGNAL_INTEGRITY],
102
+ AnalysisDomain.PATTERNS: [AnalysisDomain.PROTOCOLS, AnalysisDomain.INFERENCE],
103
+ AnalysisDomain.INFERENCE: [AnalysisDomain.PATTERNS, AnalysisDomain.PROTOCOLS],
104
+ AnalysisDomain.PROTOCOLS: [AnalysisDomain.DIGITAL, AnalysisDomain.PATTERNS],
105
+ }
106
+
107
+
108
+ class CrossDomainCorrelator:
109
+ """Correlate results across analysis domains."""
110
+
111
+ def __init__(self, tolerance: float = 0.1):
112
+ """Initialize correlator.
113
+
114
+ Args:
115
+ tolerance: Tolerance for value comparisons (fraction).
116
+ """
117
+ self.tolerance = tolerance
118
+ self._correlation_rules = self._build_correlation_rules()
119
+
120
+ def correlate(
121
+ self,
122
+ results: dict[AnalysisDomain, dict[str, Any]],
123
+ ) -> CorrelationResult:
124
+ """Find correlations between domain results.
125
+
126
+ Args:
127
+ results: Dictionary mapping domains to their results.
128
+
129
+ Returns:
130
+ CorrelationResult with insights and adjustments.
131
+ """
132
+ correlation_result = CorrelationResult()
133
+
134
+ # Only correlate domains that have results
135
+ active_domains = [d for d, r in results.items() if r]
136
+
137
+ # Track checked pairs to avoid duplicates
138
+ checked_pairs: set[tuple[AnalysisDomain, AnalysisDomain]] = set()
139
+
140
+ # Check each pair of related domains
141
+ for domain in active_domains:
142
+ related = DOMAIN_AFFINITY.get(domain, [])
143
+ for related_domain in related:
144
+ if related_domain in active_domains:
145
+ # Create canonical pair ordering to avoid duplicates
146
+ pair_tuple: tuple[AnalysisDomain, AnalysisDomain] = tuple(
147
+ sorted([domain, related_domain], key=lambda d: d.value)
148
+ ) # type: ignore[assignment]
149
+ if pair_tuple not in checked_pairs:
150
+ checked_pairs.add(pair_tuple)
151
+ insights = self._correlate_pair(
152
+ domain, results[domain], related_domain, results[related_domain]
153
+ )
154
+ correlation_result.insights.extend(insights)
155
+
156
+ # Count agreements and conflicts
157
+ for insight in correlation_result.insights:
158
+ if insight.insight_type == "agreement":
159
+ correlation_result.agreements_detected += 1
160
+ elif insight.insight_type == "conflict":
161
+ correlation_result.conflicts_detected += 1
162
+
163
+ # Calculate confidence adjustments
164
+ correlation_result.confidence_adjustments = self._calculate_adjustments(
165
+ correlation_result.insights
166
+ )
167
+
168
+ return correlation_result
169
+
170
+ def _correlate_pair(
171
+ self,
172
+ domain1: AnalysisDomain,
173
+ results1: dict[str, Any],
174
+ domain2: AnalysisDomain,
175
+ results2: dict[str, Any],
176
+ ) -> list[CrossDomainInsight]:
177
+ """Correlate a pair of domains."""
178
+ insights = []
179
+
180
+ # Apply correlation rules
181
+ for rule in self._correlation_rules:
182
+ if rule["domains"] == {domain1, domain2} or rule["domains"] == {domain2, domain1}:
183
+ try:
184
+ insight = rule["check"](results1, results2, domain1, domain2)
185
+ if insight:
186
+ insights.append(insight)
187
+ except Exception as e:
188
+ logger.debug(f"Correlation rule failed: {e}")
189
+
190
+ return insights
191
+
192
+ def _build_correlation_rules(self) -> list[dict[str, Any]]:
193
+ """Build correlation rules for domain pairs."""
194
+ return [
195
+ {
196
+ "domains": {AnalysisDomain.SPECTRAL, AnalysisDomain.TIMING},
197
+ "check": self._check_frequency_timing_agreement,
198
+ },
199
+ {
200
+ "domains": {AnalysisDomain.DIGITAL, AnalysisDomain.TIMING},
201
+ "check": self._check_digital_timing_consistency,
202
+ },
203
+ {
204
+ "domains": {AnalysisDomain.JITTER, AnalysisDomain.EYE},
205
+ "check": self._check_jitter_eye_correlation,
206
+ },
207
+ {
208
+ "domains": {AnalysisDomain.WAVEFORM, AnalysisDomain.STATISTICS},
209
+ "check": self._check_waveform_stats_consistency,
210
+ },
211
+ ]
212
+
213
+ def _check_frequency_timing_agreement(
214
+ self,
215
+ results1: dict[str, Any],
216
+ results2: dict[str, Any],
217
+ domain1: AnalysisDomain,
218
+ domain2: AnalysisDomain,
219
+ ) -> CrossDomainInsight | None:
220
+ """Check if spectral frequency matches timing period."""
221
+ # Extract frequency from spectral results
222
+ spectral_freq = self._extract_value(
223
+ results1 if domain1 == AnalysisDomain.SPECTRAL else results2,
224
+ ["dominant_frequency", "peak_frequency", "fundamental"],
225
+ )
226
+
227
+ # Extract period from timing results
228
+ timing_period = self._extract_value(
229
+ results2 if domain2 == AnalysisDomain.TIMING else results1,
230
+ ["period", "avg_period", "mean_period"],
231
+ )
232
+
233
+ if spectral_freq and timing_period and spectral_freq > 0 and timing_period > 0:
234
+ expected_period = 1.0 / spectral_freq
235
+ ratio = timing_period / expected_period
236
+
237
+ if 0.9 < ratio < 1.1: # Within 10%
238
+ return CrossDomainInsight(
239
+ insight_type="agreement",
240
+ source_domains=[AnalysisDomain.SPECTRAL, AnalysisDomain.TIMING],
241
+ description=(
242
+ f"Spectral frequency ({spectral_freq:.1f} Hz) matches "
243
+ f"timing period ({timing_period:.3e} s)"
244
+ ),
245
+ confidence_impact=0.15,
246
+ details={
247
+ "spectral_freq": spectral_freq,
248
+ "timing_period": timing_period,
249
+ "ratio": ratio,
250
+ },
251
+ )
252
+ elif ratio < 0.5 or ratio > 2.0:
253
+ return CrossDomainInsight(
254
+ insight_type="conflict",
255
+ source_domains=[AnalysisDomain.SPECTRAL, AnalysisDomain.TIMING],
256
+ description=(
257
+ f"Spectral frequency ({spectral_freq:.1f} Hz) conflicts with "
258
+ f"timing period ({timing_period:.3e} s)"
259
+ ),
260
+ confidence_impact=-0.2,
261
+ details={
262
+ "spectral_freq": spectral_freq,
263
+ "timing_period": timing_period,
264
+ "ratio": ratio,
265
+ },
266
+ )
267
+
268
+ return None
269
+
270
+ def _check_digital_timing_consistency(
271
+ self,
272
+ results1: dict[str, Any],
273
+ results2: dict[str, Any],
274
+ domain1: AnalysisDomain,
275
+ domain2: AnalysisDomain,
276
+ ) -> CrossDomainInsight | None:
277
+ """Check if digital edge count matches timing analysis."""
278
+ digital_results = results1 if domain1 == AnalysisDomain.DIGITAL else results2
279
+ timing_results = results2 if domain2 == AnalysisDomain.TIMING else results1
280
+
281
+ edge_count = self._extract_value(
282
+ digital_results, ["edge_count", "num_edges", "transitions"]
283
+ )
284
+ timing_edges = self._extract_value(timing_results, ["edge_count", "transitions_detected"])
285
+
286
+ if edge_count and timing_edges:
287
+ if abs(edge_count - timing_edges) <= 2:
288
+ return CrossDomainInsight(
289
+ insight_type="agreement",
290
+ source_domains=[AnalysisDomain.DIGITAL, AnalysisDomain.TIMING],
291
+ description=(f"Edge counts agree: Digital={edge_count}, Timing={timing_edges}"),
292
+ confidence_impact=0.1,
293
+ )
294
+
295
+ return None
296
+
297
+ def _check_jitter_eye_correlation(
298
+ self,
299
+ results1: dict[str, Any],
300
+ results2: dict[str, Any],
301
+ domain1: AnalysisDomain,
302
+ domain2: AnalysisDomain,
303
+ ) -> CrossDomainInsight | None:
304
+ """Check jitter vs eye diagram correlation."""
305
+ jitter_results = results1 if domain1 == AnalysisDomain.JITTER else results2
306
+ eye_results = results2 if domain2 == AnalysisDomain.EYE else results1
307
+
308
+ total_jitter = self._extract_value(jitter_results, ["total_jitter", "tj", "jitter_pp"])
309
+ eye_width = self._extract_value(eye_results, ["eye_width", "horizontal_opening"])
310
+
311
+ # High jitter should correlate with narrow eye
312
+ if total_jitter is not None and eye_width is not None:
313
+ return CrossDomainInsight(
314
+ insight_type="implication",
315
+ source_domains=[AnalysisDomain.JITTER, AnalysisDomain.EYE],
316
+ description=(f"Jitter ({total_jitter:.2e}) affects eye width ({eye_width:.2e})"),
317
+ confidence_impact=0.05,
318
+ details={"jitter": total_jitter, "eye_width": eye_width},
319
+ )
320
+
321
+ return None
322
+
323
+ def _check_waveform_stats_consistency(
324
+ self,
325
+ results1: dict[str, Any],
326
+ results2: dict[str, Any],
327
+ domain1: AnalysisDomain,
328
+ domain2: AnalysisDomain,
329
+ ) -> CrossDomainInsight | None:
330
+ """Check waveform measurements vs statistical analysis."""
331
+ waveform_results = results1 if domain1 == AnalysisDomain.WAVEFORM else results2
332
+ stats_results = results2 if domain2 == AnalysisDomain.STATISTICS else results1
333
+
334
+ wf_amplitude = self._extract_value(waveform_results, ["amplitude", "vpp", "peak_to_peak"])
335
+ stats_std = self._extract_value(stats_results, ["std", "standard_deviation", "stdev"])
336
+
337
+ if wf_amplitude and stats_std:
338
+ # For periodic signals, amplitude ~ 2.83 * std (for sine wave)
339
+ expected_ratio = wf_amplitude / (2.83 * stats_std) if stats_std > 0 else 0
340
+
341
+ if 0.8 < expected_ratio < 1.2:
342
+ return CrossDomainInsight(
343
+ insight_type="agreement",
344
+ source_domains=[AnalysisDomain.WAVEFORM, AnalysisDomain.STATISTICS],
345
+ description="Waveform amplitude consistent with statistical std dev",
346
+ confidence_impact=0.1,
347
+ )
348
+
349
+ return None
350
+
351
+ def _extract_value(
352
+ self,
353
+ results: dict[str, Any],
354
+ keys: list[str],
355
+ ) -> float | None:
356
+ """Extract a value from results using multiple possible keys."""
357
+ for key in keys:
358
+ # Try direct key
359
+ if key in results:
360
+ val = results[key]
361
+ if isinstance(val, int | float) and not np.isnan(val):
362
+ return float(val)
363
+
364
+ # Try nested keys
365
+ for result_val in results.values():
366
+ if isinstance(result_val, dict) and key in result_val:
367
+ val = result_val[key]
368
+ if isinstance(val, int | float) and not np.isnan(val):
369
+ return float(val)
370
+
371
+ return None
372
+
373
+ def _calculate_adjustments(
374
+ self,
375
+ insights: list[CrossDomainInsight],
376
+ ) -> dict[str, float]:
377
+ """Calculate confidence adjustments based on insights."""
378
+ adjustments: dict[str, float] = {}
379
+
380
+ for insight in insights:
381
+ for domain in insight.source_domains:
382
+ domain_key = domain.value
383
+ current = adjustments.get(domain_key, 0.0)
384
+ adjustments[domain_key] = current + insight.confidence_impact
385
+
386
+ # Clamp adjustments to [-0.3, +0.3]
387
+ return {k: max(-0.3, min(0.3, v)) for k, v in adjustments.items()}
388
+
389
+
390
+ def correlate_results(
391
+ results: dict[AnalysisDomain, dict[str, Any]],
392
+ tolerance: float = 0.1,
393
+ ) -> CorrelationResult:
394
+ """Convenience function to correlate domain results.
395
+
396
+ Args:
397
+ results: Dictionary mapping domains to their results.
398
+ tolerance: Tolerance for value comparisons.
399
+
400
+ Returns:
401
+ CorrelationResult with insights.
402
+
403
+ Example:
404
+ >>> from oscura.reporting.config import AnalysisDomain
405
+ >>> results = {
406
+ ... AnalysisDomain.SPECTRAL: {'dominant_frequency': 1000.0},
407
+ ... AnalysisDomain.TIMING: {'period': 0.001}
408
+ ... }
409
+ >>> correlation = correlate_results(results)
410
+ >>> print(f"Insights: {len(correlation.insights)}")
411
+ """
412
+ correlator = CrossDomainCorrelator(tolerance)
413
+ return correlator.correlate(results)
414
+
415
+
416
+ __all__ = [
417
+ "DOMAIN_AFFINITY",
418
+ "CorrelationResult",
419
+ "CrossDomainCorrelator",
420
+ "CrossDomainInsight",
421
+ "correlate_results",
422
+ ]