oscura 0.7.0__py3-none-any.whl → 0.10.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 +19 -19
- oscura/analyzers/__init__.py +2 -0
- oscura/analyzers/digital/extraction.py +2 -3
- oscura/analyzers/digital/quality.py +1 -1
- oscura/analyzers/digital/timing.py +1 -1
- oscura/analyzers/eye/__init__.py +5 -1
- oscura/analyzers/eye/generation.py +501 -0
- oscura/analyzers/jitter/__init__.py +6 -6
- oscura/analyzers/jitter/timing.py +419 -0
- oscura/analyzers/patterns/__init__.py +94 -0
- oscura/analyzers/patterns/reverse_engineering.py +991 -0
- oscura/analyzers/power/__init__.py +35 -12
- oscura/analyzers/power/basic.py +3 -3
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +3 -3
- oscura/analyzers/signal_classification.py +529 -0
- oscura/analyzers/signal_integrity/sparams.py +3 -3
- oscura/analyzers/statistics/__init__.py +4 -0
- oscura/analyzers/statistics/basic.py +152 -0
- oscura/analyzers/statistics/correlation.py +47 -6
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/__init__.py +2 -0
- oscura/analyzers/waveform/measurements.py +329 -163
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +498 -54
- oscura/api/dsl/commands.py +15 -6
- oscura/api/server/templates/base.html +137 -146
- oscura/api/server/templates/export.html +84 -110
- oscura/api/server/templates/home.html +248 -267
- oscura/api/server/templates/protocols.html +44 -48
- oscura/api/server/templates/reports.html +27 -35
- oscura/api/server/templates/session_detail.html +68 -78
- oscura/api/server/templates/sessions.html +62 -72
- oscura/api/server/templates/waveforms.html +54 -64
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/dbc/generator.py +638 -23
- oscura/automotive/dtc/data.json +102 -17
- oscura/automotive/uds/decoder.py +99 -6
- oscura/cli/analyze.py +8 -2
- oscura/cli/batch.py +36 -5
- oscura/cli/characterize.py +18 -4
- oscura/cli/export.py +47 -5
- oscura/cli/main.py +2 -0
- oscura/cli/onboarding/wizard.py +10 -6
- oscura/cli/pipeline.py +585 -0
- oscura/cli/visualize.py +6 -4
- oscura/convenience.py +400 -32
- oscura/core/config/loader.py +0 -1
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +8 -2
- oscura/core/schemas/packet_format.json +24 -4
- oscura/core/schemas/protocol_definition.json +12 -2
- oscura/core/types.py +300 -199
- oscura/correlation/multi_protocol.py +1 -1
- oscura/export/legacy/__init__.py +11 -0
- oscura/export/legacy/wav.py +75 -0
- oscura/exporters/__init__.py +19 -0
- oscura/exporters/wireshark.py +809 -0
- oscura/hardware/acquisition/file.py +5 -19
- oscura/hardware/acquisition/saleae.py +10 -10
- oscura/hardware/acquisition/socketcan.py +4 -6
- oscura/hardware/acquisition/synthetic.py +1 -5
- oscura/hardware/acquisition/visa.py +6 -6
- oscura/hardware/security/side_channel_detector.py +5 -508
- oscura/inference/message_format.py +686 -1
- oscura/jupyter/display.py +2 -2
- oscura/jupyter/magic.py +3 -3
- oscura/loaders/__init__.py +17 -12
- oscura/loaders/binary.py +1 -1
- oscura/loaders/chipwhisperer.py +1 -2
- oscura/loaders/configurable.py +1 -1
- oscura/loaders/csv_loader.py +2 -2
- oscura/loaders/hdf5_loader.py +1 -1
- oscura/loaders/lazy.py +6 -1
- oscura/loaders/mmap_loader.py +0 -1
- oscura/loaders/numpy_loader.py +8 -7
- oscura/loaders/preprocessing.py +3 -5
- oscura/loaders/rigol.py +21 -7
- oscura/loaders/sigrok.py +2 -5
- oscura/loaders/tdms.py +3 -2
- oscura/loaders/tektronix.py +38 -32
- oscura/loaders/tss.py +20 -27
- oscura/loaders/vcd.py +13 -8
- oscura/loaders/wav.py +1 -6
- oscura/pipeline/__init__.py +76 -0
- oscura/pipeline/handlers/__init__.py +165 -0
- oscura/pipeline/handlers/analyzers.py +1045 -0
- oscura/pipeline/handlers/decoders.py +899 -0
- oscura/pipeline/handlers/exporters.py +1103 -0
- oscura/pipeline/handlers/filters.py +891 -0
- oscura/pipeline/handlers/loaders.py +640 -0
- oscura/pipeline/handlers/transforms.py +768 -0
- oscura/reporting/__init__.py +88 -1
- oscura/reporting/automation.py +348 -0
- oscura/reporting/citations.py +374 -0
- oscura/reporting/core.py +54 -0
- oscura/reporting/formatting/__init__.py +11 -0
- oscura/reporting/formatting/measurements.py +320 -0
- oscura/reporting/html.py +57 -0
- oscura/reporting/interpretation.py +431 -0
- oscura/reporting/summary.py +329 -0
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/reporting/visualization.py +542 -0
- oscura/side_channel/__init__.py +38 -57
- oscura/utils/builders/signal_builder.py +5 -5
- oscura/utils/comparison/compare.py +7 -9
- oscura/utils/comparison/golden.py +1 -1
- oscura/utils/filtering/convenience.py +2 -2
- oscura/utils/math/arithmetic.py +38 -62
- oscura/utils/math/interpolation.py +20 -20
- oscura/utils/pipeline/__init__.py +4 -17
- oscura/utils/progressive.py +1 -4
- oscura/utils/triggering/edge.py +1 -1
- oscura/utils/triggering/pattern.py +2 -2
- oscura/utils/triggering/pulse.py +2 -2
- oscura/utils/triggering/window.py +3 -3
- oscura/validation/hil_testing.py +11 -11
- oscura/visualization/__init__.py +47 -284
- oscura/visualization/batch.py +160 -0
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/__init__.py +2 -0
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +7 -8
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +788 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
- oscura/side_channel/dpa.py +0 -1025
- oscura/utils/optimization/__init__.py +0 -19
- oscura/utils/optimization/parallel.py +0 -443
- oscura/utils/optimization/search.py +0 -532
- oscura/utils/pipeline/base.py +0 -338
- oscura/utils/pipeline/composition.py +0 -248
- oscura/utils/pipeline/parallel.py +0 -449
- oscura/utils/pipeline/pipeline.py +0 -375
- oscura/utils/search/__init__.py +0 -16
- oscura/utils/search/anomaly.py +0 -424
- oscura/utils/search/context.py +0 -294
- oscura/utils/search/pattern.py +0 -288
- oscura/utils/storage/__init__.py +0 -61
- oscura/utils/storage/database.py +0 -1166
- oscura/visualization/accessibility.py +0 -526
- oscura/visualization/annotations.py +0 -371
- oscura/visualization/axis_scaling.py +0 -305
- oscura/visualization/colors.py +0 -451
- oscura/visualization/digital.py +0 -436
- oscura/visualization/eye.py +0 -571
- oscura/visualization/histogram.py +0 -281
- oscura/visualization/interactive.py +0 -1035
- oscura/visualization/jitter.py +0 -1042
- oscura/visualization/keyboard.py +0 -394
- oscura/visualization/layout.py +0 -400
- oscura/visualization/optimization.py +0 -1079
- oscura/visualization/palettes.py +0 -446
- oscura/visualization/power.py +0 -508
- oscura/visualization/power_extended.py +0 -955
- oscura/visualization/presets.py +0 -469
- oscura/visualization/protocols.py +0 -1246
- oscura/visualization/render.py +0 -223
- oscura/visualization/rendering.py +0 -444
- oscura/visualization/reverse_engineering.py +0 -838
- oscura/visualization/signal_integrity.py +0 -989
- oscura/visualization/specialized.py +0 -643
- oscura/visualization/spectral.py +0 -1226
- oscura/visualization/thumbnails.py +0 -340
- oscura/visualization/time_axis.py +0 -351
- oscura/visualization/waveform.py +0 -454
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
oscura/reporting/__init__.py
CHANGED
|
@@ -18,6 +18,12 @@ from oscura.reporting.auto_report import (
|
|
|
18
18
|
from oscura.reporting.auto_report import (
|
|
19
19
|
generate_report as generate_auto_report,
|
|
20
20
|
)
|
|
21
|
+
from oscura.reporting.automation import (
|
|
22
|
+
auto_interpret_results,
|
|
23
|
+
flag_anomalies,
|
|
24
|
+
identify_issues,
|
|
25
|
+
suggest_follow_up_analyses,
|
|
26
|
+
)
|
|
21
27
|
from oscura.reporting.batch import (
|
|
22
28
|
BatchReportResult,
|
|
23
29
|
aggregate_batch_measurements,
|
|
@@ -30,6 +36,13 @@ from oscura.reporting.chart_selection import (
|
|
|
30
36
|
get_axis_scaling,
|
|
31
37
|
recommend_chart_with_reasoning,
|
|
32
38
|
)
|
|
39
|
+
from oscura.reporting.citations import (
|
|
40
|
+
Citation,
|
|
41
|
+
CitationManager,
|
|
42
|
+
auto_cite_measurement,
|
|
43
|
+
get_standard_info,
|
|
44
|
+
list_available_standards,
|
|
45
|
+
)
|
|
33
46
|
from oscura.reporting.comparison import (
|
|
34
47
|
compare_waveforms,
|
|
35
48
|
generate_comparison_report,
|
|
@@ -73,8 +86,12 @@ from oscura.reporting.export import (
|
|
|
73
86
|
export_report,
|
|
74
87
|
)
|
|
75
88
|
from oscura.reporting.formatting import (
|
|
89
|
+
MeasurementFormatter,
|
|
76
90
|
NumberFormatter,
|
|
91
|
+
convert_to_measurement_dict,
|
|
77
92
|
format_margin,
|
|
93
|
+
format_measurement,
|
|
94
|
+
format_measurement_dict,
|
|
78
95
|
format_pass_fail,
|
|
79
96
|
format_value,
|
|
80
97
|
format_with_context,
|
|
@@ -82,6 +99,7 @@ from oscura.reporting.formatting import (
|
|
|
82
99
|
format_with_units,
|
|
83
100
|
)
|
|
84
101
|
from oscura.reporting.html import (
|
|
102
|
+
embed_plots,
|
|
85
103
|
generate_html_report,
|
|
86
104
|
save_html_report,
|
|
87
105
|
)
|
|
@@ -89,6 +107,16 @@ from oscura.reporting.index import (
|
|
|
89
107
|
IndexGenerator,
|
|
90
108
|
TemplateEngine,
|
|
91
109
|
)
|
|
110
|
+
from oscura.reporting.interpretation import (
|
|
111
|
+
ComplianceStatus,
|
|
112
|
+
MeasurementInterpretation,
|
|
113
|
+
QualityLevel,
|
|
114
|
+
compliance_check,
|
|
115
|
+
generate_finding,
|
|
116
|
+
interpret_measurement,
|
|
117
|
+
interpret_results_batch,
|
|
118
|
+
quality_score,
|
|
119
|
+
)
|
|
92
120
|
from oscura.reporting.multichannel import (
|
|
93
121
|
generate_multichannel_report,
|
|
94
122
|
)
|
|
@@ -127,6 +155,12 @@ from oscura.reporting.standards import (
|
|
|
127
155
|
format_executive_summary_html,
|
|
128
156
|
generate_executive_summary,
|
|
129
157
|
)
|
|
158
|
+
from oscura.reporting.summary import (
|
|
159
|
+
ExecutiveSummarySection,
|
|
160
|
+
identify_key_findings,
|
|
161
|
+
recommendations_from_findings,
|
|
162
|
+
summarize_measurements,
|
|
163
|
+
)
|
|
130
164
|
from oscura.reporting.summary_generator import (
|
|
131
165
|
Finding,
|
|
132
166
|
Summary,
|
|
@@ -144,6 +178,10 @@ from oscura.reporting.template_system import (
|
|
|
144
178
|
list_templates,
|
|
145
179
|
load_template,
|
|
146
180
|
)
|
|
181
|
+
from oscura.reporting.visualization import (
|
|
182
|
+
IEEEPlotGenerator,
|
|
183
|
+
PlotStyler,
|
|
184
|
+
)
|
|
147
185
|
|
|
148
186
|
__all__ = [
|
|
149
187
|
# Comprehensive Analysis Report API (CAR-001 through CAR-007)
|
|
@@ -160,30 +198,46 @@ __all__ = [
|
|
|
160
198
|
"BatchReportResult",
|
|
161
199
|
# Chart Selection (REPORT-028)
|
|
162
200
|
"ChartType",
|
|
201
|
+
# Citations (NEW)
|
|
202
|
+
"Citation",
|
|
203
|
+
"CitationManager",
|
|
163
204
|
# Standards (REPORT-001, REPORT-002, REPORT-004)
|
|
164
205
|
"ColorScheme",
|
|
206
|
+
# Compliance (NEW)
|
|
207
|
+
"ComplianceStatus",
|
|
165
208
|
"DataOutputConfig",
|
|
166
209
|
"DomainConfig",
|
|
167
210
|
# Enhanced Reports (Feature 6)
|
|
168
211
|
"EnhancedReportConfig",
|
|
169
212
|
"EnhancedReportGenerator",
|
|
170
213
|
"ExecutiveSummary",
|
|
214
|
+
# Executive Summary (NEW)
|
|
215
|
+
"ExecutiveSummarySection",
|
|
171
216
|
# Summary Generation
|
|
172
217
|
"Finding",
|
|
173
218
|
"FormatStandards",
|
|
219
|
+
# IEEE Visualization (NEW)
|
|
220
|
+
"IEEEPlotGenerator",
|
|
174
221
|
"IndexGenerator",
|
|
175
222
|
"InputType",
|
|
223
|
+
# Formatting (REPORT-026)
|
|
224
|
+
"MeasurementFormatter",
|
|
225
|
+
# Measurement Interpretation (NEW)
|
|
226
|
+
"MeasurementInterpretation",
|
|
176
227
|
# Multi-format (REPORT-010)
|
|
177
228
|
"MultiFormatRenderer",
|
|
178
|
-
# Formatting (REPORT-026)
|
|
179
229
|
"NumberFormatter",
|
|
180
230
|
"OutputManager",
|
|
181
231
|
# PPTX Export (REPORT-023)
|
|
182
232
|
"PPTXPresentation",
|
|
183
233
|
"PPTXSlide",
|
|
184
234
|
"PlotGenerator",
|
|
235
|
+
# Plot Styling (NEW)
|
|
236
|
+
"PlotStyler",
|
|
185
237
|
"ProgressCallback",
|
|
186
238
|
"ProgressInfo",
|
|
239
|
+
# Quality Assessment (NEW)
|
|
240
|
+
"QualityLevel",
|
|
187
241
|
# Core
|
|
188
242
|
"Report",
|
|
189
243
|
"ReportConfig",
|
|
@@ -199,12 +253,18 @@ __all__ = [
|
|
|
199
253
|
"VisualEmphasis",
|
|
200
254
|
"aggregate_batch_measurements",
|
|
201
255
|
"analyze",
|
|
256
|
+
# Automation (NEW)
|
|
257
|
+
"auto_cite_measurement",
|
|
258
|
+
"auto_interpret_results",
|
|
202
259
|
"auto_select_chart",
|
|
203
260
|
# Export
|
|
204
261
|
"batch_export_formats",
|
|
205
262
|
"batch_report",
|
|
206
263
|
# Comparison
|
|
207
264
|
"compare_waveforms",
|
|
265
|
+
# Compliance (NEW)
|
|
266
|
+
"compliance_check",
|
|
267
|
+
"convert_to_measurement_dict",
|
|
208
268
|
# Tables
|
|
209
269
|
"create_comparison_table",
|
|
210
270
|
# Sections
|
|
@@ -220,12 +280,18 @@ __all__ = [
|
|
|
220
280
|
"create_violations_section",
|
|
221
281
|
# Multi-format (REPORT-010)
|
|
222
282
|
"detect_format_from_extension",
|
|
283
|
+
# HTML Generation
|
|
284
|
+
"embed_plots",
|
|
223
285
|
"export_multiple_reports",
|
|
224
286
|
"export_pptx",
|
|
225
287
|
"export_report",
|
|
288
|
+
# Anomaly Detection (NEW)
|
|
289
|
+
"flag_anomalies",
|
|
226
290
|
"format_batch_summary_table",
|
|
227
291
|
"format_executive_summary_html",
|
|
228
292
|
"format_margin",
|
|
293
|
+
"format_measurement",
|
|
294
|
+
"format_measurement_dict",
|
|
229
295
|
"format_pass_fail",
|
|
230
296
|
"format_value",
|
|
231
297
|
"format_with_context",
|
|
@@ -235,6 +301,8 @@ __all__ = [
|
|
|
235
301
|
"generate_batch_report",
|
|
236
302
|
"generate_comparison_report",
|
|
237
303
|
"generate_executive_summary",
|
|
304
|
+
# Findings (NEW)
|
|
305
|
+
"generate_finding",
|
|
238
306
|
# HTML Generation
|
|
239
307
|
"generate_html_report",
|
|
240
308
|
# Multi-Channel
|
|
@@ -246,12 +314,31 @@ __all__ = [
|
|
|
246
314
|
"generate_summary",
|
|
247
315
|
"get_available_analyses",
|
|
248
316
|
"get_axis_scaling",
|
|
317
|
+
# Citations (NEW)
|
|
318
|
+
"get_standard_info",
|
|
319
|
+
# Issue Identification (NEW)
|
|
320
|
+
"identify_issues",
|
|
321
|
+
# Key Findings (NEW)
|
|
322
|
+
"identify_key_findings",
|
|
323
|
+
# Interpretation (NEW)
|
|
324
|
+
"interpret_measurement",
|
|
325
|
+
"interpret_results_batch",
|
|
326
|
+
# Standards (NEW)
|
|
327
|
+
"list_available_standards",
|
|
249
328
|
"list_templates",
|
|
250
329
|
"load_template",
|
|
330
|
+
# Quality (NEW)
|
|
331
|
+
"quality_score",
|
|
251
332
|
"recommend_chart_with_reasoning",
|
|
333
|
+
# Recommendations (NEW)
|
|
334
|
+
"recommendations_from_findings",
|
|
252
335
|
"register_plot",
|
|
253
336
|
# Multi-format (REPORT-010)
|
|
254
337
|
"render_all_formats",
|
|
255
338
|
"save_html_report",
|
|
256
339
|
"save_pdf_report",
|
|
340
|
+
# Follow-up Analysis (NEW)
|
|
341
|
+
"suggest_follow_up_analyses",
|
|
342
|
+
# Summary (NEW)
|
|
343
|
+
"summarize_measurements",
|
|
257
344
|
]
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""Automated analysis and anomaly detection for reports.
|
|
2
|
+
|
|
3
|
+
This module provides automated interpretation of results, anomaly flagging,
|
|
4
|
+
and intelligent recommendations generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from oscura.reporting.interpretation import MeasurementInterpretation, interpret_measurement
|
|
14
|
+
from oscura.reporting.summary import (
|
|
15
|
+
identify_key_findings,
|
|
16
|
+
recommendations_from_findings,
|
|
17
|
+
summarize_measurements,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def auto_interpret_results(results: dict[str, Any]) -> dict[str, MeasurementInterpretation]:
|
|
22
|
+
"""Automatically interpret all results in a dictionary.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
results: Dictionary of measurement results. Can be nested.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary mapping measurement names to interpretations.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> results = {"snr": 45.2, "bandwidth": 1e9, "jitter": 50e-12}
|
|
32
|
+
>>> interpretations = auto_interpret_results(results)
|
|
33
|
+
>>> len(interpretations) >= 3
|
|
34
|
+
True
|
|
35
|
+
"""
|
|
36
|
+
interpretations = {}
|
|
37
|
+
|
|
38
|
+
for key, value in results.items():
|
|
39
|
+
if isinstance(value, dict):
|
|
40
|
+
# Nested dictionary - recurse
|
|
41
|
+
if "value" in value:
|
|
42
|
+
# This is a measurement dict with value and metadata
|
|
43
|
+
meas_value = value["value"]
|
|
44
|
+
units = value.get("units", "")
|
|
45
|
+
spec_min = value.get("spec_min")
|
|
46
|
+
spec_max = value.get("spec_max")
|
|
47
|
+
|
|
48
|
+
interp = interpret_measurement(key, meas_value, units, spec_min, spec_max)
|
|
49
|
+
interpretations[key] = interp
|
|
50
|
+
else:
|
|
51
|
+
# Recurse into nested dict
|
|
52
|
+
nested = auto_interpret_results(value)
|
|
53
|
+
interpretations.update(nested)
|
|
54
|
+
elif isinstance(value, int | float):
|
|
55
|
+
# Simple numeric value
|
|
56
|
+
interp = interpret_measurement(key, value)
|
|
57
|
+
interpretations[key] = interp
|
|
58
|
+
|
|
59
|
+
return interpretations
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def generate_summary(
|
|
63
|
+
results: dict[str, Any],
|
|
64
|
+
include_recommendations: bool = True,
|
|
65
|
+
) -> dict[str, Any]:
|
|
66
|
+
"""Generate comprehensive automated summary of results.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
results: Dictionary of measurement results.
|
|
70
|
+
include_recommendations: Whether to include recommendations.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Summary dictionary with statistics, findings, and recommendations.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> results = {"snr": 45.2, "thd": -60.5, "bandwidth": 1e9}
|
|
77
|
+
>>> summary = generate_summary(results)
|
|
78
|
+
>>> "statistics" in summary
|
|
79
|
+
True
|
|
80
|
+
>>> "key_findings" in summary
|
|
81
|
+
True
|
|
82
|
+
"""
|
|
83
|
+
# Flatten results to simple dict
|
|
84
|
+
flat_results = _flatten_results(results)
|
|
85
|
+
|
|
86
|
+
# Generate statistics
|
|
87
|
+
statistics = summarize_measurements(flat_results)
|
|
88
|
+
|
|
89
|
+
# Interpret results
|
|
90
|
+
interpretations = auto_interpret_results(results)
|
|
91
|
+
|
|
92
|
+
# Identify key findings
|
|
93
|
+
key_findings = identify_key_findings(flat_results, interpretations, max_findings=10)
|
|
94
|
+
|
|
95
|
+
# Generate recommendations
|
|
96
|
+
recommendations = []
|
|
97
|
+
if include_recommendations:
|
|
98
|
+
recommendations = recommendations_from_findings(flat_results, interpretations)
|
|
99
|
+
|
|
100
|
+
# Flag anomalies
|
|
101
|
+
anomalies = flag_anomalies(flat_results)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"statistics": statistics,
|
|
105
|
+
"key_findings": key_findings,
|
|
106
|
+
"recommendations": recommendations,
|
|
107
|
+
"anomalies": anomalies,
|
|
108
|
+
"interpretations": {k: v.__dict__ for k, v in interpretations.items()},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def flag_anomalies(
|
|
113
|
+
measurements: dict[str, Any],
|
|
114
|
+
threshold_std: float = 3.0,
|
|
115
|
+
) -> list[dict[str, Any]]:
|
|
116
|
+
"""Flag anomalous measurements using statistical analysis.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
measurements: Dictionary of measurements.
|
|
120
|
+
threshold_std: Number of standard deviations for outlier detection.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of anomaly dictionaries with name, value, and reason.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> measurements = {"m1": 10, "m2": 11, "m3": 12, "m4": 100}
|
|
127
|
+
>>> anomalies = flag_anomalies(measurements, threshold_std=2.0)
|
|
128
|
+
>>> len(anomalies) >= 1
|
|
129
|
+
True
|
|
130
|
+
"""
|
|
131
|
+
anomalies: list[dict[str, Any]] = []
|
|
132
|
+
|
|
133
|
+
# Extract numeric values
|
|
134
|
+
numeric_measurements = {k: v for k, v in measurements.items() if isinstance(v, int | float)}
|
|
135
|
+
|
|
136
|
+
if len(numeric_measurements) < 3:
|
|
137
|
+
# Not enough data for statistical analysis
|
|
138
|
+
return anomalies
|
|
139
|
+
|
|
140
|
+
values = np.array(list(numeric_measurements.values()))
|
|
141
|
+
mean = np.mean(values)
|
|
142
|
+
std = np.std(values)
|
|
143
|
+
|
|
144
|
+
if std == 0:
|
|
145
|
+
return anomalies
|
|
146
|
+
|
|
147
|
+
# Check each measurement
|
|
148
|
+
for name, value in numeric_measurements.items():
|
|
149
|
+
z_score = abs((value - mean) / std)
|
|
150
|
+
|
|
151
|
+
if z_score > threshold_std:
|
|
152
|
+
anomalies.append(
|
|
153
|
+
{
|
|
154
|
+
"name": name,
|
|
155
|
+
"value": value,
|
|
156
|
+
"z_score": float(z_score),
|
|
157
|
+
"reason": f"Value is {z_score:.1f} std devs from mean ({mean:.3e})",
|
|
158
|
+
"severity": "high" if z_score > 5 else "medium",
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Domain-specific anomaly checks
|
|
163
|
+
for name, value in measurements.items():
|
|
164
|
+
if not isinstance(value, int | float):
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
# Negative SNR
|
|
168
|
+
if "snr" in name.lower() and value < 0:
|
|
169
|
+
anomalies.append(
|
|
170
|
+
{
|
|
171
|
+
"name": name,
|
|
172
|
+
"value": value,
|
|
173
|
+
"reason": "Negative SNR indicates signal weaker than noise",
|
|
174
|
+
"severity": "critical",
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Negative bandwidth
|
|
179
|
+
if "bandwidth" in name.lower() and value <= 0:
|
|
180
|
+
anomalies.append(
|
|
181
|
+
{
|
|
182
|
+
"name": name,
|
|
183
|
+
"value": value,
|
|
184
|
+
"reason": "Invalid negative or zero bandwidth",
|
|
185
|
+
"severity": "critical",
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Extremely high jitter (>1 second)
|
|
190
|
+
if "jitter" in name.lower() and value > 1.0:
|
|
191
|
+
anomalies.append(
|
|
192
|
+
{
|
|
193
|
+
"name": name,
|
|
194
|
+
"value": value,
|
|
195
|
+
"reason": "Unrealistically high jitter value (>1 second)",
|
|
196
|
+
"severity": "critical",
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Invalid power factor (must be -1 to +1)
|
|
201
|
+
if "power_factor" in name.lower() and abs(value) > 1.0:
|
|
202
|
+
anomalies.append(
|
|
203
|
+
{
|
|
204
|
+
"name": name,
|
|
205
|
+
"value": value,
|
|
206
|
+
"reason": "Power factor must be between -1 and +1",
|
|
207
|
+
"severity": "critical",
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return anomalies
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def suggest_follow_up_analyses(
|
|
215
|
+
measurements: dict[str, Any],
|
|
216
|
+
interpretations: dict[str, MeasurementInterpretation] | None = None,
|
|
217
|
+
) -> list[str]:
|
|
218
|
+
"""Suggest follow-up analyses based on measurement results.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
measurements: Dictionary of measurements.
|
|
222
|
+
interpretations: Optional measurement interpretations.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of suggested analysis descriptions.
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
>>> measurements = {"snr": 15.5, "thd": -40}
|
|
229
|
+
>>> suggestions = suggest_follow_up_analyses(measurements)
|
|
230
|
+
>>> len(suggestions) > 0
|
|
231
|
+
True
|
|
232
|
+
"""
|
|
233
|
+
suggestions = []
|
|
234
|
+
|
|
235
|
+
# Low SNR - suggest noise analysis
|
|
236
|
+
if "snr" in measurements:
|
|
237
|
+
snr = measurements["snr"]
|
|
238
|
+
if isinstance(snr, int | float) and snr < 30:
|
|
239
|
+
suggestions.append("Perform detailed noise analysis to identify noise sources")
|
|
240
|
+
suggestions.append("Analyze frequency spectrum for interference peaks")
|
|
241
|
+
|
|
242
|
+
# High THD - suggest harmonic analysis
|
|
243
|
+
if "thd" in measurements:
|
|
244
|
+
thd = measurements["thd"]
|
|
245
|
+
if isinstance(thd, int | float) and abs(thd) < 40: # THD typically negative dB
|
|
246
|
+
suggestions.append("Perform harmonic analysis to identify distortion sources")
|
|
247
|
+
suggestions.append("Check for clipping or saturation in signal path")
|
|
248
|
+
|
|
249
|
+
# Jitter present - suggest jitter decomposition
|
|
250
|
+
if any("jitter" in k.lower() for k in measurements):
|
|
251
|
+
suggestions.append("Perform jitter decomposition (RJ, DJ, PJ) for root cause analysis")
|
|
252
|
+
suggestions.append("Analyze clock quality and investigate timing sources")
|
|
253
|
+
|
|
254
|
+
# Power measurements - suggest power quality
|
|
255
|
+
if any("power" in k.lower() for k in measurements):
|
|
256
|
+
suggestions.append("Perform power quality analysis (harmonics, flicker, sags/swells)")
|
|
257
|
+
|
|
258
|
+
# Eye diagram quality issues
|
|
259
|
+
if interpretations:
|
|
260
|
+
poor_quality = [
|
|
261
|
+
k for k, v in interpretations.items() if v.quality.value in ("marginal", "poor")
|
|
262
|
+
]
|
|
263
|
+
if poor_quality:
|
|
264
|
+
suggestions.append(
|
|
265
|
+
f"Investigate {len(poor_quality)} marginal/poor measurements: "
|
|
266
|
+
f"{', '.join(poor_quality[:3])}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return suggestions
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def identify_issues(
|
|
273
|
+
measurements: dict[str, Any],
|
|
274
|
+
anomalies: list[dict[str, Any]],
|
|
275
|
+
) -> list[dict[str, Any]]:
|
|
276
|
+
"""Identify issues from measurements and anomalies.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
measurements: Dictionary of measurements.
|
|
280
|
+
anomalies: List of detected anomalies.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
List of issue dictionaries with severity and description.
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> measurements = {"snr": 10}
|
|
287
|
+
>>> anomalies = [{"name": "snr", "value": 10, "severity": "high"}]
|
|
288
|
+
>>> issues = identify_issues(measurements, anomalies)
|
|
289
|
+
>>> len(issues) > 0
|
|
290
|
+
True
|
|
291
|
+
"""
|
|
292
|
+
issues = []
|
|
293
|
+
|
|
294
|
+
# Add critical anomalies as issues
|
|
295
|
+
for anomaly in anomalies:
|
|
296
|
+
if anomaly.get("severity") == "critical":
|
|
297
|
+
issues.append(
|
|
298
|
+
{
|
|
299
|
+
"severity": "critical",
|
|
300
|
+
"measurement": anomaly["name"],
|
|
301
|
+
"description": anomaly["reason"],
|
|
302
|
+
"value": anomaly["value"],
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Domain-specific issue detection
|
|
307
|
+
if "snr" in measurements:
|
|
308
|
+
snr = measurements["snr"]
|
|
309
|
+
if isinstance(snr, int | float) and snr < 20:
|
|
310
|
+
issues.append(
|
|
311
|
+
{
|
|
312
|
+
"severity": "high",
|
|
313
|
+
"measurement": "snr",
|
|
314
|
+
"description": f"Low SNR ({snr:.1f} dB) may impact measurement accuracy",
|
|
315
|
+
"value": snr,
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if "bandwidth" in measurements:
|
|
320
|
+
bw = measurements["bandwidth"]
|
|
321
|
+
if isinstance(bw, int | float) and bw < 10e6:
|
|
322
|
+
issues.append(
|
|
323
|
+
{
|
|
324
|
+
"severity": "medium",
|
|
325
|
+
"measurement": "bandwidth",
|
|
326
|
+
"description": f"Limited bandwidth ({bw / 1e6:.1f} MHz) may restrict signal fidelity",
|
|
327
|
+
"value": bw,
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return issues
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _flatten_results(results: dict[str, Any]) -> dict[str, Any]:
|
|
335
|
+
"""Flatten nested results dictionary."""
|
|
336
|
+
flat = {}
|
|
337
|
+
|
|
338
|
+
for key, value in results.items():
|
|
339
|
+
if isinstance(value, dict):
|
|
340
|
+
if "value" in value:
|
|
341
|
+
flat[key] = value["value"]
|
|
342
|
+
else:
|
|
343
|
+
nested = _flatten_results(value)
|
|
344
|
+
flat.update(nested)
|
|
345
|
+
elif isinstance(value, int | float | str):
|
|
346
|
+
flat[key] = value
|
|
347
|
+
|
|
348
|
+
return flat
|