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,127 @@
|
|
|
1
|
+
"""Executive summary auto-generation.
|
|
2
|
+
|
|
3
|
+
Automatically generates concise executive summaries with key findings,
|
|
4
|
+
pass/fail status, and critical violations highlighted.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
References:
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Literal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ExecutiveSummary:
|
|
18
|
+
"""Executive summary of analysis results.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
overall_status: Overall pass/fail status.
|
|
22
|
+
pass_count: Number of passing tests.
|
|
23
|
+
total_count: Total number of tests.
|
|
24
|
+
key_findings: List of 3-5 key findings.
|
|
25
|
+
critical_violations: List of critical violations.
|
|
26
|
+
min_margin_pct: Minimum margin percentage.
|
|
27
|
+
summary_text: Natural language summary.
|
|
28
|
+
|
|
29
|
+
References:
|
|
30
|
+
REPORT-004: Executive Summary Auto-Generation
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
overall_status: bool
|
|
34
|
+
pass_count: int
|
|
35
|
+
total_count: int
|
|
36
|
+
key_findings: list[str] = field(default_factory=list)
|
|
37
|
+
critical_violations: list[str] = field(default_factory=list)
|
|
38
|
+
min_margin_pct: float | None = None
|
|
39
|
+
summary_text: str = ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def generate_executive_summary(
|
|
43
|
+
results: dict[str, Any],
|
|
44
|
+
*,
|
|
45
|
+
max_findings: int = 5,
|
|
46
|
+
length: Literal["short", "detailed"] = "short",
|
|
47
|
+
) -> ExecutiveSummary:
|
|
48
|
+
"""Generate executive summary from analysis results.
|
|
49
|
+
|
|
50
|
+
Automatically extracts top 3-5 key findings, pass/fail status in first
|
|
51
|
+
sentence, and critical violations in bullet list.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
results: Analysis results dictionary.
|
|
55
|
+
max_findings: Maximum number of key findings (default 5).
|
|
56
|
+
length: Summary length (short=1 paragraph, detailed=1 page).
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
ExecutiveSummary with generated content.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> results = {"pass_count": 10, "total_count": 12}
|
|
63
|
+
>>> summary = generate_executive_summary(results)
|
|
64
|
+
>>> print(summary.summary_text)
|
|
65
|
+
'All 10 tests passed with >25% margin. No violations.'
|
|
66
|
+
|
|
67
|
+
References:
|
|
68
|
+
REPORT-004: Executive Summary Auto-Generation
|
|
69
|
+
"""
|
|
70
|
+
# Extract basic counts
|
|
71
|
+
pass_count = results.get("pass_count", 0)
|
|
72
|
+
total_count = results.get("total_count", 0)
|
|
73
|
+
fail_count = total_count - pass_count if total_count else 0
|
|
74
|
+
overall_status = fail_count == 0
|
|
75
|
+
|
|
76
|
+
# Extract violations
|
|
77
|
+
violations = results.get("violations", [])
|
|
78
|
+
critical_violations = [v for v in violations if v.get("severity", "").lower() == "critical"]
|
|
79
|
+
|
|
80
|
+
# Extract key findings
|
|
81
|
+
key_findings: list[str] = []
|
|
82
|
+
if critical_violations:
|
|
83
|
+
key_findings.append(
|
|
84
|
+
f"{len(critical_violations)} critical violation(s) require immediate attention"
|
|
85
|
+
)
|
|
86
|
+
elif violations:
|
|
87
|
+
key_findings.append(f"{len(violations)} violation(s) detected")
|
|
88
|
+
|
|
89
|
+
# Add margin information
|
|
90
|
+
min_margin = results.get("min_margin")
|
|
91
|
+
if min_margin is not None and min_margin < 20:
|
|
92
|
+
status = "critical" if min_margin < 10 else "marginal"
|
|
93
|
+
key_findings.append(f"Minimum margin is {min_margin:.1f}% ({status})")
|
|
94
|
+
|
|
95
|
+
# Build summary text in natural language
|
|
96
|
+
if overall_status and total_count > 0:
|
|
97
|
+
summary_text = f"All {pass_count} tests passed."
|
|
98
|
+
if min_margin is not None and min_margin > 20:
|
|
99
|
+
summary_text += f" Minimum margin: {min_margin:.1f}%."
|
|
100
|
+
elif total_count > 0:
|
|
101
|
+
pct = fail_count / total_count * 100
|
|
102
|
+
summary_text = f"{fail_count} of {total_count} tests failed ({pct:.0f}% failure rate)."
|
|
103
|
+
else:
|
|
104
|
+
summary_text = "Analysis completed successfully."
|
|
105
|
+
|
|
106
|
+
if critical_violations:
|
|
107
|
+
summary_text += (
|
|
108
|
+
f" Critical: {len(critical_violations)} violation(s) require immediate action."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Add detailed findings for detailed mode
|
|
112
|
+
if length == "detailed" and key_findings:
|
|
113
|
+
summary_text += "\n\nKey Findings:\n"
|
|
114
|
+
summary_text += "\n".join(f" - {finding}" for finding in key_findings[:max_findings])
|
|
115
|
+
|
|
116
|
+
return ExecutiveSummary(
|
|
117
|
+
overall_status=overall_status,
|
|
118
|
+
pass_count=pass_count,
|
|
119
|
+
total_count=total_count,
|
|
120
|
+
key_findings=key_findings[:max_findings],
|
|
121
|
+
critical_violations=[str(v) for v in critical_violations],
|
|
122
|
+
min_margin_pct=min_margin,
|
|
123
|
+
summary_text=summary_text,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
__all__ = ["ExecutiveSummary", "generate_executive_summary"]
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Smart content filtering for reports.
|
|
2
|
+
|
|
3
|
+
Shows only relevant information based on context, severity, and audience
|
|
4
|
+
with conditional sections and violation-only modes.
|
|
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 Any, Literal
|
|
15
|
+
|
|
16
|
+
from oscura.reporting.formatting.standards import Severity
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AudienceType(Enum):
|
|
20
|
+
"""Audience types for content filtering."""
|
|
21
|
+
|
|
22
|
+
EXECUTIVE = "executive"
|
|
23
|
+
ENGINEERING = "engineering"
|
|
24
|
+
DEBUG = "debug"
|
|
25
|
+
REGULATORY = "regulatory"
|
|
26
|
+
PRODUCTION = "production"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ContentFilter:
|
|
31
|
+
"""Smart content filtering configuration.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
severity_threshold: Minimum severity to show (critical/warning/info).
|
|
35
|
+
audience: Target audience type.
|
|
36
|
+
show_only: Show only specific content (violations, changes, all).
|
|
37
|
+
hide_empty_sections: Hide sections with no data.
|
|
38
|
+
relevance_threshold: Minimum relevance score (0.0-1.0).
|
|
39
|
+
|
|
40
|
+
References:
|
|
41
|
+
REPORT-005: Smart Content Filtering
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
severity_threshold: Severity = Severity.INFO
|
|
45
|
+
audience: AudienceType = AudienceType.ENGINEERING
|
|
46
|
+
show_only: Literal["all", "violations", "changes"] = "all"
|
|
47
|
+
hide_empty_sections: bool = True
|
|
48
|
+
relevance_threshold: float = 0.5
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def filter_by_severity(
|
|
52
|
+
items: list[dict[str, Any]],
|
|
53
|
+
min_severity: Severity | str,
|
|
54
|
+
) -> list[dict[str, Any]]:
|
|
55
|
+
"""Filter items by severity level.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
items: List of items with 'severity' field.
|
|
59
|
+
min_severity: Minimum severity to include.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Filtered list of items.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> items = [{"name": "test1", "severity": "critical"},
|
|
66
|
+
... {"name": "test2", "severity": "info"}]
|
|
67
|
+
>>> filtered = filter_by_severity(items, "warning")
|
|
68
|
+
>>> len(filtered) # Only critical items
|
|
69
|
+
1
|
|
70
|
+
|
|
71
|
+
References:
|
|
72
|
+
REPORT-005: Smart Content Filtering
|
|
73
|
+
"""
|
|
74
|
+
if isinstance(min_severity, str):
|
|
75
|
+
min_severity = Severity(min_severity.lower())
|
|
76
|
+
|
|
77
|
+
severity_order = {
|
|
78
|
+
Severity.INFO: 0,
|
|
79
|
+
Severity.WARNING: 1,
|
|
80
|
+
Severity.ERROR: 2,
|
|
81
|
+
Severity.CRITICAL: 3,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
min_level = severity_order.get(min_severity, 0)
|
|
85
|
+
|
|
86
|
+
filtered = []
|
|
87
|
+
for item in items:
|
|
88
|
+
item_severity_str = item.get("severity", "info").lower()
|
|
89
|
+
try:
|
|
90
|
+
item_severity = Severity(item_severity_str)
|
|
91
|
+
item_level = severity_order.get(item_severity, 0)
|
|
92
|
+
if item_level >= min_level:
|
|
93
|
+
filtered.append(item)
|
|
94
|
+
except (ValueError, KeyError):
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
return filtered
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def filter_by_audience(
|
|
101
|
+
content: dict[str, Any],
|
|
102
|
+
audience: AudienceType | str,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
"""Filter content for specific audience.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
content: Content dictionary with various sections.
|
|
108
|
+
audience: Target audience type.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Filtered content dictionary.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> content = {"methodology": "...", "results": "...", "raw_data": "..."}
|
|
115
|
+
>>> filtered = filter_by_audience(content, "executive")
|
|
116
|
+
>>> "raw_data" in filtered # False - executives don't need raw data
|
|
117
|
+
False
|
|
118
|
+
|
|
119
|
+
References:
|
|
120
|
+
REPORT-005: Smart Content Filtering
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(audience, str):
|
|
123
|
+
audience = AudienceType(audience.lower())
|
|
124
|
+
|
|
125
|
+
# Define what each audience sees
|
|
126
|
+
audience_sections = {
|
|
127
|
+
AudienceType.EXECUTIVE: ["executive_summary", "key_findings", "recommendations"],
|
|
128
|
+
AudienceType.ENGINEERING: ["summary", "results", "methodology", "plots"],
|
|
129
|
+
AudienceType.DEBUG: ["summary", "results", "methodology", "plots", "raw_data", "logs"],
|
|
130
|
+
AudienceType.REGULATORY: ["summary", "compliance", "test_procedures", "standards"],
|
|
131
|
+
AudienceType.PRODUCTION: ["summary", "pass_fail", "margin", "yield"],
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
allowed_sections = audience_sections.get(audience, list(content.keys()))
|
|
135
|
+
|
|
136
|
+
filtered = {}
|
|
137
|
+
for key, value in content.items():
|
|
138
|
+
if key in allowed_sections:
|
|
139
|
+
filtered[key] = value
|
|
140
|
+
|
|
141
|
+
return filtered
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def calculate_relevance_score(
|
|
145
|
+
item: dict[str, Any],
|
|
146
|
+
context: dict[str, Any] | None = None,
|
|
147
|
+
) -> float:
|
|
148
|
+
"""Calculate relevance score for content item.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
item: Content item.
|
|
152
|
+
context: Optional context for relevance scoring.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Relevance score 0.0-1.0.
|
|
156
|
+
|
|
157
|
+
References:
|
|
158
|
+
REPORT-005: Smart Content Filtering
|
|
159
|
+
"""
|
|
160
|
+
score = 0.5 # Base score
|
|
161
|
+
|
|
162
|
+
# Increase score for violations
|
|
163
|
+
if item.get("status") == "fail":
|
|
164
|
+
score += 0.3
|
|
165
|
+
|
|
166
|
+
# Increase score for critical severity
|
|
167
|
+
severity = item.get("severity", "").lower()
|
|
168
|
+
if severity == "critical":
|
|
169
|
+
score += 0.3
|
|
170
|
+
elif severity == "warning":
|
|
171
|
+
score += 0.1
|
|
172
|
+
|
|
173
|
+
# Increase score for outliers
|
|
174
|
+
if item.get("is_outlier"):
|
|
175
|
+
score += 0.2
|
|
176
|
+
|
|
177
|
+
# Increase score for low margins
|
|
178
|
+
margin = item.get("margin_pct")
|
|
179
|
+
if margin is not None and margin < 20:
|
|
180
|
+
score += 0.2
|
|
181
|
+
|
|
182
|
+
return min(1.0, score)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
__all__ = [
|
|
186
|
+
"AudienceType",
|
|
187
|
+
"ContentFilter",
|
|
188
|
+
"calculate_relevance_score",
|
|
189
|
+
"filter_by_audience",
|
|
190
|
+
"filter_by_severity",
|
|
191
|
+
]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Minimal boilerplate content generation.
|
|
2
|
+
|
|
3
|
+
This module eliminates unnecessary static text and focuses on data-driven
|
|
4
|
+
narrative with compact formatting and automated captions.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.reporting.content import generate_compact_text
|
|
9
|
+
>>> compact = generate_compact_text(value=2.3e-9, spec=5e-9, unit="s")
|
|
10
|
+
>>> print(compact) # "Rise time: 2.3ns (spec <5ns, ✓ 54% margin)"
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any, Literal
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MinimalContent:
|
|
23
|
+
"""Minimal boilerplate content generator.
|
|
24
|
+
|
|
25
|
+
Focuses on data-driven content with minimal filler text.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
auto_units: Auto-scale units (2300ns → 2.3μs).
|
|
29
|
+
data_first: Show results before methodology.
|
|
30
|
+
show_passing: Include passing tests or violations only.
|
|
31
|
+
auto_captions: Generate captions from data, not static templates.
|
|
32
|
+
|
|
33
|
+
References:
|
|
34
|
+
REPORT-003: Minimal Boilerplate Content
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
auto_units: bool = True
|
|
38
|
+
data_first: bool = True
|
|
39
|
+
show_passing: bool = True
|
|
40
|
+
auto_captions: bool = True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def generate_compact_text(
|
|
44
|
+
value: float,
|
|
45
|
+
spec: float | None = None,
|
|
46
|
+
unit: str = "",
|
|
47
|
+
*,
|
|
48
|
+
spec_type: Literal["max", "min"] = "max",
|
|
49
|
+
name: str = "",
|
|
50
|
+
) -> str:
|
|
51
|
+
"""Generate compact, data-driven text.
|
|
52
|
+
|
|
53
|
+
Avoids filler text like "The measurement was performed and the result was...".
|
|
54
|
+
Instead produces compact format: "Rise time: 2.3ns (spec <5ns, ✓ 54% margin)".
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
value: Measured value.
|
|
58
|
+
spec: Specification limit (optional).
|
|
59
|
+
unit: Unit string.
|
|
60
|
+
spec_type: Type of specification (max or min).
|
|
61
|
+
name: Measurement name (optional).
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Compact formatted string.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> generate_compact_text(2.3e-9, 5e-9, "s", name="Rise time")
|
|
68
|
+
'Rise time: 2.3ns (spec <5ns, ✓ 54% margin)'
|
|
69
|
+
|
|
70
|
+
References:
|
|
71
|
+
REPORT-003: Minimal Boilerplate Content
|
|
72
|
+
"""
|
|
73
|
+
from oscura.reporting.formatting.numbers import format_with_units
|
|
74
|
+
|
|
75
|
+
# Format value with auto-scaled units
|
|
76
|
+
value_str = format_with_units(value, unit)
|
|
77
|
+
|
|
78
|
+
# Build compact text
|
|
79
|
+
parts = []
|
|
80
|
+
if name:
|
|
81
|
+
parts.append(f"{name}:")
|
|
82
|
+
|
|
83
|
+
parts.append(value_str)
|
|
84
|
+
|
|
85
|
+
# Add spec context if provided
|
|
86
|
+
if spec is not None:
|
|
87
|
+
spec_str = format_with_units(spec, unit)
|
|
88
|
+
spec_symbol = "<" if spec_type == "max" else ">"
|
|
89
|
+
|
|
90
|
+
# Calculate pass/fail and margin
|
|
91
|
+
if spec_type == "max":
|
|
92
|
+
passed = value <= spec
|
|
93
|
+
margin = (spec - value) / spec * 100 if spec != 0 else 0
|
|
94
|
+
else:
|
|
95
|
+
passed = value >= spec
|
|
96
|
+
margin = (value - spec) / spec * 100 if spec != 0 else 0
|
|
97
|
+
|
|
98
|
+
status_symbol = "\u2713" if passed else "\u2717" # ✓ or ✗
|
|
99
|
+
spec_part = f"(spec {spec_symbol}{spec_str}, {status_symbol} {margin:.0f}% margin)"
|
|
100
|
+
parts.append(spec_part)
|
|
101
|
+
|
|
102
|
+
return " ".join(parts)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def auto_caption(
|
|
106
|
+
data_type: str,
|
|
107
|
+
data: dict[str, Any],
|
|
108
|
+
*,
|
|
109
|
+
include_stats: bool = True,
|
|
110
|
+
) -> str:
|
|
111
|
+
"""Generate automated captions from data.
|
|
112
|
+
|
|
113
|
+
Instead of static templates, generates captions based on actual data content.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
data_type: Type of data (measurement, plot, table).
|
|
117
|
+
data: Data dictionary.
|
|
118
|
+
include_stats: Include statistics in caption.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Generated caption string.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> data = {"name": "Rise time", "count": 100, "mean": 2.3e-9}
|
|
125
|
+
>>> auto_caption("measurement", data)
|
|
126
|
+
'Rise time measurement (n=100, mean=2.3ns)'
|
|
127
|
+
|
|
128
|
+
References:
|
|
129
|
+
REPORT-003: Minimal Boilerplate Content
|
|
130
|
+
"""
|
|
131
|
+
parts = []
|
|
132
|
+
|
|
133
|
+
# Extract key information
|
|
134
|
+
name = data.get("name", data_type.title())
|
|
135
|
+
parts.append(name)
|
|
136
|
+
|
|
137
|
+
# Add data-specific information
|
|
138
|
+
if data_type == "measurement" and include_stats:
|
|
139
|
+
count = data.get("count")
|
|
140
|
+
if count:
|
|
141
|
+
parts.append(f"(n={count}")
|
|
142
|
+
|
|
143
|
+
mean = data.get("mean")
|
|
144
|
+
if mean is not None:
|
|
145
|
+
from oscura.reporting.formatting.numbers import format_with_units
|
|
146
|
+
|
|
147
|
+
unit = data.get("unit", "")
|
|
148
|
+
mean_str = format_with_units(mean, unit)
|
|
149
|
+
parts[-1] += f", mean={mean_str}"
|
|
150
|
+
|
|
151
|
+
parts[-1] += ")"
|
|
152
|
+
|
|
153
|
+
elif data_type == "plot":
|
|
154
|
+
plot_type = data.get("type", "plot")
|
|
155
|
+
if plot_type != "plot":
|
|
156
|
+
parts.append(f"- {plot_type}")
|
|
157
|
+
|
|
158
|
+
elif data_type == "table":
|
|
159
|
+
rows = data.get("rows")
|
|
160
|
+
cols = data.get("cols")
|
|
161
|
+
if rows and cols:
|
|
162
|
+
parts.append(f"({rows}x{cols})")
|
|
163
|
+
|
|
164
|
+
return " ".join(parts)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def remove_filler_text(text: str) -> str:
|
|
168
|
+
"""Remove common filler phrases from text.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
text: Input text.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Text with filler removed.
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> text = "The measurement was performed and the result was 2.3ns."
|
|
178
|
+
>>> remove_filler_text(text)
|
|
179
|
+
'Result: 2.3ns.'
|
|
180
|
+
|
|
181
|
+
References:
|
|
182
|
+
REPORT-003: Minimal Boilerplate Content
|
|
183
|
+
"""
|
|
184
|
+
# Common filler phrases to remove
|
|
185
|
+
filler_phrases = [
|
|
186
|
+
"The measurement was performed and",
|
|
187
|
+
"The result was",
|
|
188
|
+
"It was found that",
|
|
189
|
+
"The analysis shows that",
|
|
190
|
+
"It can be seen that",
|
|
191
|
+
"As can be observed",
|
|
192
|
+
"The data indicates",
|
|
193
|
+
"It should be noted that",
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
result = text
|
|
197
|
+
for phrase in filler_phrases:
|
|
198
|
+
result = result.replace(phrase, "").strip()
|
|
199
|
+
|
|
200
|
+
# Clean up extra spaces
|
|
201
|
+
while " " in result:
|
|
202
|
+
result = result.replace(" ", " ")
|
|
203
|
+
|
|
204
|
+
# Capitalize first letter after removal
|
|
205
|
+
if result and not result[0].isupper():
|
|
206
|
+
result = result[0].upper() + result[1:]
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def conditional_section(
|
|
212
|
+
data: list[Any] | dict[str, Any],
|
|
213
|
+
section_title: str,
|
|
214
|
+
) -> tuple[bool, str]:
|
|
215
|
+
"""Determine if section should be shown.
|
|
216
|
+
|
|
217
|
+
Only show sections if data exists (no empty sections).
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
data: Section data.
|
|
221
|
+
section_title: Section title.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Tuple of (should_show, reason).
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
>>> should_show, reason = conditional_section([], "Violations")
|
|
228
|
+
>>> print(should_show) # False
|
|
229
|
+
>>> print(reason) # "No violations found"
|
|
230
|
+
|
|
231
|
+
References:
|
|
232
|
+
REPORT-003: Minimal Boilerplate Content
|
|
233
|
+
"""
|
|
234
|
+
if isinstance(data, list):
|
|
235
|
+
if not data:
|
|
236
|
+
return False, f"No {section_title.lower()} found"
|
|
237
|
+
return True, f"{len(data)} {section_title.lower()}"
|
|
238
|
+
|
|
239
|
+
elif isinstance(data, dict):
|
|
240
|
+
if not data or all(not v for v in data.values()):
|
|
241
|
+
return False, f"No {section_title.lower()} found"
|
|
242
|
+
return True, ""
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
# For other types, check if truthy
|
|
246
|
+
if not data: # type: ignore[unreachable]
|
|
247
|
+
return False, f"No {section_title.lower()} found"
|
|
248
|
+
return True, ""
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
__all__ = [
|
|
252
|
+
"MinimalContent",
|
|
253
|
+
"auto_caption",
|
|
254
|
+
"conditional_section",
|
|
255
|
+
"generate_compact_text",
|
|
256
|
+
"remove_filler_text",
|
|
257
|
+
]
|