lesson_plan_template
Lesson plan skeleton for a subject, level and duration: warm-up, presentation, practice, production, assessment and closure with whole minutes allocated in proportion to stage weights (largest remainder, so they add up exactly), and objectives written with verbs of the revised Bloom taxonomy (Anderson & Krathwohl 2001) chosen by level, in English or Spanish. Returns the stages, the objectives and a Markdown plan.
Install
/tools install lesson_plan_template /tools install lesson_plan_template@1.0.0
In a console session, or let the model find it in normal mode: axlr_catalog describes it and axlr_install_tool asks for the same install card. The card reads catalog axlr · lesson_plan_template 1.0.0 · verified once the index signature, the manifest digest and every file digest check out. Then the model runs it with axlr_run_tool lesson_plan_template; each run is approved as a local_exec.
Arguments
One JSON object on stdin, validated against the schema before the program starts.
| name | type | required | default | meaning |
|---|---|---|---|---|
subject | string | yes | length ≥ 1; length ≤ 200 | |
topic | string | no | length ≤ 200 | |
level | string | no | "secondary" | one of "primary", "secondary", "upper_secondary", "university", "adult" |
duration_minutes | integer | yes | ≥ 10; ≤ 480 | |
language | string | no | "en" | one of "en", "es" |
objectives | array of object | no | items ≥ 1; items ≤ 12 | |
objective_count | integer | no | 3 | Objectives to draft when none are given. ≥ 1; ≤ 6 |
stage_weights | object | no | Default 10, 25, 25, 20, 10, 10; 0 leaves a stage out |
Input schema (JSON)
{
"type": "object",
"required": [
"subject",
"duration_minutes"
],
"additionalProperties": false,
"properties": {
"subject": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"topic": {
"type": "string",
"maxLength": 200
},
"level": {
"type": "string",
"enum": [
"primary",
"secondary",
"upper_secondary",
"university",
"adult"
],
"default": "secondary"
},
"duration_minutes": {
"type": "integer",
"minimum": 10,
"maximum": 480
},
"language": {
"type": "string",
"enum": [
"en",
"es"
],
"default": "en"
},
"objectives": {
"type": "array",
"minItems": 1,
"maxItems": 12,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"level"
],
"properties": {
"level": {
"type": "string",
"enum": [
"remember",
"understand",
"apply",
"analyze",
"evaluate",
"create"
]
},
"content": {
"type": "string",
"maxLength": 200
},
"verb": {
"type": "string",
"maxLength": 40,
"description": "Overrides the table's verb"
}
}
}
},
"objective_count": {
"type": "integer",
"minimum": 1,
"maximum": 6,
"default": 3,
"description": "Objectives to draft when none are given"
},
"stage_weights": {
"type": "object",
"additionalProperties": false,
"properties": {
"warm_up": {
"type": "number",
"minimum": 0
},
"presentation": {
"type": "number",
"minimum": 0
},
"practice": {
"type": "number",
"minimum": 0
},
"production": {
"type": "number",
"minimum": 0
},
"assessment": {
"type": "number",
"minimum": 0
},
"closure": {
"type": "number",
"minimum": 0
}
},
"description": "Default 10, 25, 25, 20, 10, 10; 0 leaves a stage out"
}
}
}
Example
Input
{
"subject": "Biology",
"topic": "photosynthesis",
"duration_minutes": 50
}
Output (stdout)
{
"stages": [
{
"stage": "warm_up",
"minutes": 5
},
{
"stage": "presentation",
"minutes": 13
},
{
"stage": "practice",
"minutes": 12
},
{
"stage": "production",
"minutes": 10
},
{
"stage": "assessment",
"minutes": 5
},
{
"stage": "closure",
"minutes": 5
}
]
}
Files
The command the install card registers is python3 lesson_plan_template.py, run in the workspace with PATH and HOME as its only environment. Each file's SHA-256 is in the signed index; the console checks them before showing the card and again before writing.
lesson_plan_template.py · sha256 502feb1db654efc4dde7f206b57ea0e16a6c2c5007d64d6e97e2b6815327790b
#!/usr/bin/env python3
"""Lesson plan skeleton: stages with minutes allocated in proportion to weights (largest
remainder, so they add up to the duration) and objectives written with verbs of the revised
Bloom taxonomy (Anderson & Krathwohl 2001), in English or Spanish.
Input (JSON on stdin):
subject string (required)
topic string, optional
level "primary" | "secondary" | "upper_secondary" | "university" | "adult" default secondary
duration_minutes integer 10-480 (required)
language "en" | "es" default en
objectives optional [{"level": "analyze", "content": "..."}] or strings with a Bloom level prefix
objective_count when objectives are absent: how many to draft (1-6, default 3)
stage_weights optional {"warm_up": 10, "presentation": 25, "practice": 25, "production": 20,
"assessment": 10, "closure": 10} (the default); a stage with weight 0 is left out
"""
import json
import sys
from fractions import Fraction
LEVELS = ["remember", "understand", "apply", "analyze", "evaluate", "create"]
LEVEL_NAMES = {"en": ["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"],
"es": ["Recordar", "Comprender", "Aplicar", "Analizar", "Evaluar", "Crear"]}
# Verbs per level: the taxonomy's own process verbs first (Anderson & Krathwohl 2001, Table 5.1),
# then common objective verbs; compiled for this tool.
VERBS = {
"en": {"remember": ["identify", "recall", "list", "define", "name"],
"understand": ["explain", "summarize", "classify", "compare", "interpret"],
"apply": ["use", "solve", "implement", "calculate", "demonstrate"],
"analyze": ["differentiate", "organize", "distinguish", "examine", "outline"],
"evaluate": ["judge", "critique", "justify", "assess", "check"],
"create": ["design", "produce", "plan", "compose", "generate"]},
"es": {"remember": ["identificar", "recordar", "enumerar", "definir", "nombrar"],
"understand": ["explicar", "resumir", "clasificar", "comparar", "interpretar"],
"apply": ["usar", "resolver", "implementar", "calcular", "demostrar"],
"analyze": ["diferenciar", "organizar", "distinguir", "examinar", "esquematizar"],
"evaluate": ["juzgar", "criticar", "justificar", "valorar", "comprobar"],
"create": ["diseñar", "producir", "planificar", "componer", "generar"]},
}
DEFAULT_LEVELS = {"primary": ["remember", "understand", "apply"], "secondary": ["understand", "apply", "analyze"],
"upper_secondary": ["apply", "analyze", "evaluate"], "university": ["analyze", "evaluate", "create"],
"adult": ["apply", "analyze", "create"]}
EXTRA_LEVELS = ["remember", "understand", "apply", "analyze", "evaluate", "create"]
STAGES = ["warm_up", "presentation", "practice", "production", "assessment", "closure"]
DEFAULT_WEIGHTS = {"warm_up": 10, "presentation": 25, "practice": 25, "production": 20, "assessment": 10, "closure": 10}
TEXT = {
"en": {
"titles": {"warm_up": "Warm-up", "presentation": "Presentation", "practice": "Controlled practice",
"production": "Production", "assessment": "Assessment", "closure": "Closure"},
"activities": {
"warm_up": "Activate prior knowledge of {topic}: a question, image or short quiz; share the objectives.",
"presentation": "Introduce the new content on {topic} with examples; check understanding with questions.",
"practice": "Guided exercises with feedback, individually or in pairs.",
"production": "Open task where students apply {topic} with more autonomy (project, problem, discussion).",
"assessment": "Check the objectives ({objectives}): exit ticket, short quiz or observation with a rubric.",
"closure": "Summarise the key points, answer questions and preview the next lesson."},
"objective": "By the end of the lesson, students will be able to {verb} {content}.",
"heading": "Lesson plan", "level": "Level", "duration": "Duration", "objectives": "Objectives",
"sequence": "Sequence", "stage": "Stage", "minutes": "Minutes", "time": "Time", "activities_h": "Activities",
"materials": "Materials", "materials_text": "[list materials and resources]", "content": "[content]", "about": "[content about {topic}]",
"levels": {"primary": "Primary", "secondary": "Secondary", "upper_secondary": "Upper secondary",
"university": "University", "adult": "Adult education"},
},
"es": {
"titles": {"warm_up": "Activación", "presentation": "Presentación", "practice": "Práctica guiada",
"production": "Producción", "assessment": "Evaluación", "closure": "Cierre"},
"activities": {
"warm_up": "Activar conocimientos previos sobre {topic}: una pregunta, imagen o cuestionario breve; compartir los objetivos.",
"presentation": "Presentar el contenido nuevo sobre {topic} con ejemplos; comprobar la comprensión con preguntas.",
"practice": "Ejercicios guiados con retroalimentación, individualmente o por parejas.",
"production": "Tarea abierta en la que el alumnado aplica {topic} con más autonomía (proyecto, problema, debate).",
"assessment": "Comprobar los objetivos ({objectives}): ticket de salida, cuestionario breve u observación con rúbrica.",
"closure": "Resumir las ideas clave, resolver dudas y anticipar la próxima sesión."},
"objective": "Al final de la sesión, el alumnado será capaz de {verb} {content}.",
"heading": "Plan de clase", "level": "Nivel", "duration": "Duración", "objectives": "Objetivos",
"sequence": "Secuencia", "stage": "Fase", "minutes": "Minutos", "time": "Tiempo", "activities_h": "Actividades",
"materials": "Materiales", "materials_text": "[enumerar materiales y recursos]", "content": "[contenido]", "about": "[contenido sobre {topic}]",
"levels": {"primary": "Primaria", "secondary": "Secundaria", "upper_secondary": "Bachillerato",
"university": "Universidad", "adult": "Educación de adultos"},
},
}
def fail(message):
print(json.dumps({"error": message}))
sys.exit(2)
def allocate(duration, weights):
"""Largest-remainder (Hamilton) allocation of whole minutes; ties go to the earlier stage."""
total = sum(weights.values())
exact = {s: Fraction(duration) * w / total for s, w in weights.items()}
minutes = {s: int(v) for s, v in exact.items()}
left = duration - sum(minutes.values())
order = sorted(weights, key=lambda s: (-(exact[s] - minutes[s]), STAGES.index(s)))
for s in order[:left]:
minutes[s] += 1
return minutes, exact
def clean(value, field, required=False, limit=200):
if value is None and not required:
return None
if not isinstance(value, str) or not value.strip() or len(value) > limit:
fail(f"{field} must be a non-empty string of at most {limit} characters")
return value.strip()
def main():
try:
request = json.load(sys.stdin)
except ValueError as error:
fail(f"stdin is not JSON: {error}")
if not isinstance(request, dict):
fail("input must be a JSON object")
extra = sorted(set(request) - {"subject", "topic", "level", "duration_minutes", "language", "objectives",
"objective_count", "stage_weights"})
if extra:
fail(f"unknown field(s): {', '.join(extra)}")
subject = clean(request.get("subject"), "subject", required=True)
topic = clean(request.get("topic"), "topic")
level = request.get("level", "secondary")
if not isinstance(level, str) or level not in DEFAULT_LEVELS:
fail(f"level must be one of {', '.join(DEFAULT_LEVELS)}")
language = request.get("language", "en")
if not isinstance(language, str) or language not in TEXT:
fail("language must be en or es")
t = TEXT[language]
duration = request.get("duration_minutes")
if isinstance(duration, bool) or not isinstance(duration, int) or not 10 <= duration <= 480:
fail("duration_minutes must be an integer from 10 to 480")
weights = request.get("stage_weights", DEFAULT_WEIGHTS)
if not isinstance(weights, dict) or set(weights) - set(STAGES):
fail(f"stage_weights keys must be among {', '.join(STAGES)}")
for s, w in weights.items():
if isinstance(w, bool) or not isinstance(w, (int, float)) or w < 0:
fail(f"stage_weights.{s} must be a number zero or more")
weights = {s: Fraction(str(weights[s])) for s in STAGES if s in weights and weights[s] > 0}
if not weights:
fail("stage_weights must give at least one stage a positive weight")
raw = request.get("objectives")
specs = []
if raw is not None:
if not isinstance(raw, list) or not 1 <= len(raw) <= 12:
fail("objectives must be a list of 1 to 12 items")
for i, item in enumerate(raw):
if not isinstance(item, dict) or set(item) - {"level", "content", "verb"}:
fail(f"objectives[{i}] must be an object with level, content and optional verb")
lv = item.get("level")
if lv not in LEVELS:
fail(f"objectives[{i}].level must be one of {', '.join(LEVELS)}")
specs.append((lv, clean(item.get("content"), f"objectives[{i}].content"),
clean(item.get("verb"), f"objectives[{i}].verb", limit=40)))
else:
count = request.get("objective_count", 3)
if isinstance(count, bool) or not isinstance(count, int) or not 1 <= count <= 6:
fail("objective_count must be an integer from 1 to 6")
base = DEFAULT_LEVELS[level]
chosen = (base + [lv for lv in EXTRA_LEVELS if lv not in base])[:count]
draft = t["about"].format(topic=topic) if topic else None
specs = [(lv, draft, None) for lv in sorted(chosen, key=LEVELS.index)]
used = {lv: 0 for lv in LEVELS}
objectives = []
for lv, content, verb in specs:
if verb is None:
verb = VERBS[language][lv][used[lv] % len(VERBS[language][lv])]
used[lv] += 1
objectives.append({"level": lv, "level_name": LEVEL_NAMES[language][LEVELS.index(lv)], "verb": verb,
"text": t["objective"].format(verb=verb, content=content or t["content"])})
minutes, exact = allocate(duration, weights)
topic_text = topic or subject
numbers = ", ".join(str(i + 1) for i in range(len(objectives)))
stages, clock = [], 0
for s in STAGES:
if s not in weights:
continue
stages.append({"stage": s, "title": t["titles"][s], "minutes": minutes[s], "start_minute": clock,
"end_minute": clock + minutes[s], "exact_share": str(exact[s]),
"activities": t["activities"][s].format(topic=topic_text, objectives=numbers)})
clock += minutes[s]
lines = [f"# {t['heading']}: {subject}" + (f" — {topic}" if topic else ""), "",
f"**{t['level']}:** {t['levels'][level]} ", f"**{t['duration']}:** {duration} min", "",
f"## {t['objectives']}", ""]
lines += [f"{i + 1}. {o['text']} *({o['level_name']})*" for i, o in enumerate(objectives)]
lines += ["", f"## {t['sequence']}", "", f"| {t['stage']} | {t['minutes']} | {t['time']} | {t['activities_h']} |",
"|:--|--:|:--|:--|"]
lines += [f"| {s['title']} | {s['minutes']} | {s['start_minute']}–{s['end_minute']} | {s['activities']} |" for s in stages]
lines += ["", f"## {t['materials']}", "", t["materials_text"], ""]
print(json.dumps({
"subject": subject, "topic": topic, "level": level, "language": language, "duration_minutes": duration,
"objectives": objectives, "stages": stages, "markdown": "\n".join(lines),
"allocation": "largest remainder over the stage weights; ties to the earlier stage",
"source": "Bloom verbs after the revised taxonomy (Anderson & Krathwohl 2001); stages after the "
"presentation-practice-production lesson model",
}, ensure_ascii=False))
if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception as error: # last resort: a usage error, never a traceback
fail(f"invalid input ({type(error).__name__})")
Where it is served
The registry serves only AXLR clients, so a browser gets 403; fetch with a User-Agent that starts with axlr:
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/lesson_plan_template/1.0.0/manifest.json
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/lesson_plan_template/1.0.0/lesson_plan_template.py