jupytermind 0.3.0

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 (193) hide show
  1. package/.github/skills/ai-chemistry-scientist/SKILL.md +97 -0
  2. package/.github/skills/ai-chemistry-scientist/manifest.json +156 -0
  3. package/.github/skills/ai-data-scientist/SKILL.md +330 -0
  4. package/.github/skills/ai-genomics-scientist/SKILL.md +98 -0
  5. package/.github/skills/ai-genomics-scientist/manifest.json +93 -0
  6. package/.github/skills/ai-materials-scientist/SKILL.md +51 -0
  7. package/.github/skills/ai-materials-scientist/manifest.json +58 -0
  8. package/.github/skills/ai-scientist/SKILL.md +69 -0
  9. package/.github/skills/ai-scientist/manifest.json +61 -0
  10. package/.github/skills/ai-structural-biology-scientist/SKILL.md +67 -0
  11. package/.github/skills/ai-structural-biology-scientist/manifest.json +72 -0
  12. package/.github/skills/japanese-prose/NOTICE.md +17 -0
  13. package/.github/skills/japanese-prose/SKILL.md +111 -0
  14. package/.github/skills/japanese-prose/references/review-workflow.md +50 -0
  15. package/.github/skills/japanese-prose/references/scoring.md +24 -0
  16. package/.github/skills/japanese-prose/references/writing-guidelines.md +60 -0
  17. package/.github/skills/japanese-prose/scripts/core.py +192 -0
  18. package/.github/skills/japanese-prose/scripts/fixtures/natural.md +5 -0
  19. package/.github/skills/japanese-prose/scripts/fixtures/unnatural.md +5 -0
  20. package/.github/skills/japanese-prose/scripts/lint.py +378 -0
  21. package/.github/skills/japanese-prose/scripts/outline.py +68 -0
  22. package/.github/skills/japanese-prose/scripts/terms.py +112 -0
  23. package/.github/skills/japanese-prose/scripts/test_engine.py +117 -0
  24. package/.github/skills/presentation-planner/SKILL.md +257 -0
  25. package/.github/skills/presentation-planner/assets/design-templates/data-report.yaml +97 -0
  26. package/.github/skills/presentation-planner/assets/design-templates/executive-proposal.yaml +92 -0
  27. package/.github/skills/presentation-planner/assets/design-templates/technical-briefing.yaml +96 -0
  28. package/.github/skills/presentation-planner/assets/scenario-templates/data-report.md +47 -0
  29. package/.github/skills/presentation-planner/assets/scenario-templates/executive-decision.md +43 -0
  30. package/.github/skills/presentation-planner/assets/scenario-templates/technical-briefing.md +45 -0
  31. package/.github/skills/presentation-planner/references/customizing-design-templates.md +160 -0
  32. package/.github/skills/presentation-planner/references/design-spec-schema.md +72 -0
  33. package/.github/skills/presentation-planner/references/handoff-contract.md +49 -0
  34. package/.github/skills/presentation-planner/references/responsibility-boundary.md +32 -0
  35. package/.github/skills/presentation-planner/references/scenario-templates.md +55 -0
  36. package/.github/skills/tech-writer/SKILL.md +434 -0
  37. package/.github/skills/tech-writer/assets/templates/blueprint.md +187 -0
  38. package/.github/skills/tech-writer/assets/templates/design-doc.md +29 -0
  39. package/.github/skills/tech-writer/assets/templates/migration-plan.md +173 -0
  40. package/.github/skills/tech-writer/assets/templates/operations-runbook.md +202 -0
  41. package/.github/skills/tech-writer/assets/templates/pr-description.md +23 -0
  42. package/.github/skills/tech-writer/assets/templates/qiita.md +44 -0
  43. package/.github/skills/tech-writer/assets/templates/readme.md +38 -0
  44. package/.github/skills/tech-writer/assets/templates/requirements-definition.md +170 -0
  45. package/.github/skills/tech-writer/assets/templates/rfi.md +113 -0
  46. package/.github/skills/tech-writer/assets/templates/rfp.md +180 -0
  47. package/.github/skills/tech-writer/assets/templates/security-design.md +167 -0
  48. package/.github/skills/tech-writer/assets/templates/system-design.md +220 -0
  49. package/.github/skills/tech-writer/assets/templates/technical-proposal.md +112 -0
  50. package/.github/skills/tech-writer/assets/templates/test-plan.md +153 -0
  51. package/.github/skills/tech-writer/assets/templates/user-manual.md +22 -0
  52. package/.github/skills/tech-writer/assets/templates/white-paper.md +192 -0
  53. package/.github/skills/tech-writer/references/doctypes/api-docs.md +33 -0
  54. package/.github/skills/tech-writer/references/doctypes/blueprint.md +81 -0
  55. package/.github/skills/tech-writer/references/doctypes/code-comments.md +39 -0
  56. package/.github/skills/tech-writer/references/doctypes/design-doc.md +42 -0
  57. package/.github/skills/tech-writer/references/doctypes/migration-plan.md +63 -0
  58. package/.github/skills/tech-writer/references/doctypes/operations-runbook.md +63 -0
  59. package/.github/skills/tech-writer/references/doctypes/pr-commit.md +82 -0
  60. package/.github/skills/tech-writer/references/doctypes/qiita.md +75 -0
  61. package/.github/skills/tech-writer/references/doctypes/readme.md +43 -0
  62. package/.github/skills/tech-writer/references/doctypes/release-notes.md +30 -0
  63. package/.github/skills/tech-writer/references/doctypes/requirements-definition.md +61 -0
  64. package/.github/skills/tech-writer/references/doctypes/rfi.md +43 -0
  65. package/.github/skills/tech-writer/references/doctypes/rfp.md +46 -0
  66. package/.github/skills/tech-writer/references/doctypes/security-design.md +71 -0
  67. package/.github/skills/tech-writer/references/doctypes/system-design.md +74 -0
  68. package/.github/skills/tech-writer/references/doctypes/technical-proposal.md +49 -0
  69. package/.github/skills/tech-writer/references/doctypes/test-plan.md +67 -0
  70. package/.github/skills/tech-writer/references/doctypes/user-manual.md +58 -0
  71. package/.github/skills/tech-writer/references/doctypes/white-paper.md +84 -0
  72. package/.github/skills/tech-writer/references/doctypes/zenn.md +66 -0
  73. package/.github/skills/tech-writer/references/japanese-prose-optimization.md +110 -0
  74. package/.github/skills/tech-writer/references/style-constitution.md +104 -0
  75. package/.github/skills/tech-writer/scripts/lint.py +412 -0
  76. package/LICENSE +21 -0
  77. package/README.md +92 -0
  78. package/bin/ai-data-scientist.js +123 -0
  79. package/package.json +41 -0
  80. package/pyproject.toml +45 -0
  81. package/src/ai_chemistry_scientist/__init__.py +0 -0
  82. package/src/ai_chemistry_scientist/admet_prediction.py +71 -0
  83. package/src/ai_chemistry_scientist/bioactivity_classification.py +73 -0
  84. package/src/ai_chemistry_scientist/data/sample_molecules.csv +21 -0
  85. package/src/ai_chemistry_scientist/dispatch.py +369 -0
  86. package/src/ai_chemistry_scientist/docking_score.py +97 -0
  87. package/src/ai_chemistry_scientist/drug_likeness_rules.py +84 -0
  88. package/src/ai_chemistry_scientist/evidence.py +41 -0
  89. package/src/ai_chemistry_scientist/molecular_descriptors.py +97 -0
  90. package/src/ai_chemistry_scientist/molecular_formula_mass.py +40 -0
  91. package/src/ai_chemistry_scientist/molecular_similarity.py +78 -0
  92. package/src/ai_chemistry_scientist/qsar_modeling.py +105 -0
  93. package/src/ai_chemistry_scientist/salt_standardization.py +81 -0
  94. package/src/ai_chemistry_scientist/structural_alerts.py +76 -0
  95. package/src/ai_chemistry_scientist/structure_format_conversion.py +84 -0
  96. package/src/ai_chemistry_scientist/validation.py +70 -0
  97. package/src/ai_data_scientist/__init__.py +0 -0
  98. package/src/ai_data_scientist/analysis_assumptions.py +121 -0
  99. package/src/ai_data_scientist/anomaly_detection.py +39 -0
  100. package/src/ai_data_scientist/automl.py +109 -0
  101. package/src/ai_data_scientist/cleaning.py +56 -0
  102. package/src/ai_data_scientist/cli.py +90 -0
  103. package/src/ai_data_scientist/clustering.py +54 -0
  104. package/src/ai_data_scientist/dashboard.py +33 -0
  105. package/src/ai_data_scientist/data_definition.py +100 -0
  106. package/src/ai_data_scientist/data_quality.py +164 -0
  107. package/src/ai_data_scientist/dataset_validation.py +135 -0
  108. package/src/ai_data_scientist/dependency_pins.py +60 -0
  109. package/src/ai_data_scientist/eda.py +82 -0
  110. package/src/ai_data_scientist/experiment_evaluation.py +635 -0
  111. package/src/ai_data_scientist/explainability.py +340 -0
  112. package/src/ai_data_scientist/feature_engineering.py +163 -0
  113. package/src/ai_data_scientist/gate_config.py +32 -0
  114. package/src/ai_data_scientist/ingestion.py +127 -0
  115. package/src/ai_data_scientist/insight_engine.py +180 -0
  116. package/src/ai_data_scientist/japanese_nlp.py +43 -0
  117. package/src/ai_data_scientist/jupyter_launcher.py +137 -0
  118. package/src/ai_data_scientist/jupyter_mcp_client.py +94 -0
  119. package/src/ai_data_scientist/language_router.py +28 -0
  120. package/src/ai_data_scientist/lifecycle.py +221 -0
  121. package/src/ai_data_scientist/mcp_gateway.py +113 -0
  122. package/src/ai_data_scientist/mcp_runtime.py +194 -0
  123. package/src/ai_data_scientist/mcp_transport.py +53 -0
  124. package/src/ai_data_scientist/ml_modeling.py +451 -0
  125. package/src/ai_data_scientist/model_tuning.py +104 -0
  126. package/src/ai_data_scientist/notebook_audit.py +574 -0
  127. package/src/ai_data_scientist/project_manager.py +243 -0
  128. package/src/ai_data_scientist/report_export.py +73 -0
  129. package/src/ai_data_scientist/sensitivity.py +445 -0
  130. package/src/ai_data_scientist/signal_analysis.py +201 -0
  131. package/src/ai_data_scientist/skill_packaging.py +40 -0
  132. package/src/ai_data_scientist/stats_analysis.py +88 -0
  133. package/src/ai_data_scientist/text_nlp.py +44 -0
  134. package/src/ai_data_scientist/timeseries.py +68 -0
  135. package/src/ai_data_scientist/visualization.py +708 -0
  136. package/src/ai_genomics_scientist/__init__.py +1 -0
  137. package/src/ai_genomics_scientist/differential_expression.py +147 -0
  138. package/src/ai_genomics_scientist/dispatch.py +267 -0
  139. package/src/ai_genomics_scientist/evidence.py +45 -0
  140. package/src/ai_genomics_scientist/gene_set_enrichment.py +76 -0
  141. package/src/ai_genomics_scientist/sequence_alignment.py +97 -0
  142. package/src/ai_genomics_scientist/sequence_features.py +111 -0
  143. package/src/ai_genomics_scientist/splice_site_scoring.py +66 -0
  144. package/src/ai_genomics_scientist/validation.py +83 -0
  145. package/src/ai_genomics_scientist/variant_effect.py +147 -0
  146. package/src/ai_genomics_scientist/variant_pathogenicity.py +125 -0
  147. package/src/ai_materials_scientist/__init__.py +0 -0
  148. package/src/ai_materials_scientist/calphad.py +117 -0
  149. package/src/ai_materials_scientist/classical_monte_carlo.py +165 -0
  150. package/src/ai_materials_scientist/crystal_plasticity.py +184 -0
  151. package/src/ai_materials_scientist/dispatch.py +100 -0
  152. package/src/ai_materials_scientist/evidence.py +84 -0
  153. package/src/ai_materials_scientist/fem.py +279 -0
  154. package/src/ai_materials_scientist/kinetic_monte_carlo.py +145 -0
  155. package/src/ai_materials_scientist/molecular_dynamics.py +240 -0
  156. package/src/ai_materials_scientist/phase_field.py +167 -0
  157. package/src/ai_materials_scientist/validation.py +70 -0
  158. package/src/ai_scientist/__init__.py +1 -0
  159. package/src/ai_scientist/completion_gate.py +15 -0
  160. package/src/ai_scientist/data_analysis.py +46 -0
  161. package/src/ai_scientist/evidence_registry.py +99 -0
  162. package/src/ai_scientist/experimental_design.py +20 -0
  163. package/src/ai_scientist/language.py +14 -0
  164. package/src/ai_scientist/latex_renderer.py +41 -0
  165. package/src/ai_scientist/literature_review.py +37 -0
  166. package/src/ai_scientist/manifest.py +87 -0
  167. package/src/ai_scientist/manuscript.py +94 -0
  168. package/src/ai_scientist/mcp_config.py +76 -0
  169. package/src/ai_scientist/mcp_external.py +42 -0
  170. package/src/ai_scientist/mcp_failures.py +23 -0
  171. package/src/ai_scientist/mcp_gateway.py +38 -0
  172. package/src/ai_scientist/mcp_managed.py +180 -0
  173. package/src/ai_scientist/npm_packaging.py +49 -0
  174. package/src/ai_scientist/orchestrator.py +133 -0
  175. package/src/ai_scientist/peer_review.py +60 -0
  176. package/src/ai_scientist/phase_gate.py +74 -0
  177. package/src/ai_scientist/phase_state.py +230 -0
  178. package/src/ai_scientist/presentation.py +56 -0
  179. package/src/ai_scientist/project_config.py +31 -0
  180. package/src/ai_scientist/project_handle.py +74 -0
  181. package/src/ai_scientist/reproducibility.py +20 -0
  182. package/src/ai_scientist/research_planning.py +20 -0
  183. package/src/ai_scientist/skill_invocation.py +21 -0
  184. package/src/ai_scientist/tdd_gate.py +99 -0
  185. package/src/ai_structural_biology_scientist/__init__.py +0 -0
  186. package/src/ai_structural_biology_scientist/contact_map.py +87 -0
  187. package/src/ai_structural_biology_scientist/dispatch.py +269 -0
  188. package/src/ai_structural_biology_scientist/evidence.py +43 -0
  189. package/src/ai_structural_biology_scientist/hydrophobicity.py +101 -0
  190. package/src/ai_structural_biology_scientist/protein_docking_score.py +104 -0
  191. package/src/ai_structural_biology_scientist/secondary_structure.py +95 -0
  192. package/src/ai_structural_biology_scientist/structural_similarity.py +74 -0
  193. package/src/ai_structural_biology_scientist/validation.py +100 -0
@@ -0,0 +1,95 @@
1
+ """Secondary-structure heuristic module (DES-ASTRUCT-010 / REQ-ASTRUCT-010)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ai_structural_biology_scientist.validation import (
6
+ fail,
7
+ register_validator,
8
+ validate_uppercase_sequence,
9
+ )
10
+
11
+ _MODULE_NAME = "secondary-structure-heuristic"
12
+ _PROPENSITIES = {
13
+ "A": (1.42, 0.83, 0.75),
14
+ "R": (0.98, 0.93, 1.09),
15
+ "N": (0.67, 0.89, 1.44),
16
+ "D": (1.01, 0.54, 1.45),
17
+ "C": (0.70, 1.19, 1.11),
18
+ "Q": (1.11, 1.10, 0.79),
19
+ "E": (1.51, 0.37, 1.12),
20
+ "G": (0.57, 0.75, 1.68),
21
+ "H": (1.00, 0.87, 1.13),
22
+ "I": (1.08, 1.60, 0.32),
23
+ "L": (1.21, 1.30, 0.49),
24
+ "K": (1.16, 0.74, 1.10),
25
+ "M": (1.45, 1.05, 0.50),
26
+ "F": (1.13, 1.38, 0.49),
27
+ "P": (0.57, 0.55, 1.88),
28
+ "S": (0.77, 0.75, 1.48),
29
+ "T": (0.83, 1.19, 0.98),
30
+ "W": (1.08, 1.37, 0.55),
31
+ "Y": (0.69, 1.47, 0.84),
32
+ "V": (1.06, 1.70, 0.41),
33
+ }
34
+ _CLASS_ORDER = ("H", "E", "C")
35
+
36
+ LIMITATION_LABEL_KEY = "secondary_structure_heuristic_limitation"
37
+ LIMITATION_LABEL_TEXT = {
38
+ "en": (
39
+ "Heuristic only: a fixed illustrative per-residue propensity lookup, not a "
40
+ "validated secondary-structure predictor (no windowing, no real Chou-Fasman "
41
+ "statistics)."
42
+ ),
43
+ "ja": (
44
+ "ヒューリスティックのみ:固定の説明用残基別 propensity lookup であり、"
45
+ "検証済みの二次構造予測器ではない(windowing なし、実際の Chou-Fasman "
46
+ "統計なし)。"
47
+ ),
48
+ }
49
+
50
+
51
+ def _secondary_structure_validator(params: dict) -> dict:
52
+ """DES-ASTRUCT-002 registered atomic validator for this module."""
53
+ if "sequence" not in params:
54
+ return fail("sequence", "is required")
55
+ return validate_uppercase_sequence(params)
56
+
57
+
58
+ register_validator(_MODULE_NAME, _secondary_structure_validator)
59
+
60
+
61
+ # @id CODE-ASTRUCT-010
62
+ # @implements REQ-ASTRUCT-010
63
+ # @design DES-ASTRUCT-010
64
+ def run_secondary_structure(sequence: str) -> dict:
65
+ """Compute the fixed-table structural assignment for ``sequence``."""
66
+ sequence_validation = validate_uppercase_sequence({"sequence": sequence})
67
+ if not sequence_validation["ok"]:
68
+ raise ValueError("sequence must already be validated by the handler wrapper")
69
+
70
+ residues = []
71
+ classes = []
72
+ for position, residue in enumerate(sequence):
73
+ helix, sheet, coil = _PROPENSITIES[residue]
74
+ propensity_by_class = {"H": helix, "E": sheet, "C": coil}
75
+ # Documented fixed tie-break order H > E > C: ``_CLASS_ORDER`` is
76
+ # iterated in that exact order and ``max`` keeps the first maximal
77
+ # element on ties, so this is equivalent to, but clearer than, an
78
+ # explicit negated-index secondary sort key.
79
+ assigned_class = max(
80
+ _CLASS_ORDER,
81
+ key=lambda klass: propensity_by_class[klass],
82
+ )
83
+ residues.append({"position": position, "residue": residue, "class": assigned_class})
84
+ classes.append(assigned_class)
85
+
86
+ secondary_structure = "".join(classes)
87
+ length = len(sequence)
88
+ return {
89
+ "residues": residues,
90
+ "secondary_structure": secondary_structure,
91
+ "helix_fraction": secondary_structure.count("H") / length,
92
+ "sheet_fraction": secondary_structure.count("E") / length,
93
+ "coil_fraction": secondary_structure.count("C") / length,
94
+ "limitation_label_key": LIMITATION_LABEL_KEY,
95
+ }
@@ -0,0 +1,74 @@
1
+ """Structural similarity module (DES-ASTRUCT-040 / REQ-ASTRUCT-040)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+
7
+ from ai_structural_biology_scientist.validation import fail, register_validator, validate_coordinate
8
+
9
+ _MODULE_NAME = "structural-similarity-rmsd"
10
+
11
+
12
+ def _structural_similarity_validator(params: dict) -> dict:
13
+ """DES-ASTRUCT-002 registered atomic validator for this module."""
14
+ for name in ("structure_a", "structure_b"):
15
+ if name not in params:
16
+ return fail(name, "is required")
17
+
18
+ structure_a = params["structure_a"]
19
+ structure_b = params["structure_b"]
20
+ if not isinstance(structure_a, (list, tuple)):
21
+ return fail("structure_a", "must be a list/tuple of coordinates")
22
+ if not isinstance(structure_b, (list, tuple)):
23
+ return fail("structure_b", "must be a list/tuple of coordinates")
24
+ if len(structure_a) != len(structure_b):
25
+ return fail("structure_b", "must have the same length as structure_a")
26
+ if len(structure_a) < 3:
27
+ return fail("structure_a", "must contain at least 3 coordinates")
28
+
29
+ for name, structure in (("structure_a", structure_a), ("structure_b", structure_b)):
30
+ for index, coordinate in enumerate(structure):
31
+ coordinate_validation = validate_coordinate(coordinate, parameter=f"{name}[{index}]")
32
+ if not coordinate_validation["ok"]:
33
+ return coordinate_validation
34
+
35
+ return {"ok": True}
36
+
37
+
38
+ register_validator(_MODULE_NAME, _structural_similarity_validator)
39
+
40
+
41
+ # @id CODE-ASTRUCT-040
42
+ # @implements REQ-ASTRUCT-040
43
+ # @design DES-ASTRUCT-040
44
+ def run_structural_similarity(structure_a: list, structure_b: list) -> dict:
45
+ """Compute Kabsch RMSD after optimal rigid-body superposition."""
46
+ validation = _structural_similarity_validator(
47
+ {"structure_a": structure_a, "structure_b": structure_b}
48
+ )
49
+ if not validation["ok"]:
50
+ raise ValueError("parameters must already be validated by the handler wrapper")
51
+
52
+ a = np.asarray(structure_a, dtype=float)
53
+ b = np.asarray(structure_b, dtype=float)
54
+ centroid_a = a.mean(axis=0)
55
+ centroid_b = b.mean(axis=0)
56
+ centered_a = a - centroid_a
57
+ centered_b = b - centroid_b
58
+
59
+ cross_covariance = centered_a.T @ centered_b
60
+ u, _, vt = np.linalg.svd(cross_covariance)
61
+ rotation = vt.T @ u.T
62
+ if np.linalg.det(rotation) < 0:
63
+ vt[-1, :] *= -1
64
+ rotation = vt.T @ u.T
65
+
66
+ translation = centroid_b - rotation @ centroid_a
67
+ aligned_a = (rotation @ a.T).T + translation
68
+ rmsd = np.sqrt(np.mean(np.sum((aligned_a - b) ** 2, axis=1)))
69
+
70
+ return {
71
+ "rmsd": float(rmsd),
72
+ "rotation_matrix": rotation.tolist(),
73
+ "translation": translation.tolist(),
74
+ }
@@ -0,0 +1,100 @@
1
+ """Shared parameter validator (DES-ASTRUCT-002 / REQ-ASTRUCT-003)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import math
7
+ from collections.abc import Callable, Mapping, Sequence
8
+ from numbers import Real
9
+ from typing import Any
10
+
11
+ ValidationResult = dict[str, Any]
12
+ ValidatorFn = Callable[[dict[str, Any]], ValidationResult]
13
+
14
+ _REGISTRY: dict[str, ValidatorFn] = {}
15
+ _VALIDATOR_MODULES = {
16
+ "secondary-structure-heuristic": "ai_structural_biology_scientist.secondary_structure",
17
+ "hydrophobicity-burial-heuristic": "ai_structural_biology_scientist.hydrophobicity",
18
+ "protein-protein-docking-score": "ai_structural_biology_scientist.protein_docking_score",
19
+ "structural-similarity-rmsd": "ai_structural_biology_scientist.structural_similarity",
20
+ "residue-contact-map": "ai_structural_biology_scientist.contact_map",
21
+ }
22
+ _SEQUENCE_ALPHABET = "ACDEFGHIKLMNPQRSTVWY"
23
+ _COORDINATE_CONSTRAINT = "must be a list/tuple of exactly 3 finite real numbers"
24
+
25
+
26
+ def ok() -> ValidationResult:
27
+ return {"ok": True}
28
+
29
+
30
+ def fail(parameter: str, constraint: str) -> ValidationResult:
31
+ return {"ok": False, "parameter": parameter, "constraint": constraint}
32
+
33
+
34
+ def is_finite_real(value: Any) -> bool:
35
+ return isinstance(value, Real) and not isinstance(value, bool) and math.isfinite(value)
36
+
37
+
38
+ def validate_uppercase_sequence(
39
+ params: dict[str, Any], parameter: str = "sequence"
40
+ ) -> ValidationResult:
41
+ sequence = params.get(parameter)
42
+ if not isinstance(sequence, str) or sequence == "":
43
+ return fail(parameter, f"must be a non-empty uppercase sequence over {_SEQUENCE_ALPHABET}")
44
+ for index, residue in enumerate(sequence):
45
+ if residue not in _SEQUENCE_ALPHABET:
46
+ return fail(
47
+ parameter,
48
+ (
49
+ f"must contain only uppercase residues from {_SEQUENCE_ALPHABET}; "
50
+ f"first invalid character {residue!r} at index {index}"
51
+ ),
52
+ )
53
+ return ok()
54
+
55
+
56
+ def validate_coordinate(
57
+ coordinate: Any,
58
+ *,
59
+ parameter: str,
60
+ constraint: str = _COORDINATE_CONSTRAINT,
61
+ ) -> ValidationResult:
62
+ if not isinstance(coordinate, (list, tuple)) or len(coordinate) != 3:
63
+ return fail(parameter, constraint)
64
+ if any(not is_finite_real(value) for value in coordinate):
65
+ return fail(parameter, constraint)
66
+ return ok()
67
+
68
+
69
+ def _ensure_validator_registered(module_name: str) -> None:
70
+ if module_name in _REGISTRY:
71
+ return
72
+ module_path = _VALIDATOR_MODULES.get(module_name)
73
+ if module_path is not None:
74
+ importlib.import_module(module_path)
75
+
76
+
77
+ # @id CODE-ASTRUCT-002
78
+ # @implements REQ-ASTRUCT-003
79
+ # @design DES-ASTRUCT-002
80
+ def register_validator(module_name: str, validator: ValidatorFn) -> None:
81
+ """Register ``module_name``'s documented atomic validator."""
82
+ if module_name in _REGISTRY:
83
+ raise ValueError( # noqa: TRY004
84
+ f"validator already registered for module '{module_name}'"
85
+ )
86
+ _REGISTRY[module_name] = validator
87
+
88
+
89
+ # @id CODE-ASTRUCT-902
90
+ # @implements REQ-ASTRUCT-003
91
+ # @design DES-ASTRUCT-002
92
+ def validate_parameters(module_name: str, params: dict[str, Any]) -> ValidationResult:
93
+ """Dispatch to ``module_name``'s registered atomic validator."""
94
+ _ensure_validator_registered(module_name)
95
+ validator = _REGISTRY.get(module_name)
96
+ if validator is None:
97
+ return fail("module", f"no validator registered for module '{module_name}'")
98
+ if not isinstance(params, Mapping):
99
+ return fail("params", "must be a dict")
100
+ return validator(dict(params))