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.
- 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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Number formatting utilities for signal analysis reports.
|
|
2
|
+
|
|
3
|
+
Provides comprehensive number formatting with SI prefixes, engineering notation,
|
|
4
|
+
locale support, and specification comparison capabilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import ClassVar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NumberFormatter:
|
|
15
|
+
"""Comprehensive number formatting utility with SI prefix support.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
sig_figs: Number of significant figures to display.
|
|
19
|
+
auto_scale: Whether to automatically scale to appropriate SI prefix.
|
|
20
|
+
engineering_notation: Use engineering notation (powers of 3).
|
|
21
|
+
unicode_prefixes: Use Unicode characters for prefixes (µ vs u).
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> fmt = NumberFormatter(sig_figs=3)
|
|
25
|
+
>>> fmt.format(2.3e-9, "s")
|
|
26
|
+
'2.30 ns'
|
|
27
|
+
>>> fmt.format(1.5e6, "Hz")
|
|
28
|
+
'1.50 MHz'
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# SI prefixes (powers of 10)
|
|
32
|
+
# Unicode uses Greek Small Letter Mu (U+03BC), ASCII uses 'u'
|
|
33
|
+
SI_PREFIXES: ClassVar[dict[int, tuple[str, str]]] = {
|
|
34
|
+
-15: ("f", "f"), # femto
|
|
35
|
+
-12: ("p", "p"), # pico
|
|
36
|
+
-9: ("n", "n"), # nano
|
|
37
|
+
-6: ("\u03bc", "u"), # micro (Greek mu U+03BC, ascii 'u')
|
|
38
|
+
-3: ("m", "m"), # milli
|
|
39
|
+
0: ("", ""), # base
|
|
40
|
+
3: ("k", "k"), # kilo
|
|
41
|
+
6: ("M", "M"), # mega
|
|
42
|
+
9: ("G", "G"), # giga
|
|
43
|
+
12: ("T", "T"), # tera
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
sig_figs: int = 3,
|
|
49
|
+
auto_scale: bool = True,
|
|
50
|
+
engineering_notation: bool = True,
|
|
51
|
+
unicode_prefixes: bool = True,
|
|
52
|
+
precision: int | None = None,
|
|
53
|
+
use_si: bool | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize formatter.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
sig_figs: Number of significant figures (default 3).
|
|
59
|
+
auto_scale: Enable automatic SI prefix scaling (default True).
|
|
60
|
+
engineering_notation: Use engineering notation (default True).
|
|
61
|
+
unicode_prefixes: Use Unicode prefixes like µ (default True).
|
|
62
|
+
precision: Alias for sig_figs (backwards compatibility).
|
|
63
|
+
use_si: Alias for auto_scale (backwards compatibility).
|
|
64
|
+
"""
|
|
65
|
+
# Handle backwards compatibility aliases
|
|
66
|
+
self.sig_figs = precision if precision is not None else sig_figs
|
|
67
|
+
self.auto_scale = use_si if use_si is not None else auto_scale
|
|
68
|
+
self.engineering_notation = engineering_notation
|
|
69
|
+
self.unicode_prefixes = unicode_prefixes
|
|
70
|
+
|
|
71
|
+
# Legacy attribute for backwards compatibility
|
|
72
|
+
self.precision = self.sig_figs
|
|
73
|
+
self.use_si = self.auto_scale
|
|
74
|
+
|
|
75
|
+
def format(
|
|
76
|
+
self,
|
|
77
|
+
value: float,
|
|
78
|
+
unit: str = "",
|
|
79
|
+
decimal_places: int | None = None,
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Format a number with optional SI prefix.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
value: Numeric value to format.
|
|
85
|
+
unit: Unit of measurement (e.g., "V", "Hz", "s").
|
|
86
|
+
decimal_places: Override number of decimal places.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Formatted string with value, prefix, and unit.
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
>>> fmt = NumberFormatter()
|
|
93
|
+
>>> fmt.format(2.3e-6, "s")
|
|
94
|
+
'2.300 µs'
|
|
95
|
+
>>> fmt.format(1500000, "Hz")
|
|
96
|
+
'1.500 MHz'
|
|
97
|
+
"""
|
|
98
|
+
# Handle special float values
|
|
99
|
+
if math.isnan(value):
|
|
100
|
+
return f"NaN {unit}".strip()
|
|
101
|
+
if math.isinf(value):
|
|
102
|
+
sign = "-" if value < 0 else ""
|
|
103
|
+
return f"{sign}Inf {unit}".strip()
|
|
104
|
+
|
|
105
|
+
# Determine precision
|
|
106
|
+
places = decimal_places if decimal_places is not None else self.sig_figs
|
|
107
|
+
|
|
108
|
+
if not self.auto_scale:
|
|
109
|
+
# No scaling - show full value with appropriate precision
|
|
110
|
+
# Use more decimal places for small numbers to show actual value
|
|
111
|
+
if abs(value) != 0 and abs(value) < 1:
|
|
112
|
+
# Calculate needed decimal places to show significant figures
|
|
113
|
+
from math import floor, log10
|
|
114
|
+
|
|
115
|
+
order = floor(log10(abs(value)))
|
|
116
|
+
# For value like 0.0023, order=-3, need at least 4 decimals
|
|
117
|
+
decimal_places_needed = max(places, abs(order) + 1)
|
|
118
|
+
return f"{value:.{decimal_places_needed}f} {unit}".strip()
|
|
119
|
+
elif abs(value) >= 1e6:
|
|
120
|
+
return f"{value:.{places}e} {unit}".strip()
|
|
121
|
+
return f"{value:.{places}f} {unit}".strip()
|
|
122
|
+
|
|
123
|
+
# Find appropriate SI prefix
|
|
124
|
+
if value == 0:
|
|
125
|
+
return f"0.{'0' * places} {unit}".strip()
|
|
126
|
+
|
|
127
|
+
abs_val = abs(value)
|
|
128
|
+
|
|
129
|
+
# Determine the appropriate power of 10
|
|
130
|
+
if abs_val < 1e-15:
|
|
131
|
+
return f"{value:.{places}e} {unit}".strip()
|
|
132
|
+
elif abs_val < 1e-12:
|
|
133
|
+
scaled, exp = value * 1e15, -15
|
|
134
|
+
elif abs_val < 1e-9:
|
|
135
|
+
scaled, exp = value * 1e12, -12
|
|
136
|
+
elif abs_val < 1e-6:
|
|
137
|
+
scaled, exp = value * 1e9, -9
|
|
138
|
+
elif abs_val < 1e-3:
|
|
139
|
+
scaled, exp = value * 1e6, -6
|
|
140
|
+
elif abs_val < 1:
|
|
141
|
+
scaled, exp = value * 1e3, -3
|
|
142
|
+
elif abs_val < 1e3:
|
|
143
|
+
scaled, exp = value, 0
|
|
144
|
+
elif abs_val < 1e6:
|
|
145
|
+
scaled, exp = value / 1e3, 3
|
|
146
|
+
elif abs_val < 1e9:
|
|
147
|
+
scaled, exp = value / 1e6, 6
|
|
148
|
+
elif abs_val < 1e12:
|
|
149
|
+
scaled, exp = value / 1e9, 9
|
|
150
|
+
elif abs_val < 1e15:
|
|
151
|
+
scaled, exp = value / 1e12, 12
|
|
152
|
+
else:
|
|
153
|
+
return f"{value:.{places}e} {unit}".strip()
|
|
154
|
+
|
|
155
|
+
# Get prefix (unicode or ascii)
|
|
156
|
+
prefix_idx = 0 if self.unicode_prefixes else 1
|
|
157
|
+
prefix = self.SI_PREFIXES.get(exp, ("", ""))[prefix_idx]
|
|
158
|
+
|
|
159
|
+
return f"{scaled:.{places}f} {prefix}{unit}".strip()
|
|
160
|
+
|
|
161
|
+
def format_percentage(self, value: float, decimals: int = 1) -> str:
|
|
162
|
+
"""Format a value as a percentage.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
value: Value as decimal (0.543 = 54.3%) or already percentage.
|
|
166
|
+
decimals: Number of decimal places.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Formatted percentage string.
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
>>> fmt = NumberFormatter()
|
|
173
|
+
>>> fmt.format_percentage(0.543)
|
|
174
|
+
'54.3%'
|
|
175
|
+
"""
|
|
176
|
+
# If value > 1, assume it's already a percentage
|
|
177
|
+
if abs(value) > 1:
|
|
178
|
+
return f"{value:.{decimals}f}%"
|
|
179
|
+
return f"{value * 100:.{decimals}f}%"
|
|
180
|
+
|
|
181
|
+
def format_range(
|
|
182
|
+
self,
|
|
183
|
+
min_val: float,
|
|
184
|
+
typ_val: float,
|
|
185
|
+
max_val: float,
|
|
186
|
+
unit: str = "",
|
|
187
|
+
) -> str:
|
|
188
|
+
"""Format min/typ/max range.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
min_val: Minimum value.
|
|
192
|
+
typ_val: Typical value.
|
|
193
|
+
max_val: Maximum value.
|
|
194
|
+
unit: Unit of measurement.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Formatted range string.
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
>>> fmt = NumberFormatter()
|
|
201
|
+
>>> fmt.format_range(1e-6, 2e-6, 3e-6, "s")
|
|
202
|
+
'min=1.000 µs typ=2.000 µs max=3.000 µs'
|
|
203
|
+
"""
|
|
204
|
+
return (
|
|
205
|
+
f"min={self.format(min_val, unit)} "
|
|
206
|
+
f"typ={self.format(typ_val, unit)} "
|
|
207
|
+
f"max={self.format(max_val, unit)}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def format_value(
|
|
212
|
+
value: float,
|
|
213
|
+
unit_or_precision: str | int = 3,
|
|
214
|
+
unit: str = "",
|
|
215
|
+
sig_figs: int | None = None,
|
|
216
|
+
) -> str:
|
|
217
|
+
"""Format a numeric value with SI prefix.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
value: Value to format.
|
|
221
|
+
unit_or_precision: Either unit (str) or precision (int).
|
|
222
|
+
unit: Unit string (if precision specified first).
|
|
223
|
+
sig_figs: Number of significant figures (alternative to precision arg).
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Formatted value with appropriate SI prefix.
|
|
227
|
+
|
|
228
|
+
Examples:
|
|
229
|
+
>>> format_value(2.3e-9, "s")
|
|
230
|
+
'2.300 ns'
|
|
231
|
+
>>> format_value(1.5e6, 4, "Hz")
|
|
232
|
+
'1.5000 MHz'
|
|
233
|
+
"""
|
|
234
|
+
# Handle flexible arguments
|
|
235
|
+
if isinstance(unit_or_precision, str):
|
|
236
|
+
precision = sig_figs if sig_figs is not None else 3
|
|
237
|
+
actual_unit = unit_or_precision
|
|
238
|
+
else:
|
|
239
|
+
precision = sig_figs if sig_figs is not None else unit_or_precision
|
|
240
|
+
actual_unit = unit
|
|
241
|
+
|
|
242
|
+
fmt = NumberFormatter(sig_figs=precision)
|
|
243
|
+
return fmt.format(value, actual_unit)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def format_with_units(value: float, unit: str, sig_figs: int = 3) -> str:
|
|
247
|
+
"""Format value with units and SI prefix.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
value: Numeric value.
|
|
251
|
+
unit: Unit of measurement.
|
|
252
|
+
sig_figs: Number of significant figures.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Formatted string with value and unit.
|
|
256
|
+
"""
|
|
257
|
+
fmt = NumberFormatter(sig_figs=sig_figs)
|
|
258
|
+
return fmt.format(value, unit)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def format_with_context(
|
|
262
|
+
value: float,
|
|
263
|
+
context: str = "",
|
|
264
|
+
spec: float | None = None,
|
|
265
|
+
unit: str = "",
|
|
266
|
+
spec_type: str = "max",
|
|
267
|
+
show_margin: bool = True,
|
|
268
|
+
) -> str:
|
|
269
|
+
"""Format value with context and specification comparison.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
value: Value to format.
|
|
273
|
+
context: Context string for display.
|
|
274
|
+
spec: Specification limit for comparison.
|
|
275
|
+
unit: Unit of measurement.
|
|
276
|
+
spec_type: Type of specification ("max", "min", or "exact").
|
|
277
|
+
show_margin: Whether to show margin percentage.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Formatted string with pass/fail indication if spec provided.
|
|
281
|
+
|
|
282
|
+
Examples:
|
|
283
|
+
>>> format_with_context(2.3e-9, spec=5e-9, unit="s", spec_type="max")
|
|
284
|
+
'2.300 ns ✓'
|
|
285
|
+
"""
|
|
286
|
+
formatted = format_value(value, unit)
|
|
287
|
+
|
|
288
|
+
if spec is not None:
|
|
289
|
+
# Determine pass/fail
|
|
290
|
+
if spec_type == "max":
|
|
291
|
+
passed = value <= spec
|
|
292
|
+
margin = ((spec - value) / spec * 100) if spec != 0 else 0
|
|
293
|
+
elif spec_type == "min":
|
|
294
|
+
passed = value >= spec
|
|
295
|
+
margin = ((value - spec) / spec * 100) if spec != 0 else 0
|
|
296
|
+
else: # exact
|
|
297
|
+
tolerance = abs(spec * 0.01) # 1% tolerance
|
|
298
|
+
passed = abs(value - spec) <= tolerance
|
|
299
|
+
margin = 100 - abs((value - spec) / spec * 100) if spec != 0 else 100
|
|
300
|
+
|
|
301
|
+
# Unicode checkmark/cross
|
|
302
|
+
status = "\u2713" if passed else "\u2717"
|
|
303
|
+
|
|
304
|
+
if show_margin and passed:
|
|
305
|
+
return f"{formatted} {status} ({margin:.1f}% margin)"
|
|
306
|
+
return f"{formatted} {status}"
|
|
307
|
+
|
|
308
|
+
if context:
|
|
309
|
+
return f"{formatted} ({context})"
|
|
310
|
+
return formatted
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def format_percentage(value: float, decimals: int = 1) -> str:
|
|
314
|
+
"""Format as percentage.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
value: Value as decimal (0.5 = 50%) or already percentage (>1).
|
|
318
|
+
decimals: Number of decimal places.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Formatted percentage string.
|
|
322
|
+
"""
|
|
323
|
+
if abs(value) > 1:
|
|
324
|
+
return f"{value:.{decimals}f}%"
|
|
325
|
+
return f"{value * 100:.{decimals}f}%"
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def format_range(min_val: float, max_val: float, unit: str = "") -> str:
|
|
329
|
+
"""Format a range of values.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
min_val: Minimum value.
|
|
333
|
+
max_val: Maximum value.
|
|
334
|
+
unit: Unit of measurement.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Formatted range string.
|
|
338
|
+
"""
|
|
339
|
+
return f"{format_value(min_val, unit)} to {format_value(max_val, unit)}"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def format_with_locale(
|
|
343
|
+
value: float | None = None,
|
|
344
|
+
locale: str = "en_US",
|
|
345
|
+
date_value: float | None = None,
|
|
346
|
+
) -> str:
|
|
347
|
+
"""Format value with locale-specific formatting.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
value: Numeric value to format.
|
|
351
|
+
locale: Locale string (e.g., 'en_US', 'de_DE', 'fr_FR').
|
|
352
|
+
date_value: Unix timestamp for date formatting.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Formatted string with locale-specific separators.
|
|
356
|
+
Returns empty string if value is None and no date_value.
|
|
357
|
+
|
|
358
|
+
Examples:
|
|
359
|
+
>>> format_with_locale(1234.56, locale="en_US")
|
|
360
|
+
'1,234.56'
|
|
361
|
+
>>> format_with_locale(1234.56, locale="de_DE")
|
|
362
|
+
'1.234,56'
|
|
363
|
+
"""
|
|
364
|
+
# Handle date formatting
|
|
365
|
+
if date_value is not None:
|
|
366
|
+
dt = datetime.fromtimestamp(date_value)
|
|
367
|
+
if locale.startswith("en"):
|
|
368
|
+
return dt.strftime("%m/%d/%Y")
|
|
369
|
+
elif locale.startswith("de"):
|
|
370
|
+
return dt.strftime("%d.%m.%Y")
|
|
371
|
+
elif locale.startswith("fr"):
|
|
372
|
+
return dt.strftime("%d/%m/%Y")
|
|
373
|
+
else:
|
|
374
|
+
return dt.strftime("%Y-%m-%d")
|
|
375
|
+
|
|
376
|
+
# Handle None value gracefully
|
|
377
|
+
if value is None:
|
|
378
|
+
return ""
|
|
379
|
+
|
|
380
|
+
# Handle numeric formatting by locale
|
|
381
|
+
if locale.startswith("en"):
|
|
382
|
+
return f"{value:,.2f}"
|
|
383
|
+
elif locale.startswith("de"):
|
|
384
|
+
# German: 1.234,56
|
|
385
|
+
formatted = f"{value:,.2f}"
|
|
386
|
+
return formatted.replace(",", "X").replace(".", ",").replace("X", ".")
|
|
387
|
+
elif locale.startswith("fr"):
|
|
388
|
+
# French: 1 234,56 (narrow no-break space for thousands)
|
|
389
|
+
formatted = f"{value:,.2f}"
|
|
390
|
+
return formatted.replace(",", " ").replace(".", ",")
|
|
391
|
+
else:
|
|
392
|
+
return f"{value:,.2f}"
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
__all__ = [
|
|
396
|
+
"NumberFormatter",
|
|
397
|
+
"format_percentage",
|
|
398
|
+
"format_range",
|
|
399
|
+
"format_value",
|
|
400
|
+
"format_with_context",
|
|
401
|
+
"format_with_locale",
|
|
402
|
+
"format_with_units",
|
|
403
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Formatting standards."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Severity(Enum):
|
|
9
|
+
"""Severity levels."""
|
|
10
|
+
|
|
11
|
+
INFO = "info"
|
|
12
|
+
WARNING = "warning"
|
|
13
|
+
ERROR = "error"
|
|
14
|
+
CRITICAL = "critical"
|
|
15
|
+
SUCCESS = "success"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ColorScheme:
|
|
20
|
+
"""Color scheme for reports."""
|
|
21
|
+
|
|
22
|
+
primary: str = "#007ACC"
|
|
23
|
+
secondary: str = "#6C757D"
|
|
24
|
+
success: str = "#28A745"
|
|
25
|
+
warning: str = "#FFC107"
|
|
26
|
+
error: str = "#DC3545"
|
|
27
|
+
info: str = "#17A2B8"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class FormatStandards:
|
|
32
|
+
"""Formatting standards for reports."""
|
|
33
|
+
|
|
34
|
+
title_size: int = 18
|
|
35
|
+
heading_size: int = 14
|
|
36
|
+
body_size: int = 10
|
|
37
|
+
code_font: str = "Courier New"
|
|
38
|
+
body_font: str = "Arial"
|
|
39
|
+
colors: ColorScheme = field(default_factory=ColorScheme)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def apply_formatting_standards(content: Any, standards: FormatStandards | None = None) -> Any:
|
|
43
|
+
"""Apply formatting standards to content."""
|
|
44
|
+
if standards is None:
|
|
45
|
+
standards = FormatStandards()
|
|
46
|
+
# Placeholder implementation
|
|
47
|
+
return content
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"ColorScheme",
|
|
52
|
+
"FormatStandards",
|
|
53
|
+
"Severity",
|
|
54
|
+
"apply_formatting_standards",
|
|
55
|
+
]
|