oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Verbosity level control for reports.
|
|
2
|
+
|
|
3
|
+
Configurable report detail level for different audiences with 5 distinct
|
|
4
|
+
verbosity levels from executive to debug.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
References:
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from oscura.reporting.core import Report
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VerbosityLevel(Enum):
|
|
21
|
+
"""Verbosity levels for report detail.
|
|
22
|
+
|
|
23
|
+
- EXECUTIVE: 1 page, pass/fail + key findings only
|
|
24
|
+
- SUMMARY: 2-5 pages, results + brief context, no raw data
|
|
25
|
+
- STANDARD: 5-20 pages, full results + methodology + plots
|
|
26
|
+
- DETAILED: 20-50 pages, all measurements + intermediate results
|
|
27
|
+
- DEBUG: 50+ pages, raw data + traces + full provenance
|
|
28
|
+
|
|
29
|
+
References:
|
|
30
|
+
REPORT-009: Verbosity Level Control
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
EXECUTIVE = "executive"
|
|
34
|
+
SUMMARY = "summary"
|
|
35
|
+
STANDARD = "standard"
|
|
36
|
+
DETAILED = "detailed"
|
|
37
|
+
DEBUG = "debug"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class VerbosityController:
|
|
42
|
+
"""Verbosity level controller.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
level: Current verbosity level.
|
|
46
|
+
|
|
47
|
+
References:
|
|
48
|
+
REPORT-009: Verbosity Level Control
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
level: VerbosityLevel = VerbosityLevel.STANDARD
|
|
52
|
+
|
|
53
|
+
def should_include_section(self, section_name: str) -> bool:
|
|
54
|
+
"""Determine if section should be included at current verbosity.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
section_name: Name of section to check.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if section should be included.
|
|
61
|
+
|
|
62
|
+
References:
|
|
63
|
+
REPORT-009: Verbosity Level Control
|
|
64
|
+
"""
|
|
65
|
+
# Define sections for each level
|
|
66
|
+
sections_by_level = {
|
|
67
|
+
VerbosityLevel.EXECUTIVE: {"executive_summary", "key_findings"},
|
|
68
|
+
VerbosityLevel.SUMMARY: {"summary", "results", "key_plots"},
|
|
69
|
+
VerbosityLevel.STANDARD: {
|
|
70
|
+
"summary",
|
|
71
|
+
"results",
|
|
72
|
+
"methodology",
|
|
73
|
+
"plots",
|
|
74
|
+
"tables",
|
|
75
|
+
},
|
|
76
|
+
VerbosityLevel.DETAILED: {
|
|
77
|
+
"summary",
|
|
78
|
+
"results",
|
|
79
|
+
"methodology",
|
|
80
|
+
"plots",
|
|
81
|
+
"tables",
|
|
82
|
+
"measurements",
|
|
83
|
+
"intermediate_results",
|
|
84
|
+
},
|
|
85
|
+
VerbosityLevel.DEBUG: {
|
|
86
|
+
"summary",
|
|
87
|
+
"results",
|
|
88
|
+
"methodology",
|
|
89
|
+
"plots",
|
|
90
|
+
"tables",
|
|
91
|
+
"measurements",
|
|
92
|
+
"intermediate_results",
|
|
93
|
+
"raw_data",
|
|
94
|
+
"logs",
|
|
95
|
+
"provenance",
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
allowed_sections = sections_by_level.get(self.level, set())
|
|
100
|
+
return section_name in allowed_sections
|
|
101
|
+
|
|
102
|
+
def get_max_pages(self) -> int:
|
|
103
|
+
"""Get maximum pages for current verbosity level.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Maximum page count.
|
|
107
|
+
|
|
108
|
+
References:
|
|
109
|
+
REPORT-009: Verbosity Level Control
|
|
110
|
+
"""
|
|
111
|
+
max_pages = {
|
|
112
|
+
VerbosityLevel.EXECUTIVE: 1,
|
|
113
|
+
VerbosityLevel.SUMMARY: 5,
|
|
114
|
+
VerbosityLevel.STANDARD: 20,
|
|
115
|
+
VerbosityLevel.DETAILED: 50,
|
|
116
|
+
VerbosityLevel.DEBUG: 999,
|
|
117
|
+
}
|
|
118
|
+
return max_pages.get(self.level, 20)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def apply_verbosity_level(
|
|
122
|
+
report: Report,
|
|
123
|
+
level: VerbosityLevel | str,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Apply verbosity level to report.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
report: Report object to modify.
|
|
129
|
+
level: Verbosity level to apply.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
>>> from oscura.reporting.core import Report, ReportConfig
|
|
133
|
+
>>> report = Report(config=ReportConfig())
|
|
134
|
+
>>> apply_verbosity_level(report, "summary")
|
|
135
|
+
|
|
136
|
+
References:
|
|
137
|
+
REPORT-009: Verbosity Level Control
|
|
138
|
+
"""
|
|
139
|
+
if isinstance(level, str):
|
|
140
|
+
level = VerbosityLevel(level.lower())
|
|
141
|
+
|
|
142
|
+
controller = VerbosityController(level=level)
|
|
143
|
+
|
|
144
|
+
# Filter sections based on verbosity
|
|
145
|
+
if hasattr(report, "sections"):
|
|
146
|
+
visible_sections = []
|
|
147
|
+
for section in report.sections:
|
|
148
|
+
if controller.should_include_section(section.title.lower()):
|
|
149
|
+
visible_sections.append(section)
|
|
150
|
+
|
|
151
|
+
report.sections = visible_sections
|
|
152
|
+
|
|
153
|
+
# Update config
|
|
154
|
+
if hasattr(report, "config"):
|
|
155
|
+
report.config.verbosity = level.value
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = [
|
|
159
|
+
"VerbosityController",
|
|
160
|
+
"VerbosityLevel",
|
|
161
|
+
"apply_verbosity_level",
|
|
162
|
+
]
|
oscura/reporting/core.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Core report generation for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides the main report generation functionality including
|
|
4
|
+
report structure, configuration, and output generation.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.reporting import generate_report
|
|
9
|
+
>>> report = generate_report(results, "report.pdf", verbosity="summary")
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from numpy.typing import NDArray
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Section:
|
|
25
|
+
"""A section in a report.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
title: Section title.
|
|
29
|
+
content: Section content (text, tables, figures).
|
|
30
|
+
level: Heading level (1-4).
|
|
31
|
+
collapsible: Whether section is collapsible in HTML output.
|
|
32
|
+
visible: Whether section is visible in output.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
title: str
|
|
36
|
+
content: str | list[Any] = ""
|
|
37
|
+
level: int = 2
|
|
38
|
+
collapsible: bool = False
|
|
39
|
+
visible: bool = True
|
|
40
|
+
subsections: list[Section] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ReportConfig:
|
|
45
|
+
"""Report generation configuration.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
title: Report title.
|
|
49
|
+
author: Report author.
|
|
50
|
+
verbosity: Detail level (executive, summary, standard, detailed, debug).
|
|
51
|
+
format: Output format (pdf, html, markdown, docx).
|
|
52
|
+
template: Template name or path.
|
|
53
|
+
page_size: Page size (letter, A4).
|
|
54
|
+
margins: Page margins in inches.
|
|
55
|
+
logo_path: Path to logo image.
|
|
56
|
+
watermark: Watermark text.
|
|
57
|
+
show_toc: Include table of contents.
|
|
58
|
+
show_page_numbers: Include page numbers.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
title: str = "TraceKit Analysis Report"
|
|
62
|
+
author: str = ""
|
|
63
|
+
verbosity: Literal["executive", "summary", "standard", "detailed", "debug"] = "standard"
|
|
64
|
+
format: Literal["pdf", "html", "markdown", "docx"] = "pdf"
|
|
65
|
+
template: str = "default"
|
|
66
|
+
page_size: Literal["letter", "A4"] = "letter"
|
|
67
|
+
margins: float = 1.0
|
|
68
|
+
logo_path: str | None = None
|
|
69
|
+
watermark: str | None = None
|
|
70
|
+
show_toc: bool = True
|
|
71
|
+
show_page_numbers: bool = True
|
|
72
|
+
created: datetime = field(default_factory=datetime.now)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Report:
|
|
77
|
+
"""A generated report.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
config: Report configuration.
|
|
81
|
+
sections: Report sections.
|
|
82
|
+
metadata: Report metadata.
|
|
83
|
+
figures: Embedded figures.
|
|
84
|
+
tables: Embedded tables.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
config: ReportConfig
|
|
88
|
+
sections: list[Section] = field(default_factory=list)
|
|
89
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
90
|
+
figures: list[Any] = field(default_factory=list)
|
|
91
|
+
tables: list[Any] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
def add_section(
|
|
94
|
+
self,
|
|
95
|
+
title: str,
|
|
96
|
+
content: str | list[Any] = "",
|
|
97
|
+
level: int = 2,
|
|
98
|
+
**kwargs: Any,
|
|
99
|
+
) -> Section:
|
|
100
|
+
"""Add a section to the report.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
title: Section title.
|
|
104
|
+
content: Section content.
|
|
105
|
+
level: Heading level.
|
|
106
|
+
**kwargs: Additional section options.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
The created Section.
|
|
110
|
+
"""
|
|
111
|
+
section = Section(title=title, content=content, level=level, **kwargs)
|
|
112
|
+
self.sections.append(section)
|
|
113
|
+
return section
|
|
114
|
+
|
|
115
|
+
def add_table(
|
|
116
|
+
self,
|
|
117
|
+
data: list[list[Any]] | NDArray[Any],
|
|
118
|
+
headers: list[str] | None = None,
|
|
119
|
+
caption: str = "",
|
|
120
|
+
) -> dict: # type: ignore[type-arg]
|
|
121
|
+
"""Add a table to the report.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
data: Table data as 2D list or array.
|
|
125
|
+
headers: Column headers.
|
|
126
|
+
caption: Table caption.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Table reference dictionary.
|
|
130
|
+
"""
|
|
131
|
+
table = {
|
|
132
|
+
"type": "table",
|
|
133
|
+
"data": data if isinstance(data, list) else data.tolist(),
|
|
134
|
+
"headers": headers,
|
|
135
|
+
"caption": caption,
|
|
136
|
+
"id": len(self.tables),
|
|
137
|
+
}
|
|
138
|
+
self.tables.append(table)
|
|
139
|
+
return table
|
|
140
|
+
|
|
141
|
+
def add_figure(
|
|
142
|
+
self,
|
|
143
|
+
figure: Any,
|
|
144
|
+
caption: str = "",
|
|
145
|
+
width: str = "100%",
|
|
146
|
+
) -> dict: # type: ignore[type-arg]
|
|
147
|
+
"""Add a figure to the report.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
figure: Matplotlib figure or image path.
|
|
151
|
+
caption: Figure caption.
|
|
152
|
+
width: Figure width.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Figure reference dictionary.
|
|
156
|
+
"""
|
|
157
|
+
fig = {
|
|
158
|
+
"type": "figure",
|
|
159
|
+
"figure": figure,
|
|
160
|
+
"caption": caption,
|
|
161
|
+
"width": width,
|
|
162
|
+
"id": len(self.figures),
|
|
163
|
+
}
|
|
164
|
+
self.figures.append(fig)
|
|
165
|
+
return fig
|
|
166
|
+
|
|
167
|
+
def generate_executive_summary(
|
|
168
|
+
self,
|
|
169
|
+
results: dict[str, Any],
|
|
170
|
+
key_findings: list[str] | None = None,
|
|
171
|
+
) -> str:
|
|
172
|
+
"""Generate executive summary from results.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
results: Analysis results dictionary.
|
|
176
|
+
key_findings: List of key findings to highlight.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Executive summary text.
|
|
180
|
+
"""
|
|
181
|
+
summary_parts = []
|
|
182
|
+
|
|
183
|
+
# Overall status
|
|
184
|
+
if "pass_count" in results and "total_count" in results:
|
|
185
|
+
pass_count = results["pass_count"]
|
|
186
|
+
total = results["total_count"]
|
|
187
|
+
if pass_count == total:
|
|
188
|
+
summary_parts.append(f"All {total} tests passed.")
|
|
189
|
+
else:
|
|
190
|
+
fail_count = total - pass_count
|
|
191
|
+
summary_parts.append(
|
|
192
|
+
f"{fail_count} of {total} tests failed ({fail_count / total * 100:.0f}%)."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Key findings
|
|
196
|
+
if key_findings:
|
|
197
|
+
summary_parts.append("\nKey Findings:")
|
|
198
|
+
for finding in key_findings[:5]: # Top 5
|
|
199
|
+
summary_parts.append(f"- {finding}")
|
|
200
|
+
|
|
201
|
+
# Margin summary
|
|
202
|
+
if "min_margin" in results:
|
|
203
|
+
margin = results["min_margin"]
|
|
204
|
+
if margin < 10:
|
|
205
|
+
summary_parts.append(f"\nWarning: Minimum margin is {margin:.1f}%")
|
|
206
|
+
elif margin < 20:
|
|
207
|
+
summary_parts.append(f"\nNote: Minimum margin is {margin:.1f}%")
|
|
208
|
+
|
|
209
|
+
return "\n".join(summary_parts)
|
|
210
|
+
|
|
211
|
+
def to_markdown(self) -> str:
|
|
212
|
+
"""Convert report to Markdown format.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Markdown string.
|
|
216
|
+
"""
|
|
217
|
+
lines = []
|
|
218
|
+
|
|
219
|
+
# Title
|
|
220
|
+
lines.append(f"# {self.config.title}")
|
|
221
|
+
lines.append("")
|
|
222
|
+
|
|
223
|
+
if self.config.author:
|
|
224
|
+
lines.append(f"**Author:** {self.config.author}")
|
|
225
|
+
lines.append(f"**Date:** {self.config.created.strftime('%Y-%m-%d %H:%M')}")
|
|
226
|
+
lines.append("")
|
|
227
|
+
|
|
228
|
+
# Sections
|
|
229
|
+
for section in self.sections:
|
|
230
|
+
if not section.visible:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
prefix = "#" * (section.level + 1)
|
|
234
|
+
lines.append(f"{prefix} {section.title}")
|
|
235
|
+
lines.append("")
|
|
236
|
+
|
|
237
|
+
if isinstance(section.content, str):
|
|
238
|
+
lines.append(section.content)
|
|
239
|
+
elif isinstance(section.content, list):
|
|
240
|
+
for item in section.content:
|
|
241
|
+
if isinstance(item, dict) and item.get("type") == "table":
|
|
242
|
+
lines.extend(self._table_to_markdown(item))
|
|
243
|
+
else:
|
|
244
|
+
lines.append(str(item))
|
|
245
|
+
lines.append("")
|
|
246
|
+
|
|
247
|
+
# Subsections
|
|
248
|
+
for subsec in section.subsections:
|
|
249
|
+
if not subsec.visible:
|
|
250
|
+
continue
|
|
251
|
+
prefix = "#" * (subsec.level + 1)
|
|
252
|
+
lines.append(f"{prefix} {subsec.title}")
|
|
253
|
+
lines.append("")
|
|
254
|
+
if isinstance(subsec.content, str):
|
|
255
|
+
lines.append(subsec.content)
|
|
256
|
+
lines.append("")
|
|
257
|
+
|
|
258
|
+
return "\n".join(lines)
|
|
259
|
+
|
|
260
|
+
def _table_to_markdown(self, table: dict) -> list[str]: # type: ignore[type-arg]
|
|
261
|
+
"""Convert table to Markdown format."""
|
|
262
|
+
lines = []
|
|
263
|
+
headers = table.get("headers", [])
|
|
264
|
+
data = table.get("data", [])
|
|
265
|
+
|
|
266
|
+
if headers:
|
|
267
|
+
lines.append("| " + " | ".join(str(h) for h in headers) + " |")
|
|
268
|
+
lines.append("| " + " | ".join("---" for _ in headers) + " |")
|
|
269
|
+
|
|
270
|
+
for row in data:
|
|
271
|
+
lines.append("| " + " | ".join(str(cell) for cell in row) + " |")
|
|
272
|
+
|
|
273
|
+
if table.get("caption"):
|
|
274
|
+
lines.append("")
|
|
275
|
+
lines.append(f"*{table['caption']}*")
|
|
276
|
+
|
|
277
|
+
return lines
|
|
278
|
+
|
|
279
|
+
def save(self, path: str | Path) -> None:
|
|
280
|
+
"""Save report to file.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
path: Output file path.
|
|
284
|
+
"""
|
|
285
|
+
path = Path(path)
|
|
286
|
+
|
|
287
|
+
if path.suffix == ".md":
|
|
288
|
+
content = self.to_markdown()
|
|
289
|
+
path.write_text(content)
|
|
290
|
+
elif path.suffix == ".html":
|
|
291
|
+
content = self.to_html()
|
|
292
|
+
path.write_text(content)
|
|
293
|
+
else:
|
|
294
|
+
# For PDF and other formats, use Markdown as intermediate
|
|
295
|
+
content = self.to_markdown()
|
|
296
|
+
path.with_suffix(".md").write_text(content)
|
|
297
|
+
|
|
298
|
+
def to_html(self) -> str:
|
|
299
|
+
"""Convert report to HTML format.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
HTML string.
|
|
303
|
+
"""
|
|
304
|
+
lines = [
|
|
305
|
+
"<!DOCTYPE html>",
|
|
306
|
+
"<html>",
|
|
307
|
+
"<head>",
|
|
308
|
+
f"<title>{self.config.title}</title>",
|
|
309
|
+
"<style>",
|
|
310
|
+
"body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }",
|
|
311
|
+
"h1 { color: #333; }",
|
|
312
|
+
"h2 { color: #555; border-bottom: 1px solid #ddd; }",
|
|
313
|
+
"table { border-collapse: collapse; width: 100%; margin: 10px 0; }",
|
|
314
|
+
"th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }",
|
|
315
|
+
"th { background-color: #f2f2f2; }",
|
|
316
|
+
"tr:nth-child(even) { background-color: #f9f9f9; }",
|
|
317
|
+
".pass { color: green; }",
|
|
318
|
+
".fail { color: red; }",
|
|
319
|
+
".warning { color: orange; }",
|
|
320
|
+
"</style>",
|
|
321
|
+
"</head>",
|
|
322
|
+
"<body>",
|
|
323
|
+
f"<h1>{self.config.title}</h1>",
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
if self.config.author:
|
|
327
|
+
lines.append(f"<p><strong>Author:</strong> {self.config.author}</p>")
|
|
328
|
+
lines.append(
|
|
329
|
+
f"<p><strong>Date:</strong> {self.config.created.strftime('%Y-%m-%d %H:%M')}</p>"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
for section in self.sections:
|
|
333
|
+
if not section.visible:
|
|
334
|
+
continue
|
|
335
|
+
|
|
336
|
+
tag = f"h{min(section.level + 1, 6)}"
|
|
337
|
+
lines.append(f"<{tag}>{section.title}</{tag}>")
|
|
338
|
+
|
|
339
|
+
if isinstance(section.content, str):
|
|
340
|
+
lines.append(f"<p>{section.content}</p>")
|
|
341
|
+
elif isinstance(section.content, list):
|
|
342
|
+
for item in section.content:
|
|
343
|
+
if isinstance(item, dict) and item.get("type") == "table":
|
|
344
|
+
lines.extend(self._table_to_html(item))
|
|
345
|
+
else:
|
|
346
|
+
lines.append(f"<p>{item}</p>")
|
|
347
|
+
|
|
348
|
+
lines.extend(["</body>", "</html>"])
|
|
349
|
+
return "\n".join(lines)
|
|
350
|
+
|
|
351
|
+
def _table_to_html(self, table: dict) -> list[str]: # type: ignore[type-arg]
|
|
352
|
+
"""Convert table to HTML format."""
|
|
353
|
+
lines = ["<table>"]
|
|
354
|
+
headers = table.get("headers", [])
|
|
355
|
+
data = table.get("data", [])
|
|
356
|
+
|
|
357
|
+
if headers:
|
|
358
|
+
lines.append("<thead><tr>")
|
|
359
|
+
for h in headers:
|
|
360
|
+
lines.append(f"<th>{h}</th>")
|
|
361
|
+
lines.append("</tr></thead>")
|
|
362
|
+
|
|
363
|
+
lines.append("<tbody>")
|
|
364
|
+
for row in data:
|
|
365
|
+
lines.append("<tr>")
|
|
366
|
+
for cell in row:
|
|
367
|
+
lines.append(f"<td>{cell}</td>")
|
|
368
|
+
lines.append("</tr>")
|
|
369
|
+
lines.append("</tbody>")
|
|
370
|
+
|
|
371
|
+
lines.append("</table>")
|
|
372
|
+
|
|
373
|
+
if table.get("caption"):
|
|
374
|
+
lines.append(f"<p><em>{table['caption']}</em></p>")
|
|
375
|
+
|
|
376
|
+
return lines
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def generate_report(
|
|
380
|
+
results: dict[str, Any],
|
|
381
|
+
output_path: str | Path | None = None,
|
|
382
|
+
*,
|
|
383
|
+
title: str = "TraceKit Analysis Report",
|
|
384
|
+
verbosity: Literal["executive", "summary", "standard", "detailed", "debug"] = ("standard"),
|
|
385
|
+
template: str = "default",
|
|
386
|
+
formats: list[str] | None = None,
|
|
387
|
+
**kwargs: Any,
|
|
388
|
+
) -> Report:
|
|
389
|
+
"""Generate a report from analysis results.
|
|
390
|
+
|
|
391
|
+
Creates a formatted report from analysis results with configurable
|
|
392
|
+
verbosity and output formats.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
results: Analysis results dictionary.
|
|
396
|
+
output_path: Output file path (optional).
|
|
397
|
+
title: Report title.
|
|
398
|
+
verbosity: Detail level.
|
|
399
|
+
template: Template name.
|
|
400
|
+
formats: Output formats (pdf, html, markdown).
|
|
401
|
+
**kwargs: Additional configuration options.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Generated Report object.
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
>>> report = generate_report(results, "report.pdf", verbosity="summary")
|
|
408
|
+
"""
|
|
409
|
+
config = ReportConfig(
|
|
410
|
+
title=title,
|
|
411
|
+
verbosity=verbosity,
|
|
412
|
+
template=template,
|
|
413
|
+
**{k: v for k, v in kwargs.items() if hasattr(ReportConfig, k)},
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
report = Report(config=config, metadata={"source": "TraceKit"})
|
|
417
|
+
|
|
418
|
+
# Add executive summary
|
|
419
|
+
if verbosity in ("executive", "summary", "standard", "detailed", "debug"):
|
|
420
|
+
summary = report.generate_executive_summary(results)
|
|
421
|
+
report.add_section("Executive Summary", summary, level=1)
|
|
422
|
+
|
|
423
|
+
# Add results section
|
|
424
|
+
if verbosity in ("summary", "standard", "detailed", "debug"):
|
|
425
|
+
_add_results_section(report, results, verbosity)
|
|
426
|
+
|
|
427
|
+
# Add methodology section
|
|
428
|
+
if verbosity in ("standard", "detailed", "debug"):
|
|
429
|
+
_add_methodology_section(report, results)
|
|
430
|
+
|
|
431
|
+
# Add raw data section
|
|
432
|
+
if verbosity in ("detailed", "debug"):
|
|
433
|
+
_add_raw_data_section(report, results)
|
|
434
|
+
|
|
435
|
+
# Save if output path provided
|
|
436
|
+
if output_path:
|
|
437
|
+
output_path = Path(output_path)
|
|
438
|
+
if formats:
|
|
439
|
+
for fmt in formats:
|
|
440
|
+
path = output_path.with_suffix(f".{fmt}")
|
|
441
|
+
report.save(path)
|
|
442
|
+
else:
|
|
443
|
+
report.save(output_path)
|
|
444
|
+
|
|
445
|
+
return report
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _add_results_section(
|
|
449
|
+
report: Report,
|
|
450
|
+
results: dict[str, Any],
|
|
451
|
+
verbosity: str,
|
|
452
|
+
) -> None:
|
|
453
|
+
"""Add results section to report."""
|
|
454
|
+
report.add_section("Test Results", level=1)
|
|
455
|
+
|
|
456
|
+
# Create results table
|
|
457
|
+
if "measurements" in results:
|
|
458
|
+
measurements = results["measurements"]
|
|
459
|
+
headers = ["Parameter", "Value", "Specification", "Status"]
|
|
460
|
+
data = []
|
|
461
|
+
|
|
462
|
+
for name, meas in measurements.items():
|
|
463
|
+
value = meas.get("value", "N/A")
|
|
464
|
+
spec = meas.get("specification", "N/A")
|
|
465
|
+
status = "PASS" if meas.get("passed", True) else "FAIL"
|
|
466
|
+
data.append([name, value, spec, status])
|
|
467
|
+
|
|
468
|
+
report.add_table(data, headers, "Measurement Results")
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _add_methodology_section(
|
|
472
|
+
report: Report,
|
|
473
|
+
results: dict[str, Any],
|
|
474
|
+
) -> None:
|
|
475
|
+
"""Add methodology section to report."""
|
|
476
|
+
content = []
|
|
477
|
+
|
|
478
|
+
if "sample_rate" in results:
|
|
479
|
+
content.append(f"Sample rate: {results['sample_rate']} Hz")
|
|
480
|
+
if "num_samples" in results:
|
|
481
|
+
content.append(f"Number of samples: {results['num_samples']}")
|
|
482
|
+
if "analysis_time" in results:
|
|
483
|
+
content.append(f"Analysis time: {results['analysis_time']:.3f} seconds")
|
|
484
|
+
|
|
485
|
+
report.add_section(
|
|
486
|
+
"Methodology",
|
|
487
|
+
"\n".join(content) if content else "Standard analysis methodology applied.",
|
|
488
|
+
level=1,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _add_raw_data_section(
|
|
493
|
+
report: Report,
|
|
494
|
+
results: dict[str, Any],
|
|
495
|
+
) -> None:
|
|
496
|
+
"""Add raw data section to report."""
|
|
497
|
+
content = []
|
|
498
|
+
|
|
499
|
+
for key, value in results.items():
|
|
500
|
+
if isinstance(value, int | float | str):
|
|
501
|
+
content.append(f"{key}: {value}")
|
|
502
|
+
|
|
503
|
+
report.add_section(
|
|
504
|
+
"Raw Data",
|
|
505
|
+
"\n".join(content) if content else "No raw data available.",
|
|
506
|
+
level=1,
|
|
507
|
+
collapsible=True,
|
|
508
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Core report generation functionality - Multi-format renderers.
|
|
2
|
+
|
|
3
|
+
Note: Renamed from 'core' to 'core_formats' to avoid naming conflict
|
|
4
|
+
with core.py module which contains Report, ReportConfig, Section.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from oscura.reporting.core_formats.multi_format import (
|
|
8
|
+
MultiFormatRenderer,
|
|
9
|
+
detect_format_from_extension,
|
|
10
|
+
render_all_formats,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"MultiFormatRenderer",
|
|
15
|
+
"detect_format_from_extension",
|
|
16
|
+
"render_all_formats",
|
|
17
|
+
]
|