oscura 0.6.0__py3-none-any.whl → 0.8.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.
Files changed (38) hide show
  1. oscura/__init__.py +1 -1
  2. oscura/analyzers/eye/__init__.py +5 -1
  3. oscura/analyzers/eye/generation.py +501 -0
  4. oscura/analyzers/jitter/__init__.py +6 -6
  5. oscura/analyzers/jitter/timing.py +419 -0
  6. oscura/analyzers/patterns/__init__.py +28 -0
  7. oscura/analyzers/patterns/reverse_engineering.py +991 -0
  8. oscura/analyzers/power/__init__.py +35 -12
  9. oscura/analyzers/statistics/__init__.py +4 -0
  10. oscura/analyzers/statistics/basic.py +149 -0
  11. oscura/analyzers/statistics/correlation.py +47 -6
  12. oscura/analyzers/waveform/__init__.py +2 -0
  13. oscura/analyzers/waveform/measurements.py +145 -23
  14. oscura/analyzers/waveform/spectral.py +361 -8
  15. oscura/automotive/__init__.py +1 -1
  16. oscura/core/config/loader.py +0 -1
  17. oscura/core/types.py +108 -0
  18. oscura/loaders/__init__.py +12 -4
  19. oscura/loaders/tss.py +456 -0
  20. oscura/reporting/__init__.py +88 -1
  21. oscura/reporting/automation.py +348 -0
  22. oscura/reporting/citations.py +374 -0
  23. oscura/reporting/core.py +54 -0
  24. oscura/reporting/formatting/__init__.py +11 -0
  25. oscura/reporting/formatting/measurements.py +279 -0
  26. oscura/reporting/html.py +57 -0
  27. oscura/reporting/interpretation.py +431 -0
  28. oscura/reporting/summary.py +329 -0
  29. oscura/reporting/visualization.py +542 -0
  30. oscura/visualization/__init__.py +2 -1
  31. oscura/visualization/batch.py +521 -0
  32. oscura/workflows/__init__.py +2 -0
  33. oscura/workflows/waveform.py +783 -0
  34. {oscura-0.6.0.dist-info → oscura-0.8.0.dist-info}/METADATA +37 -19
  35. {oscura-0.6.0.dist-info → oscura-0.8.0.dist-info}/RECORD +38 -26
  36. {oscura-0.6.0.dist-info → oscura-0.8.0.dist-info}/WHEEL +0 -0
  37. {oscura-0.6.0.dist-info → oscura-0.8.0.dist-info}/entry_points.txt +0 -0
  38. {oscura-0.6.0.dist-info → oscura-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,374 @@
1
+ """IEEE standard citation system for professional reports.
2
+
3
+ This module provides automatic citation management for IEEE standards
4
+ referenced in analysis reports, with proper DOI links and bibliographic
5
+ formatting.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Any
12
+
13
+ # IEEE Standards Database with DOIs
14
+ IEEE_STANDARDS: dict[str, dict[str, str]] = {
15
+ "181": {
16
+ "title": "IEEE Standard for Transitions, Pulses, and Related Waveforms",
17
+ "year": "2011",
18
+ "doi": "10.1109/IEEESTD.2011.6016359",
19
+ "url": "https://doi.org/10.1109/IEEESTD.2011.6016359",
20
+ "full_name": "IEEE Std 181-2011",
21
+ "scope": "Pulse measurement terminology, definitions, and algorithms",
22
+ },
23
+ "1241": {
24
+ "title": "IEEE Standard for Terminology and Test Methods for Analog-to-Digital Converters",
25
+ "year": "2010",
26
+ "doi": "10.1109/IEEESTD.2011.5692956",
27
+ "url": "https://doi.org/10.1109/IEEESTD.2011.5692956",
28
+ "full_name": "IEEE Std 1241-2010",
29
+ "scope": "ADC characterization including SNR, ENOB, and dynamic performance",
30
+ },
31
+ "1057": {
32
+ "title": "IEEE Standard for Digitizing Waveform Recorders",
33
+ "year": "2017",
34
+ "doi": "10.1109/IEEESTD.2017.8291139",
35
+ "url": "https://doi.org/10.1109/IEEESTD.2017.8291139",
36
+ "full_name": "IEEE Std 1057-2017",
37
+ "scope": "Oscilloscope and digitizer performance specifications",
38
+ },
39
+ "2414": {
40
+ "title": "IEEE Standard for Jitter and Phase Noise",
41
+ "year": "2020",
42
+ "doi": "10.1109/IEEESTD.2020.9268529",
43
+ "url": "https://doi.org/10.1109/IEEESTD.2020.9268529",
44
+ "full_name": "IEEE Std 2414-2020",
45
+ "scope": "Jitter measurement and analysis methodologies",
46
+ },
47
+ "1459": {
48
+ "title": "IEEE Standard Definitions for the Measurement of Electric Power Quantities Under Sinusoidal, Nonsinusoidal, Balanced, or Unbalanced Conditions",
49
+ "year": "2010",
50
+ "doi": "10.1109/IEEESTD.2010.5439063",
51
+ "url": "https://doi.org/10.1109/IEEESTD.2010.5439063",
52
+ "full_name": "IEEE Std 1459-2010",
53
+ "scope": "Power measurement definitions and algorithms",
54
+ },
55
+ "829": {
56
+ "title": "IEEE Standard for Software and System Test Documentation",
57
+ "year": "2008",
58
+ "doi": "10.1109/IEEESTD.2008.4578383",
59
+ "url": "https://doi.org/10.1109/IEEESTD.2008.4578383",
60
+ "full_name": "IEEE Std 829-2008",
61
+ "scope": "Test documentation and reporting standards",
62
+ },
63
+ }
64
+
65
+
66
+ @dataclass
67
+ class Citation:
68
+ """A citation to an IEEE standard.
69
+
70
+ Attributes:
71
+ standard_id: IEEE standard number (e.g., "181", "1241").
72
+ section: Specific section if applicable.
73
+ context: Context where citation is used.
74
+ page: Page number if applicable.
75
+ """
76
+
77
+ standard_id: str
78
+ section: str | None = None
79
+ context: str = ""
80
+ page: int | None = None
81
+
82
+ def format_inline(self) -> str:
83
+ """Format citation for inline use.
84
+
85
+ Returns:
86
+ Formatted citation string (e.g., "[IEEE 181]" or "[IEEE 181 §3.1]").
87
+
88
+ Example:
89
+ >>> citation = Citation("181", section="3.1")
90
+ >>> citation.format_inline()
91
+ '[IEEE 181 §3.1]'
92
+ """
93
+ if self.section:
94
+ return f"[IEEE {self.standard_id} §{self.section}]"
95
+ return f"[IEEE {self.standard_id}]"
96
+
97
+ def format_bibliography(self) -> str:
98
+ """Format citation for bibliography/references section.
99
+
100
+ Returns:
101
+ Formatted bibliography entry with DOI link.
102
+
103
+ Example:
104
+ >>> citation = Citation("181")
105
+ >>> print(citation.format_bibliography())
106
+ IEEE Std 181-2011, "IEEE Standard for Transitions, Pulses, and Related Waveforms," 2011. DOI: 10.1109/IEEESTD.2011.6016359
107
+ """
108
+ if self.standard_id not in IEEE_STANDARDS:
109
+ return f"[IEEE {self.standard_id}] - Standard not in database"
110
+
111
+ std = IEEE_STANDARDS[self.standard_id]
112
+ return f'{std["full_name"]}, "{std["title"]}," {std["year"]}. DOI: {std["doi"]}'
113
+
114
+ def get_url(self) -> str:
115
+ """Get DOI URL for the standard.
116
+
117
+ Returns:
118
+ DOI URL string or empty string if not found.
119
+
120
+ Example:
121
+ >>> citation = Citation("181")
122
+ >>> citation.get_url()
123
+ 'https://doi.org/10.1109/IEEESTD.2011.6016359'
124
+ """
125
+ if self.standard_id in IEEE_STANDARDS:
126
+ return IEEE_STANDARDS[self.standard_id]["url"]
127
+ return ""
128
+
129
+
130
+ @dataclass
131
+ class CitationManager:
132
+ """Manages citations in a report.
133
+
134
+ Tracks all citations used in a report and generates bibliographies.
135
+ Automatically deduplicates and sorts citations.
136
+
137
+ Attributes:
138
+ citations: List of all citations in the report.
139
+
140
+ Example:
141
+ >>> manager = CitationManager()
142
+ >>> manager.add_citation("181", section="3.1", context="Rise time measurement")
143
+ >>> manager.add_citation("1241", context="SNR calculation")
144
+ >>> html = manager.generate_bibliography_html()
145
+ """
146
+
147
+ citations: list[Citation] = field(default_factory=list)
148
+
149
+ def add_citation(
150
+ self,
151
+ standard_id: str,
152
+ section: str | None = None,
153
+ context: str = "",
154
+ page: int | None = None,
155
+ ) -> Citation:
156
+ """Add a citation to the report.
157
+
158
+ Args:
159
+ standard_id: IEEE standard number (e.g., "181").
160
+ section: Specific section if applicable.
161
+ context: Context where citation is used.
162
+ page: Page number if applicable.
163
+
164
+ Returns:
165
+ The created Citation object.
166
+
167
+ Example:
168
+ >>> manager = CitationManager()
169
+ >>> cite = manager.add_citation("181", section="3.1")
170
+ >>> cite.format_inline()
171
+ '[IEEE 181 §3.1]'
172
+ """
173
+ citation = Citation(
174
+ standard_id=standard_id,
175
+ section=section,
176
+ context=context,
177
+ page=page,
178
+ )
179
+ self.citations.append(citation)
180
+ return citation
181
+
182
+ def get_unique_citations(self) -> list[Citation]:
183
+ """Get unique citations (deduplicated by standard_id).
184
+
185
+ Returns:
186
+ List of unique citations sorted by standard ID.
187
+
188
+ Example:
189
+ >>> manager = CitationManager()
190
+ >>> manager.add_citation("181")
191
+ >>> manager.add_citation("181", section="3.1")
192
+ >>> manager.add_citation("1241")
193
+ >>> len(manager.get_unique_citations())
194
+ 2
195
+ """
196
+ seen: set[str] = set()
197
+ unique: list[Citation] = []
198
+
199
+ for citation in sorted(self.citations, key=lambda c: c.standard_id):
200
+ if citation.standard_id not in seen:
201
+ seen.add(citation.standard_id)
202
+ unique.append(citation)
203
+
204
+ return unique
205
+
206
+ def generate_bibliography_markdown(self) -> str:
207
+ """Generate bibliography in Markdown format.
208
+
209
+ Returns:
210
+ Markdown-formatted bibliography section.
211
+
212
+ Example:
213
+ >>> manager = CitationManager()
214
+ >>> manager.add_citation("181")
215
+ >>> md = manager.generate_bibliography_markdown()
216
+ >>> "IEEE 181" in md
217
+ True
218
+ """
219
+ if not self.citations:
220
+ return ""
221
+
222
+ lines = ["## References", ""]
223
+
224
+ for citation in self.get_unique_citations():
225
+ lines.append(f"- {citation.format_bibliography()}")
226
+
227
+ return "\n".join(lines)
228
+
229
+ def generate_bibliography_html(self) -> str:
230
+ """Generate bibliography in HTML format with DOI links.
231
+
232
+ Returns:
233
+ HTML-formatted bibliography section.
234
+
235
+ Example:
236
+ >>> manager = CitationManager()
237
+ >>> manager.add_citation("181")
238
+ >>> html = manager.generate_bibliography_html()
239
+ >>> "doi.org" in html
240
+ True
241
+ """
242
+ if not self.citations:
243
+ return ""
244
+
245
+ lines = ['<div class="references">', "<h2>References</h2>", "<ol>"]
246
+
247
+ for citation in self.get_unique_citations():
248
+ url = citation.get_url()
249
+ bib = citation.format_bibliography()
250
+
251
+ if url:
252
+ lines.append(f'<li><a href="{url}" target="_blank">{bib}</a></li>')
253
+ else:
254
+ lines.append(f"<li>{bib}</li>")
255
+
256
+ lines.extend(["</ol>", "</div>"])
257
+ return "\n".join(lines)
258
+
259
+ def get_citation_context(self) -> dict[str, list[str]]:
260
+ """Get contexts where each standard was cited.
261
+
262
+ Returns:
263
+ Dictionary mapping standard IDs to list of contexts.
264
+
265
+ Example:
266
+ >>> manager = CitationManager()
267
+ >>> manager.add_citation("181", context="Rise time")
268
+ >>> manager.add_citation("181", context="Fall time")
269
+ >>> contexts = manager.get_citation_context()
270
+ >>> len(contexts["181"])
271
+ 2
272
+ """
273
+ context_map: dict[str, list[str]] = {}
274
+
275
+ for citation in self.citations:
276
+ if citation.context:
277
+ if citation.standard_id not in context_map:
278
+ context_map[citation.standard_id] = []
279
+ context_map[citation.standard_id].append(citation.context)
280
+
281
+ return context_map
282
+
283
+
284
+ def get_standard_info(standard_id: str) -> dict[str, Any]:
285
+ """Get information about an IEEE standard.
286
+
287
+ Args:
288
+ standard_id: IEEE standard number (e.g., "181").
289
+
290
+ Returns:
291
+ Dictionary with standard metadata or empty dict if not found.
292
+
293
+ Example:
294
+ >>> info = get_standard_info("181")
295
+ >>> info["year"]
296
+ '2011'
297
+ >>> "pulse" in info["scope"].lower()
298
+ True
299
+ """
300
+ return IEEE_STANDARDS.get(standard_id, {})
301
+
302
+
303
+ def list_available_standards() -> list[str]:
304
+ """List all IEEE standards in the citation database.
305
+
306
+ Returns:
307
+ List of standard IDs.
308
+
309
+ Example:
310
+ >>> standards = list_available_standards()
311
+ >>> "181" in standards
312
+ True
313
+ >>> len(standards) >= 5
314
+ True
315
+ """
316
+ return sorted(IEEE_STANDARDS.keys())
317
+
318
+
319
+ def auto_cite_measurement(measurement_name: str) -> str | None:
320
+ """Automatically determine which IEEE standard to cite for a measurement.
321
+
322
+ Args:
323
+ measurement_name: Name of measurement (e.g., "rise_time", "snr").
324
+
325
+ Returns:
326
+ Standard ID to cite, or None if no match.
327
+
328
+ Example:
329
+ >>> auto_cite_measurement("rise_time")
330
+ '181'
331
+ >>> auto_cite_measurement("snr")
332
+ '1241'
333
+ >>> auto_cite_measurement("jitter")
334
+ '2414'
335
+ """
336
+ measurement_lower = measurement_name.lower()
337
+
338
+ # Pulse/waveform measurements -> IEEE 181
339
+ if any(
340
+ term in measurement_lower
341
+ for term in [
342
+ "rise",
343
+ "fall",
344
+ "pulse",
345
+ "transition",
346
+ "overshoot",
347
+ "settling",
348
+ "slew",
349
+ ]
350
+ ):
351
+ return "181"
352
+
353
+ # ADC/quantization measurements -> IEEE 1241
354
+ if any(
355
+ term in measurement_lower
356
+ for term in ["snr", "sinad", "enob", "thd", "sfdr", "quantization"]
357
+ ):
358
+ return "1241"
359
+
360
+ # Jitter measurements -> IEEE 2414
361
+ if any(term in measurement_lower for term in ["jitter", "phase_noise", "timing"]):
362
+ return "2414"
363
+
364
+ # Power measurements -> IEEE 1459
365
+ if any(term in measurement_lower for term in ["power", "rms", "thd", "distortion", "harmonic"]):
366
+ return "1459"
367
+
368
+ # Oscilloscope/digitizer -> IEEE 1057
369
+ if any(
370
+ term in measurement_lower for term in ["bandwidth", "sample_rate", "resolution", "accuracy"]
371
+ ):
372
+ return "1057"
373
+
374
+ return None
oscura/reporting/core.py CHANGED
@@ -112,6 +112,60 @@ class Report:
112
112
  self.sections.append(section)
113
113
  return section
114
114
 
115
+ def add_measurements(
116
+ self,
117
+ title: str,
118
+ measurements: dict[str, float | int],
119
+ unit_map: dict[str, str] | None = None,
120
+ level: int = 2,
121
+ html: bool = True,
122
+ **kwargs: Any,
123
+ ) -> Section:
124
+ """Add a measurement section with automatic formatting.
125
+
126
+ This convenience method automatically formats measurement dictionaries
127
+ using the framework's measurement formatting system, including SI prefix
128
+ auto-scaling and proper unit handling.
129
+
130
+ Args:
131
+ title: Section title.
132
+ measurements: Dictionary of measurement names to values.
133
+ unit_map: Optional dictionary mapping measurement names to units.
134
+ If not provided, uses framework metadata for common measurements.
135
+ level: Heading level.
136
+ html: Whether to format as HTML (True) or plain text (False).
137
+ **kwargs: Additional section options.
138
+
139
+ Returns:
140
+ The created Section.
141
+
142
+ Example:
143
+ >>> from oscura.reporting import Report
144
+ >>> report = Report()
145
+ >>> measurements = {"amplitude": 1.5, "frequency": 440.0, "duty_cycle": 0.5}
146
+ >>> unit_map = {"amplitude": "V", "frequency": "Hz", "duty_cycle": "ratio"}
147
+ >>> report.add_measurements("Time Domain", measurements, unit_map)
148
+ """
149
+ from oscura.reporting.formatting import (
150
+ convert_to_measurement_dict,
151
+ format_measurement_dict,
152
+ )
153
+
154
+ # If no unit map provided, try to use framework metadata
155
+ if unit_map is None:
156
+ from oscura.analyzers.waveform import MEASUREMENT_METADATA
157
+
158
+ unit_map = {
159
+ key: MEASUREMENT_METADATA.get(key, {}).get("unit", "") for key in measurements
160
+ }
161
+
162
+ # Convert to measurement dict and format
163
+ meas_dict = convert_to_measurement_dict(measurements, unit_map)
164
+ formatted_content = format_measurement_dict(meas_dict, html=html)
165
+
166
+ # Add as section
167
+ return self.add_section(title, formatted_content, level, **kwargs)
168
+
115
169
  def add_table(
116
170
  self,
117
171
  data: list[list[Any]] | NDArray[Any],
@@ -5,6 +5,12 @@ from oscura.reporting.formatting.emphasis import (
5
5
  format_callout_box,
6
6
  format_severity,
7
7
  )
8
+ from oscura.reporting.formatting.measurements import (
9
+ MeasurementFormatter,
10
+ convert_to_measurement_dict,
11
+ format_measurement,
12
+ format_measurement_dict,
13
+ )
8
14
  from oscura.reporting.formatting.numbers import (
9
15
  NumberFormatter,
10
16
  format_percentage,
@@ -109,15 +115,20 @@ __all__ = [
109
115
  "ColorScheme",
110
116
  # Standards
111
117
  "FormatStandards",
118
+ # Measurements
119
+ "MeasurementFormatter",
112
120
  # Numbers
113
121
  "NumberFormatter",
114
122
  "Severity",
115
123
  # Emphasis
116
124
  "VisualEmphasis",
117
125
  "apply_formatting_standards",
126
+ "convert_to_measurement_dict",
118
127
  "format_callout_box",
119
128
  # Convenience
120
129
  "format_margin",
130
+ "format_measurement",
131
+ "format_measurement_dict",
121
132
  "format_pass_fail",
122
133
  "format_percentage",
123
134
  "format_range",