akms-learn 0.3.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.
- akms_learn/__init__.py +155 -0
- akms_learn/_code_links.py +154 -0
- akms_learn/adapters/__init__.py +38 -0
- akms_learn/adapters/fake_adapter.py +172 -0
- akms_learn/adapters/protocols.py +197 -0
- akms_learn/adapters/status.py +130 -0
- akms_learn/capabilities_catalog.py +263 -0
- akms_learn/capability_gates.py +293 -0
- akms_learn/cli.py +563 -0
- akms_learn/compiler.py +1127 -0
- akms_learn/domain_packs/__init__.py +51 -0
- akms_learn/domain_packs/capabilities.py +49 -0
- akms_learn/domain_packs/descriptors.py +208 -0
- akms_learn/domain_packs/registry.py +133 -0
- akms_learn/domain_packs/warnings.py +65 -0
- akms_learn/exporters/__init__.py +89 -0
- akms_learn/exporters/_mathaware.py +119 -0
- akms_learn/exporters/assessment.py +369 -0
- akms_learn/exporters/bundle.py +332 -0
- akms_learn/exporters/html.py +354 -0
- akms_learn/exporters/markdown.py +532 -0
- akms_learn/exporters/notebook.py +443 -0
- akms_learn/graph_import.py +363 -0
- akms_learn/llm/__init__.py +21 -0
- akms_learn/llm/no_provider_stub.py +121 -0
- akms_learn/llm/protocol.py +105 -0
- akms_learn/llm/providers/__init__.py +6 -0
- akms_learn/llm/providers/akms_completion.py +100 -0
- akms_learn/llm/providers/nlm_cli.py +187 -0
- akms_learn/llm/registry.py +232 -0
- akms_learn/models/__init__.py +76 -0
- akms_learn/models/assessment.py +119 -0
- akms_learn/models/learner_profile.py +56 -0
- akms_learn/models/llm_expansion.py +204 -0
- akms_learn/models/lsp.py +318 -0
- akms_learn/modes/__init__.py +3 -0
- akms_learn/modes/adaptive_path.py +415 -0
- akms_learn/modes/anthology.py +227 -0
- akms_learn/modes/assessment_first.py +553 -0
- akms_learn/modes/bundle_source.py +50 -0
- akms_learn/modes/derivation_first.py +469 -0
- akms_learn/modes/implementation_first.py +468 -0
- akms_learn/modes/llm_expanded.py +590 -0
- akms_learn/modes/multi_granularity.py +507 -0
- akms_learn/modes/notebook_source.py +620 -0
- akms_learn/modes/outline.py +232 -0
- akms_learn/modes/pedagogical_template.py +384 -0
- akms_learn/modes/pitfall.py +266 -0
- akms_learn/optional_metadata.py +185 -0
- akms_learn/ordering.py +512 -0
- akms_learn/plugin.py +88 -0
- akms_learn/profiles.py +3 -0
- akms_learn/requests.py +325 -0
- akms_learn/section_extraction.py +355 -0
- akms_learn/sections.py +329 -0
- akms_learn/toy_fixtures.py +671 -0
- akms_learn/validation.py +107 -0
- akms_learn/warnings.py +145 -0
- akms_learn-0.3.0.dist-info/METADATA +51 -0
- akms_learn-0.3.0.dist-info/RECORD +64 -0
- akms_learn-0.3.0.dist-info/WHEEL +5 -0
- akms_learn-0.3.0.dist-info/entry_points.txt +5 -0
- akms_learn-0.3.0.dist-info/licenses/LICENSE +201 -0
- akms_learn-0.3.0.dist-info/top_level.txt +1 -0
akms_learn/__init__.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""akms_learn: Learning Source Packet compiler and exporters for AKMS."""
|
|
2
|
+
|
|
3
|
+
from akms_learn.cli import main
|
|
4
|
+
from akms_learn.compiler import (
|
|
5
|
+
STAGES,
|
|
6
|
+
CompileResult,
|
|
7
|
+
compile_learning_source,
|
|
8
|
+
)
|
|
9
|
+
from akms_learn.domain_packs import (
|
|
10
|
+
CapabilityStatus,
|
|
11
|
+
CompanionRole,
|
|
12
|
+
DomainPackDescriptor,
|
|
13
|
+
DomainPackRegistry,
|
|
14
|
+
DomainPackWarning,
|
|
15
|
+
LearningCapabilityError,
|
|
16
|
+
RuntimeHint,
|
|
17
|
+
SourcePackDescriptor,
|
|
18
|
+
build_registry_from_paths,
|
|
19
|
+
load_descriptor_from_yaml,
|
|
20
|
+
load_source_pack_from_yaml,
|
|
21
|
+
warn_planned_companion,
|
|
22
|
+
)
|
|
23
|
+
from akms_learn.exporters.bundle import MANIFEST_VERSION
|
|
24
|
+
from akms_learn.exporters.bundle import export as bundle_export
|
|
25
|
+
from akms_learn.exporters.markdown import export as markdown_export
|
|
26
|
+
from akms_learn.graph_import import (
|
|
27
|
+
GraphSlice,
|
|
28
|
+
compute_graph_hash,
|
|
29
|
+
fixture_graph,
|
|
30
|
+
load_graph,
|
|
31
|
+
)
|
|
32
|
+
from akms_learn.modes.anthology import (
|
|
33
|
+
TEACHING_SECTIONS,
|
|
34
|
+
AnthologyEntry,
|
|
35
|
+
anthology_mode,
|
|
36
|
+
)
|
|
37
|
+
from akms_learn.modes.bundle_source import bundle_source_mode
|
|
38
|
+
from akms_learn.modes.outline import outline_mode
|
|
39
|
+
from akms_learn.modes.pitfall import (
|
|
40
|
+
PITFALL_EDGE_TYPES,
|
|
41
|
+
STRUCTURED_FIELDS,
|
|
42
|
+
pitfall_mode,
|
|
43
|
+
)
|
|
44
|
+
from akms_learn.models import (
|
|
45
|
+
AssessmentView,
|
|
46
|
+
CodeLinkView,
|
|
47
|
+
CompilerInfo,
|
|
48
|
+
LearningEdgeView,
|
|
49
|
+
LearningNodeView,
|
|
50
|
+
LearningRequestInfo,
|
|
51
|
+
LearningSourcePacket,
|
|
52
|
+
LearningWarning,
|
|
53
|
+
PacketBody,
|
|
54
|
+
PitfallView,
|
|
55
|
+
ReferenceView,
|
|
56
|
+
SourceInfo,
|
|
57
|
+
)
|
|
58
|
+
from akms_learn.ordering import LEARNING_BUCKETS, order_nodes
|
|
59
|
+
from akms_learn.requests import (
|
|
60
|
+
LearningRequest,
|
|
61
|
+
normalize_request,
|
|
62
|
+
request_hash,
|
|
63
|
+
to_canonical_dict,
|
|
64
|
+
)
|
|
65
|
+
from akms_learn.section_extraction import (
|
|
66
|
+
APPROVED_HEADINGS,
|
|
67
|
+
EXCERPT_MAX_CHARS,
|
|
68
|
+
ExtractedSection,
|
|
69
|
+
ExtractionMethod,
|
|
70
|
+
extract_sections_from_node,
|
|
71
|
+
extract_sections_from_nodes,
|
|
72
|
+
)
|
|
73
|
+
from akms_learn.sections import APPROVED_SECTIONS, SectionView, extract_sections
|
|
74
|
+
from akms_learn.validation import PacketValidationError, validate_packet
|
|
75
|
+
from akms_learn.warnings import (
|
|
76
|
+
WarningAccumulator,
|
|
77
|
+
emit_dangling_reference_warning,
|
|
78
|
+
emit_missing_section_warning,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
# Compiler
|
|
83
|
+
"STAGES",
|
|
84
|
+
"CompileResult",
|
|
85
|
+
"compile_learning_source",
|
|
86
|
+
# Graph import
|
|
87
|
+
"GraphSlice",
|
|
88
|
+
"load_graph",
|
|
89
|
+
"compute_graph_hash",
|
|
90
|
+
"fixture_graph",
|
|
91
|
+
# Ordering
|
|
92
|
+
"LEARNING_BUCKETS",
|
|
93
|
+
"order_nodes",
|
|
94
|
+
# Sections
|
|
95
|
+
"APPROVED_SECTIONS",
|
|
96
|
+
"SectionView",
|
|
97
|
+
"extract_sections",
|
|
98
|
+
# Node-level section extraction
|
|
99
|
+
"APPROVED_HEADINGS",
|
|
100
|
+
"EXCERPT_MAX_CHARS",
|
|
101
|
+
"ExtractedSection",
|
|
102
|
+
"ExtractionMethod",
|
|
103
|
+
"extract_sections_from_node",
|
|
104
|
+
"extract_sections_from_nodes",
|
|
105
|
+
# LSP models
|
|
106
|
+
"LearningSourcePacket",
|
|
107
|
+
"CompilerInfo",
|
|
108
|
+
"SourceInfo",
|
|
109
|
+
"LearningRequestInfo",
|
|
110
|
+
"PacketBody",
|
|
111
|
+
"LearningNodeView",
|
|
112
|
+
"LearningEdgeView",
|
|
113
|
+
"PitfallView",
|
|
114
|
+
"CodeLinkView",
|
|
115
|
+
"AssessmentView",
|
|
116
|
+
"ReferenceView",
|
|
117
|
+
"LearningWarning",
|
|
118
|
+
"LearningRequest",
|
|
119
|
+
"normalize_request",
|
|
120
|
+
"request_hash",
|
|
121
|
+
"to_canonical_dict",
|
|
122
|
+
"WarningAccumulator",
|
|
123
|
+
"emit_missing_section_warning",
|
|
124
|
+
"emit_dangling_reference_warning",
|
|
125
|
+
"PacketValidationError",
|
|
126
|
+
"validate_packet",
|
|
127
|
+
# Domain-pack foundation
|
|
128
|
+
"CapabilityStatus",
|
|
129
|
+
"CompanionRole",
|
|
130
|
+
"DomainPackDescriptor",
|
|
131
|
+
"DomainPackRegistry",
|
|
132
|
+
"DomainPackWarning",
|
|
133
|
+
"LearningCapabilityError",
|
|
134
|
+
"RuntimeHint",
|
|
135
|
+
"SourcePackDescriptor",
|
|
136
|
+
"build_registry_from_paths",
|
|
137
|
+
"load_descriptor_from_yaml",
|
|
138
|
+
"load_source_pack_from_yaml",
|
|
139
|
+
"warn_planned_companion",
|
|
140
|
+
# Exporters
|
|
141
|
+
"markdown_export",
|
|
142
|
+
"bundle_export",
|
|
143
|
+
"MANIFEST_VERSION",
|
|
144
|
+
# Structured modes
|
|
145
|
+
"outline_mode",
|
|
146
|
+
"anthology_mode",
|
|
147
|
+
"AnthologyEntry",
|
|
148
|
+
"TEACHING_SECTIONS",
|
|
149
|
+
"pitfall_mode",
|
|
150
|
+
"PITFALL_EDGE_TYPES",
|
|
151
|
+
"STRUCTURED_FIELDS",
|
|
152
|
+
"bundle_source_mode",
|
|
153
|
+
# CLI
|
|
154
|
+
"main",
|
|
155
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Shared helpers for code-link extraction.
|
|
2
|
+
|
|
3
|
+
Promoted from ``compiler.py`` and ``modes/implementation_first.py`` to
|
|
4
|
+
remove duplication. Lives at the package
|
|
5
|
+
top level so both the compiler stage and the ``implementation_first`` mode
|
|
6
|
+
can call into the same authoritative implementation.
|
|
7
|
+
|
|
8
|
+
Surface
|
|
9
|
+
-------
|
|
10
|
+
* :data:`MISSING_SOURCE_PATH_SENTINELS` — frozenset of placeholder strings
|
|
11
|
+
that count as "no usable source path" (``"", "unknown", "none", "null"``).
|
|
12
|
+
* :func:`is_missing_source_path` — predicate over a raw ``source_path``
|
|
13
|
+
value; case-insensitive, whitespace-tolerant, None-safe.
|
|
14
|
+
* :func:`coerce_line_range` — best-effort coercion of a node/edge
|
|
15
|
+
``line_range`` value into a ``(start, end)`` integer tuple.
|
|
16
|
+
* :func:`build_code_links` — walks ``implements`` edges and emits one
|
|
17
|
+
:class:`CodeLinkView` per edge. Optionally invokes a missing-source
|
|
18
|
+
callback so the compiler can emit ``code_mirror_missing_source_path``
|
|
19
|
+
warnings while the mode keeps its own simpler warning path.
|
|
20
|
+
|
|
21
|
+
Callers
|
|
22
|
+
-------
|
|
23
|
+
* ``compiler._build_code_links`` thin-wraps :func:`build_code_links`
|
|
24
|
+
passing the WarningAccumulator-driven callback.
|
|
25
|
+
* ``implementation_first._build_code_references`` uses the no-callback
|
|
26
|
+
form; it emits ``implementation_anchor_missing_source`` separately
|
|
27
|
+
via :func:`implementation_first._emit_anchor_missing_source_warnings`.
|
|
28
|
+
|
|
29
|
+
The module has no LLM imports.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from typing import Any, Callable, Optional
|
|
35
|
+
|
|
36
|
+
from akms_learn.models import CodeLinkView
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"MISSING_SOURCE_PATH_SENTINELS",
|
|
40
|
+
"is_missing_source_path",
|
|
41
|
+
"coerce_line_range",
|
|
42
|
+
"build_code_links",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
MISSING_SOURCE_PATH_SENTINELS: frozenset[str] = frozenset(
|
|
47
|
+
{"", "unknown", "none", "null"}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_missing_source_path(value: Any) -> bool:
|
|
52
|
+
"""Return True if *value* is an unusable placeholder source path.
|
|
53
|
+
|
|
54
|
+
Treats ``None``, empty string, and the canonical sentinels
|
|
55
|
+
(``"unknown"`` / ``"none"`` / ``"null"``) as missing. Comparison is
|
|
56
|
+
case-insensitive after stripping whitespace.
|
|
57
|
+
"""
|
|
58
|
+
if value is None:
|
|
59
|
+
return True
|
|
60
|
+
text = str(value).strip().lower()
|
|
61
|
+
return text in MISSING_SOURCE_PATH_SENTINELS
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def coerce_line_range(value: Any) -> tuple[int, int]:
|
|
65
|
+
"""Best-effort coercion of a node/edge ``line_range`` to ``(start, end)``.
|
|
66
|
+
|
|
67
|
+
Returns ``(0, 0)`` for any malformed input (non-list, wrong length,
|
|
68
|
+
non-integer elements). The placeholder pair is recognised by callers
|
|
69
|
+
that want to skip uninformative line ranges.
|
|
70
|
+
"""
|
|
71
|
+
if isinstance(value, (list, tuple)) and len(value) == 2:
|
|
72
|
+
try:
|
|
73
|
+
return (int(value[0]), int(value[1]))
|
|
74
|
+
except (TypeError, ValueError):
|
|
75
|
+
pass
|
|
76
|
+
return (0, 0)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def build_code_links(
|
|
80
|
+
edges: tuple[dict[str, Any], ...] | list[dict[str, Any]],
|
|
81
|
+
nodes_by_id: dict[str, dict[str, Any]],
|
|
82
|
+
on_missing_mirror_source: Optional[Callable[[str, str], None]] = None,
|
|
83
|
+
) -> list[CodeLinkView]:
|
|
84
|
+
"""Walk ``implements`` edges and emit one :class:`CodeLinkView` per edge.
|
|
85
|
+
|
|
86
|
+
Behaviour:
|
|
87
|
+
|
|
88
|
+
* One :class:`CodeLinkView` per edge with ``type == "implements"``.
|
|
89
|
+
* ``source_node_id`` is the edge's ``from`` endpoint.
|
|
90
|
+
* ``target`` resolves to the target node id (always set when present).
|
|
91
|
+
* ``relation`` is fixed to ``"implements"``.
|
|
92
|
+
* ``file_path`` and ``line_range`` come from the target node when the
|
|
93
|
+
target has a usable ``source_path``; otherwise the fields stay
|
|
94
|
+
``None``.
|
|
95
|
+
* When *on_missing_mirror_source* is supplied AND the target node is a
|
|
96
|
+
``code_mirror`` with a missing/sentinel ``source_path``, the callback
|
|
97
|
+
is invoked once per such edge with ``(mirror_node_id, edge_id)``.
|
|
98
|
+
The mode-level caller (``implementation_first``) passes ``None`` so
|
|
99
|
+
it can emit its own ``implementation_anchor_missing_source`` warning
|
|
100
|
+
via a separate code path.
|
|
101
|
+
|
|
102
|
+
The collector is order-stable: edges are processed in the order they
|
|
103
|
+
arrive in (assumed already sorted by the caller) and the returned list
|
|
104
|
+
preserves that order.
|
|
105
|
+
"""
|
|
106
|
+
code_links: list[CodeLinkView] = []
|
|
107
|
+
for edge in edges:
|
|
108
|
+
if edge.get("type") != "implements":
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
source_id = str(edge.get("from") or "")
|
|
112
|
+
target_id = str(edge.get("to") or "")
|
|
113
|
+
edge_id = str(edge.get("edge_id") or "")
|
|
114
|
+
|
|
115
|
+
target_node = nodes_by_id.get(target_id) or {}
|
|
116
|
+
target_source_path = target_node.get("source_path")
|
|
117
|
+
line_range_raw = target_node.get("line_range")
|
|
118
|
+
|
|
119
|
+
usable_path: Optional[str] = (
|
|
120
|
+
None
|
|
121
|
+
if is_missing_source_path(target_source_path)
|
|
122
|
+
else str(target_source_path)
|
|
123
|
+
)
|
|
124
|
+
usable_line_range: Optional[tuple[int, int]] = None
|
|
125
|
+
if usable_path is not None and line_range_raw is not None:
|
|
126
|
+
coerced = coerce_line_range(line_range_raw)
|
|
127
|
+
if coerced != (0, 0):
|
|
128
|
+
usable_line_range = coerced
|
|
129
|
+
|
|
130
|
+
target_value = target_id or (usable_path or "")
|
|
131
|
+
|
|
132
|
+
code_links.append(
|
|
133
|
+
CodeLinkView(
|
|
134
|
+
node_id=source_id or target_id or edge_id,
|
|
135
|
+
source_file=usable_path or "unknown",
|
|
136
|
+
source_node_id=source_id or None,
|
|
137
|
+
target=target_value or None,
|
|
138
|
+
relation="implements",
|
|
139
|
+
file_path=usable_path,
|
|
140
|
+
line_range=usable_line_range,
|
|
141
|
+
mirror_node_id=(
|
|
142
|
+
target_id if target_node.get("kind") == "code_mirror" else None
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
on_missing_mirror_source is not None
|
|
149
|
+
and target_node.get("kind") == "code_mirror"
|
|
150
|
+
and is_missing_source_path(target_source_path)
|
|
151
|
+
):
|
|
152
|
+
on_missing_mirror_source(target_id, edge_id)
|
|
153
|
+
|
|
154
|
+
return code_links
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Adapter protocols package for akms-learn.
|
|
2
|
+
|
|
3
|
+
This package defines pure-Python protocol surfaces for four advanced-companion
|
|
4
|
+
adapters. No real adapter implementations ship here; all concrete
|
|
5
|
+
implementations are provided by downstream packages.
|
|
6
|
+
|
|
7
|
+
No-mutation invariant
|
|
8
|
+
---------------------
|
|
9
|
+
Adapters in this package MUST NOT:
|
|
10
|
+
- Write to the AKMS global vault or any AKMS-owned path.
|
|
11
|
+
- Mutate any AKMS graph object passed to them.
|
|
12
|
+
- Write files via any file-write API targeting AKMS-owned paths.
|
|
13
|
+
|
|
14
|
+
Public surface
|
|
15
|
+
--------------
|
|
16
|
+
``protocols`` — the four runtime_checkable Protocol classes.
|
|
17
|
+
``status`` — AdapterStatus enum + adapter_registry() accessor.
|
|
18
|
+
``fake_adapter``— Fake implementations for use in tests ONLY.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from akms_learn.adapters.protocols import (
|
|
22
|
+
ConceptKitAdapter,
|
|
23
|
+
ExecutableBridgeAdapter,
|
|
24
|
+
NotebookExecutionAdapter,
|
|
25
|
+
PedagogicalWorkbenchAdapter,
|
|
26
|
+
)
|
|
27
|
+
from akms_learn.adapters.status import AdapterStatus, adapter_registry
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Protocols
|
|
31
|
+
"ConceptKitAdapter",
|
|
32
|
+
"ExecutableBridgeAdapter",
|
|
33
|
+
"NotebookExecutionAdapter",
|
|
34
|
+
"PedagogicalWorkbenchAdapter",
|
|
35
|
+
# Status
|
|
36
|
+
"AdapterStatus",
|
|
37
|
+
"adapter_registry",
|
|
38
|
+
]
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Fake adapter implementations for tests ONLY.
|
|
2
|
+
|
|
3
|
+
WARNING: These classes are for use in tests ONLY. They are intentionally
|
|
4
|
+
trivial; they return hard-coded stub payloads and do NOT perform any real
|
|
5
|
+
computation. Never import fake_adapter in production code.
|
|
6
|
+
|
|
7
|
+
No-mutation invariant
|
|
8
|
+
---------------------
|
|
9
|
+
Fake adapters MUST NOT write to any AKMS path. All output is returned in
|
|
10
|
+
memory as plain Python dicts.
|
|
11
|
+
|
|
12
|
+
Protocol conformance
|
|
13
|
+
--------------------
|
|
14
|
+
Each class satisfies its corresponding Protocol via structural subtyping.
|
|
15
|
+
Because the Protocols are ``@runtime_checkable``, the isinstance checks::
|
|
16
|
+
|
|
17
|
+
isinstance(FakeConceptKit(), ConceptKitAdapter)
|
|
18
|
+
isinstance(FakePedagogicalWorkbench(), PedagogicalWorkbenchAdapter)
|
|
19
|
+
isinstance(FakeExecutableBridge(), ExecutableBridgeAdapter)
|
|
20
|
+
isinstance(FakeNotebookExecution(), NotebookExecutionAdapter)
|
|
21
|
+
|
|
22
|
+
all return True.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from akms_learn.adapters.protocols import (
|
|
30
|
+
ConceptKitAdapter,
|
|
31
|
+
ExecutableBridgeAdapter,
|
|
32
|
+
NotebookExecutionAdapter,
|
|
33
|
+
PedagogicalWorkbenchAdapter,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"FakeConceptKit",
|
|
38
|
+
"FakeExecutableBridge",
|
|
39
|
+
"FakeNotebookExecution",
|
|
40
|
+
"FakePedagogicalWorkbench",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FakeConceptKit:
|
|
45
|
+
"""Fake ConceptKitAdapter — returns a deterministic stub payload.
|
|
46
|
+
|
|
47
|
+
Satisfies :class:`~akms_learn.adapters.protocols.ConceptKitAdapter`
|
|
48
|
+
structurally (and via ``isinstance`` due to ``@runtime_checkable``).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def generate_concept_kit(
|
|
52
|
+
self,
|
|
53
|
+
excerpt: dict[str, Any],
|
|
54
|
+
*,
|
|
55
|
+
options: dict[str, Any] | None = None,
|
|
56
|
+
) -> dict[str, Any]:
|
|
57
|
+
"""Return a deterministic stub concept-kit payload."""
|
|
58
|
+
return {
|
|
59
|
+
"adapter": "FakeConceptKit",
|
|
60
|
+
"status": "ok",
|
|
61
|
+
"concepts": ["stub_concept_A", "stub_concept_B"],
|
|
62
|
+
"excerpt_keys": sorted(excerpt.keys()),
|
|
63
|
+
"options_received": options is not None,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FakePedagogicalWorkbench:
|
|
68
|
+
"""Fake PedagogicalWorkbenchAdapter — returns a deterministic stub payload.
|
|
69
|
+
|
|
70
|
+
Satisfies :class:`~akms_learn.adapters.protocols.PedagogicalWorkbenchAdapter`
|
|
71
|
+
structurally (and via ``isinstance`` due to ``@runtime_checkable``).
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def analyse_pedagogy(
|
|
75
|
+
self,
|
|
76
|
+
excerpt: dict[str, Any],
|
|
77
|
+
*,
|
|
78
|
+
options: dict[str, Any] | None = None,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""Return a deterministic stub pedagogical analysis payload."""
|
|
81
|
+
return {
|
|
82
|
+
"adapter": "FakePedagogicalWorkbench",
|
|
83
|
+
"status": "ok",
|
|
84
|
+
"objectives": ["stub_objective_1"],
|
|
85
|
+
"difficulty": "intermediate",
|
|
86
|
+
"excerpt_keys": sorted(excerpt.keys()),
|
|
87
|
+
"options_received": options is not None,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class FakeExecutableBridge:
|
|
92
|
+
"""Fake ExecutableBridgeAdapter — returns a deterministic stub payload.
|
|
93
|
+
|
|
94
|
+
Satisfies :class:`~akms_learn.adapters.protocols.ExecutableBridgeAdapter`
|
|
95
|
+
structurally (and via ``isinstance`` due to ``@runtime_checkable``).
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def build_executable(
|
|
99
|
+
self,
|
|
100
|
+
excerpt: dict[str, Any],
|
|
101
|
+
*,
|
|
102
|
+
options: dict[str, Any] | None = None,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
"""Return a deterministic stub executable-artefact payload."""
|
|
105
|
+
return {
|
|
106
|
+
"adapter": "FakeExecutableBridge",
|
|
107
|
+
"status": "ok",
|
|
108
|
+
"executable_type": "stub",
|
|
109
|
+
"source": "# stub source",
|
|
110
|
+
"excerpt_keys": sorted(excerpt.keys()),
|
|
111
|
+
"options_received": options is not None,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class FakeNotebookExecution:
|
|
116
|
+
"""Fake NotebookExecutionAdapter — returns a deterministic stub payload.
|
|
117
|
+
|
|
118
|
+
Satisfies :class:`~akms_learn.adapters.protocols.NotebookExecutionAdapter`
|
|
119
|
+
structurally (and via ``isinstance`` due to ``@runtime_checkable``).
|
|
120
|
+
|
|
121
|
+
The stub cells include all three notebook-metadata keys described in the
|
|
122
|
+
``NotebookExecutionAdapter`` docstring (``no_execute``,
|
|
123
|
+
``illustrative_only``, ``adapter_executable``) so that downstream
|
|
124
|
+
notebook-exporter tests can rely on them.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def build_notebook(
|
|
128
|
+
self,
|
|
129
|
+
excerpt: dict[str, Any],
|
|
130
|
+
*,
|
|
131
|
+
options: dict[str, Any] | None = None,
|
|
132
|
+
) -> dict[str, Any]:
|
|
133
|
+
"""Return a deterministic stub notebook payload."""
|
|
134
|
+
stub_cell = {
|
|
135
|
+
"cell_type": "code",
|
|
136
|
+
"source": "# stub cell",
|
|
137
|
+
"metadata": {
|
|
138
|
+
"no_execute": False,
|
|
139
|
+
"illustrative_only": True,
|
|
140
|
+
"adapter_executable": False,
|
|
141
|
+
},
|
|
142
|
+
"outputs": [],
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
"adapter": "FakeNotebookExecution",
|
|
146
|
+
"status": "ok",
|
|
147
|
+
"nbformat": 4,
|
|
148
|
+
"nbformat_minor": 5,
|
|
149
|
+
"cells": [stub_cell],
|
|
150
|
+
"excerpt_keys": sorted(excerpt.keys()),
|
|
151
|
+
"options_received": options is not None,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Runtime isinstance-compatibility assertions (module-level, evaluated once).
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# These raise AssertionError at import time if the structural subtyping is
|
|
159
|
+
# accidentally broken, making test failures loud and immediate.
|
|
160
|
+
|
|
161
|
+
assert isinstance(FakeConceptKit(), ConceptKitAdapter), (
|
|
162
|
+
"FakeConceptKit must satisfy ConceptKitAdapter protocol"
|
|
163
|
+
)
|
|
164
|
+
assert isinstance(FakePedagogicalWorkbench(), PedagogicalWorkbenchAdapter), (
|
|
165
|
+
"FakePedagogicalWorkbench must satisfy PedagogicalWorkbenchAdapter protocol"
|
|
166
|
+
)
|
|
167
|
+
assert isinstance(FakeExecutableBridge(), ExecutableBridgeAdapter), (
|
|
168
|
+
"FakeExecutableBridge must satisfy ExecutableBridgeAdapter protocol"
|
|
169
|
+
)
|
|
170
|
+
assert isinstance(FakeNotebookExecution(), NotebookExecutionAdapter), (
|
|
171
|
+
"FakeNotebookExecution must satisfy NotebookExecutionAdapter protocol"
|
|
172
|
+
)
|