xtalate 0.1.0.dev0__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.
- xtalate/__init__.py +10 -0
- xtalate/capabilities/__init__.py +19 -0
- xtalate/capabilities/registry.py +126 -0
- xtalate/cli/__init__.py +11 -0
- xtalate/cli/main.py +431 -0
- xtalate/cli/render.py +126 -0
- xtalate/conversion/__init__.py +48 -0
- xtalate/conversion/engine.py +560 -0
- xtalate/conversion/preflight.py +127 -0
- xtalate/conversion/report.py +74 -0
- xtalate/discovery/__init__.py +28 -0
- xtalate/discovery/engine.py +202 -0
- xtalate/discovery/report.py +50 -0
- xtalate/discovery/sniffer.py +95 -0
- xtalate/exporters/__init__.py +34 -0
- xtalate/exporters/extxyz.py +140 -0
- xtalate/exporters/poscar.py +180 -0
- xtalate/exporters/xyz.py +67 -0
- xtalate/parsers/__init__.py +31 -0
- xtalate/parsers/_common.py +57 -0
- xtalate/parsers/extxyz.py +452 -0
- xtalate/parsers/poscar.py +428 -0
- xtalate/parsers/xyz.py +286 -0
- xtalate/py.typed +0 -0
- xtalate/recovery/__init__.py +41 -0
- xtalate/recovery/engine.py +321 -0
- xtalate/recovery/scenarios.py +86 -0
- xtalate/registry.py +28 -0
- xtalate/schema/__init__.py +40 -0
- xtalate/schema/arrays.py +115 -0
- xtalate/schema/elements.py +150 -0
- xtalate/schema/models.py +283 -0
- xtalate/schema/paths.py +88 -0
- xtalate/schema/presence.py +152 -0
- xtalate/sdk/__init__.py +27 -0
- xtalate/sdk/capabilities.py +57 -0
- xtalate/sdk/plugins.py +72 -0
- xtalate/sdk/results.py +45 -0
- xtalate/validation/__init__.py +25 -0
- xtalate/validation/engine.py +585 -0
- xtalate/validation/report.py +48 -0
- xtalate/validation/rethreshold.py +122 -0
- xtalate/validation/tolerance.py +125 -0
- xtalate-0.1.0.dev0.dist-info/METADATA +161 -0
- xtalate-0.1.0.dev0.dist-info/RECORD +49 -0
- xtalate-0.1.0.dev0.dist-info/WHEEL +4 -0
- xtalate-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- xtalate-0.1.0.dev0.dist-info/licenses/LICENSE +201 -0
- xtalate-0.1.0.dev0.dist-info/licenses/NOTICE +11 -0
xtalate/cli/render.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Human-readable terminal renderers for the report schemas (MASTER_SPEC Appendix A, Part 3 §6.3).
|
|
2
|
+
|
|
3
|
+
Pure functions: report model in, plain-text string out. The CLI is a *thin presenter* (Part 1 §2)
|
|
4
|
+
— these renderers add no scientific logic and invent no information the reports do not already
|
|
5
|
+
carry; `--json` bypasses them entirely and emits the pydantic models verbatim. ASCII glyphs keep
|
|
6
|
+
the output legible in any terminal/pipe (✓/✗ are the one concession, matching the §6.3 inventory).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from xtalate.conversion.report import ConversionReport
|
|
12
|
+
from xtalate.discovery.report import DiscoveryReport
|
|
13
|
+
from xtalate.sdk import CapabilityLevel, FormatCapabilities
|
|
14
|
+
from xtalate.validation.report import ValidationReport
|
|
15
|
+
|
|
16
|
+
_PRESENCE = {"present": "✓", "absent": "✗", "mixed": "◐"}
|
|
17
|
+
_CAP = {
|
|
18
|
+
CapabilityLevel.FULL: "full",
|
|
19
|
+
CapabilityLevel.PARTIAL: "partial",
|
|
20
|
+
CapabilityLevel.NONE: "none",
|
|
21
|
+
}
|
|
22
|
+
_CHECK = {"pass": "✓", "warn": "⚠", "fail": "✗", "skipped": "–"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def render_discovery(report: DiscoveryReport) -> str:
|
|
26
|
+
fmt = report.format
|
|
27
|
+
lines = [
|
|
28
|
+
f"File: {report.file.get('filename')} ({report.file.get('size_bytes')} bytes)",
|
|
29
|
+
f"Format: {fmt.get('format_name')} [{fmt.get('format_id')}] "
|
|
30
|
+
f"confidence {fmt.get('confidence')}"
|
|
31
|
+
+ (" (overridden)" if fmt.get("overridden") else "")
|
|
32
|
+
+ (" (ambiguous)" if fmt.get("ambiguous") else ""),
|
|
33
|
+
]
|
|
34
|
+
struct = report.structure
|
|
35
|
+
lines.append(
|
|
36
|
+
f"Structure: {struct.get('frame_count')} frame(s) × {struct.get('atom_count')} atoms; "
|
|
37
|
+
f"species {', '.join(struct.get('species', []))}"
|
|
38
|
+
)
|
|
39
|
+
lines.append("")
|
|
40
|
+
lines.append("Canonical fields (✓ present / ✗ absent / ◐ mixed · read capability):")
|
|
41
|
+
for field in report.fields:
|
|
42
|
+
glyph = _PRESENCE.get(field.status, "?")
|
|
43
|
+
cap = _CAP.get(field.format_capability, str(field.format_capability))
|
|
44
|
+
detail = f" — {field.detail}" if field.detail else ""
|
|
45
|
+
frames = (
|
|
46
|
+
f" frames {field.present_frames}"
|
|
47
|
+
if field.status == "mixed" and field.present_frames is not None
|
|
48
|
+
else ""
|
|
49
|
+
)
|
|
50
|
+
lines.append(f" {glyph} {field.path:<32} [{cap}]{detail}{frames}")
|
|
51
|
+
if report.extras:
|
|
52
|
+
lines.append("")
|
|
53
|
+
lines.append("Carried-through extras (namespaced, format-specific):")
|
|
54
|
+
lines.extend(f" + {key}" for key in report.extras)
|
|
55
|
+
if report.issues:
|
|
56
|
+
lines.append("")
|
|
57
|
+
lines.append("Parse issues:")
|
|
58
|
+
lines.extend(f" ! [{i.severity}] {i.code}: {i.message}" for i in report.issues)
|
|
59
|
+
return "\n".join(lines)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def render_conversion(report: ConversionReport) -> str:
|
|
63
|
+
lines = [
|
|
64
|
+
f"Conversion Report [{report.stage} · {report.status} · {report.mode}]",
|
|
65
|
+
f" {report.source.get('format_id')} → {report.target.get('format_id')}",
|
|
66
|
+
]
|
|
67
|
+
if report.status == "refused" and report.refusal is not None:
|
|
68
|
+
lines.append(f" REFUSED [{report.refusal.get('code')}]: {report.refusal.get('message')}")
|
|
69
|
+
for scenario in report.refusal.get("unresolved_scenarios", []):
|
|
70
|
+
lines.append(f" · {scenario.get('scenario')}: {scenario.get('detail')}")
|
|
71
|
+
lines.append(f" preserved ({len(report.preserved)}):")
|
|
72
|
+
for entry in report.preserved:
|
|
73
|
+
lines.append(f" ✓ {entry.path}" + (f" — {entry.detail}" if entry.detail else ""))
|
|
74
|
+
lines.append(f" removed ({len(report.removed)}):")
|
|
75
|
+
for removed in report.removed:
|
|
76
|
+
lines.append(f" ✗ {removed.path} — {removed.reason}")
|
|
77
|
+
if report.supplied:
|
|
78
|
+
lines.append(f" supplied ({len(report.supplied)}):")
|
|
79
|
+
for sup in report.supplied:
|
|
80
|
+
lines.append(f" + {sup.path} (from {sup.from_assumption})")
|
|
81
|
+
if report.assumptions:
|
|
82
|
+
lines.append(f" assumptions ({len(report.assumptions)}):")
|
|
83
|
+
for a in report.assumptions:
|
|
84
|
+
lines.append(f" ~ {a.id} {a.scenario}={a.choice}: {a.description}")
|
|
85
|
+
if report.warnings:
|
|
86
|
+
lines.append(f" warnings ({len(report.warnings)}):")
|
|
87
|
+
for w in report.warnings:
|
|
88
|
+
lines.append(f" ⚠ [{w.source}] {w.message}")
|
|
89
|
+
return "\n".join(lines)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def render_validation(report: ValidationReport) -> str:
|
|
93
|
+
profile = report.tolerance_profile.get("name", "?")
|
|
94
|
+
lines = [f"Validation Report [{report.status}] (tolerance profile: {profile})"]
|
|
95
|
+
for check in report.checks:
|
|
96
|
+
lines.append(f" {_CHECK.get(check.status, '?')} {check.check_id}: {check.message}")
|
|
97
|
+
if report.reparse_issues:
|
|
98
|
+
lines.append(" re-parse issues:")
|
|
99
|
+
lines.extend(f" ! [{i.severity}] {i.code}: {i.message}" for i in report.reparse_issues)
|
|
100
|
+
return "\n".join(lines)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def render_capabilities(declarations: dict[str, dict[str, FormatCapabilities]]) -> str:
|
|
104
|
+
lines: list[str] = []
|
|
105
|
+
for format_id in sorted(declarations):
|
|
106
|
+
directions = declarations[format_id]
|
|
107
|
+
any_caps = next(iter(directions.values()))
|
|
108
|
+
lines.append(f"{any_caps.format_name} [{format_id}]")
|
|
109
|
+
for direction in ("read", "write"):
|
|
110
|
+
caps = directions.get(direction)
|
|
111
|
+
if caps is None:
|
|
112
|
+
lines.append(f" {direction}: (not registered)")
|
|
113
|
+
continue
|
|
114
|
+
frames = "unlimited" if caps.max_frames is None else str(caps.max_frames)
|
|
115
|
+
lines.append(
|
|
116
|
+
f" {direction}: coords={caps.native_coordinate_system}, max_frames={frames}"
|
|
117
|
+
+ (f", requires {caps.required_fields}" if caps.required_fields else "")
|
|
118
|
+
)
|
|
119
|
+
for path in sorted(caps.fields):
|
|
120
|
+
cell = caps.fields[path]
|
|
121
|
+
note = f" — {cell.notes}" if cell.notes else ""
|
|
122
|
+
lines.append(f" {path:<40} {_CAP.get(cell.level, str(cell.level))}{note}")
|
|
123
|
+
for note in caps.lossy_notes:
|
|
124
|
+
lines.append(f" (lossy) {note}")
|
|
125
|
+
lines.append("")
|
|
126
|
+
return "\n".join(lines).rstrip()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Conversion Engine — orchestrates parse → capability diff → recovery → export → report.
|
|
2
|
+
|
|
3
|
+
Owns the pre-flight diff (Part 3 §4.3), the ``write_plan`` (Part 4 §1), the
|
|
4
|
+
``ConversionReport`` (Part 4 §2), and the completeness-invariant runtime assertion
|
|
5
|
+
(review §4.5). Delegates all format logic to the parsers/exporters via their
|
|
6
|
+
``capabilities()`` declarations. Recovery resolution and the automatic final-step
|
|
7
|
+
validation land in M5; M4 is the happy path plus structured refusal.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from xtalate.conversion.engine import (
|
|
13
|
+
CompletenessInvariantError,
|
|
14
|
+
ConversionEngine,
|
|
15
|
+
ConversionResult,
|
|
16
|
+
build_expected_object,
|
|
17
|
+
)
|
|
18
|
+
from xtalate.conversion.preflight import (
|
|
19
|
+
PreflightDiff,
|
|
20
|
+
build_preflight,
|
|
21
|
+
capability_path,
|
|
22
|
+
)
|
|
23
|
+
from xtalate.conversion.report import (
|
|
24
|
+
Assumption,
|
|
25
|
+
ConversionReport,
|
|
26
|
+
PreservedEntry,
|
|
27
|
+
RemovedEntry,
|
|
28
|
+
ReportWarning,
|
|
29
|
+
SuppliedEntry,
|
|
30
|
+
)
|
|
31
|
+
from xtalate.recovery import UnresolvedScenario
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"Assumption",
|
|
35
|
+
"CompletenessInvariantError",
|
|
36
|
+
"ConversionEngine",
|
|
37
|
+
"ConversionReport",
|
|
38
|
+
"ConversionResult",
|
|
39
|
+
"PreflightDiff",
|
|
40
|
+
"PreservedEntry",
|
|
41
|
+
"RemovedEntry",
|
|
42
|
+
"ReportWarning",
|
|
43
|
+
"SuppliedEntry",
|
|
44
|
+
"UnresolvedScenario",
|
|
45
|
+
"build_expected_object",
|
|
46
|
+
"build_preflight",
|
|
47
|
+
"capability_path",
|
|
48
|
+
]
|