clawed 2.1.1__tar.gz → 2.3.1__tar.gz
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.
- {clawed-2.1.1 → clawed-2.3.1}/PKG-INFO +8 -6
- {clawed-2.1.1 → clawed-2.3.1}/README.md +7 -5
- {clawed-2.1.1 → clawed-2.3.1}/clawed/__init__.py +1 -1
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/core.py +20 -1
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/episodes.py +50 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/loader.py +23 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/generate_lesson_bundle.py +59 -43
- {clawed-2.1.1 → clawed-2.3.1}/clawed/export_docx.py +209 -0
- clawed-2.3.1/clawed/export_handout.py +368 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/export_pptx.py +100 -44
- {clawed-2.1.1 → clawed-2.3.1}/clawed/llm.py +78 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/memory_engine.py +180 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/models.py +117 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/onboarding.py +22 -5
- clawed-2.3.1/clawed/prompts/admin_lesson_plan.txt +69 -0
- clawed-2.3.1/clawed/prompts/student_packet.txt +87 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/workspace.py +1 -0
- {clawed-2.1.1 → clawed-2.3.1}/pyproject.toml +1 -1
- clawed-2.1.1/clawed/export_handout.py +0 -229
- {clawed-2.1.1 → clawed-2.3.1}/.gitignore +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/LICENSE +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/__main__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/_legacy_gateway.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/approvals.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/autonomy.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/context.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/custom_tools.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/drive/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/drive/auth.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/drive/client.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/fake_llm.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/loop.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/curriculum.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/curriculum_kb.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/embeddings.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/identity.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/memory/preferences.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/planner.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/prompt.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/scheduler.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/base.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/configure_profile.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/curriculum_map.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/drive_create_doc.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/drive_create_slides.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/drive_list.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/drive_organize.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/drive_read.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/drive_upload.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/export_document.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/gap_analysis.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/generate_assessment.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/generate_lesson.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/generate_materials.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/generate_unit.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/ingest_materials.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/parent_comm.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/read_heartbeat.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/read_workspace.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/request_approval.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/schedule_task.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/search_lessons.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/search_my_materials.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/search_standards.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/student_insights.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/sub_packet.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/switch_model.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/agent_core/tools/update_soul.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/analytics.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/deps.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/chat.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/export.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/feedback.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/gateway_chat.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/generate.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/ingest.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/lessons.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/school.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/settings.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/routes/tools.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/server.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/static/app.js +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/static/style.css +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/static/widget.js +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/analytics.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/base.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/dashboard.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/generate.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/index.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/lesson.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/profile.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/settings.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/stats.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/api/templates/students.html +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/assessment.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/asset_registry.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/auth/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/auth/google_auth.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/bot_state.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/chat.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/cli.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/cli_chat.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/_helpers.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/bot.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/config.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/config_llm.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/config_profile.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/export.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/generate.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/generate_assessment.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/generate_unit.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/queue.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/schedule_cmd.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/sub.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/commands/workspace_cmd.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/config.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/corpus.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/curriculum_map.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/database.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/demo/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/demo/demo_assessment.json +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/demo/demo_lesson_science_g6.json +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/demo/demo_unit_plan.json +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/differentiation.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/doc_export.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/drive.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/evaluation.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/export_markdown.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/export_pdf.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/export_templates.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/export_theme.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/exporter.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/feedback.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/formats/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/formats/flipchart.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/formats/notebook.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/formats/xbk.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/gateway.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/gateway_response.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/generation.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/export.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/feedback.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/gaps.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/generate.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/ingest.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/misc.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/onboard.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/schedule.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/handlers/standards.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/improver.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/ingestor.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/io.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/lesson.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/materials.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/mcp_server.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/model_router.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/openclaw_plugin.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/parent_comm.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/persona.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/planner.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/504_accommodations.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/assessment.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/curriculum_gaps.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/dbq_assessment.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/differentiation.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/formative_assessment.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/iep_modification.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/lesson_plan.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/pacing_guide.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/parent_note.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/persona_extract.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/quiz.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/rubric.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/sub_packet.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/summative_assessment.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/tiered_assignments.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/unit_plan.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/worksheet.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/prompts/year_map.txt +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/quality.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/reading_report.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/router.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/sanitize.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/scheduler.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/school.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/search.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/art.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/base.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/computer_science.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/ela.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/foreign_language.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/history.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/library.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/math.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/music.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/physical_education.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/science.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/social_studies.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/skills/special_education.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/slide_images.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/standards.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/state.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/state_standards.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/student_bot.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/student_cli.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/student_telegram_bot.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/sub_packet.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/task_queue.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/templates_lib.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/tools.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/transports/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/transports/cli.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/transports/openclaw.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/transports/student_telegram.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/transports/telegram.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/transports/web.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/tui.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/tui_chat.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/clawed/voice.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/eduagent/__init__.py +0 -0
- {clawed-2.1.1 → clawed-2.3.1}/eduagent/_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clawed
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Claw-ED — personal AI teaching agent. Learns your voice, works while you sleep.
|
|
5
5
|
Project-URL: Homepage, https://github.com/SirhanMacx/Claw-ED
|
|
6
6
|
Project-URL: Documentation, https://github.com/SirhanMacx/Claw-ED#readme
|
|
@@ -79,15 +79,17 @@ Built on the OpenClaw agent framework. Open source. MIT license.
|
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
82
|
-
## What's new in v2.
|
|
82
|
+
## What's new in v2.3
|
|
83
83
|
|
|
84
|
-
**
|
|
84
|
+
**Three documents, not one.** Every lesson now generates three professional files in parallel:
|
|
85
85
|
|
|
86
|
-
**
|
|
86
|
+
1. **Student Packet** (4-6 page DOCX workbook) — Fill-in-the-blank guided notes, station sections with full primary source text and analysis questions, graphic organizer tables, exit ticket with sentence starters. This is what students hold in their hands.
|
|
87
|
+
2. **Admin Lesson Plan** (observation-ready DOCX) — Multi-column table with per-section teacher actions (scripted language), student actions, observer look-fors, and differentiation. Anticipated student responses and misconceptions with teacher corrections. Teacher content knowledge appendix.
|
|
88
|
+
3. **Slideshow** (PPTX) — Subject-themed slides with academic images from your own files first, then Library of Congress and Wikimedia. Vocabulary, source quotes, and section dividers on dedicated slides.
|
|
87
89
|
|
|
88
|
-
**Your files
|
|
90
|
+
**Your files are first-class.** Ingestion extracts images from your PPTX/DOCX files, catalogues YouTube links, and classifies every file by type. The agent tells you what you already have before generating.
|
|
89
91
|
|
|
90
|
-
**
|
|
92
|
+
**Pedagogical fingerprint.** "Teacher voice" means how you teach, not just how you sound. The persona captures source types, activity patterns, scaffolding moves, Do Now style, exit ticket format, and signature moves.
|
|
91
93
|
|
|
92
94
|
---
|
|
93
95
|
|
|
@@ -13,15 +13,17 @@ Built on the OpenClaw agent framework. Open source. MIT license.
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
## What's new in v2.
|
|
16
|
+
## What's new in v2.3
|
|
17
17
|
|
|
18
|
-
**
|
|
18
|
+
**Three documents, not one.** Every lesson now generates three professional files in parallel:
|
|
19
19
|
|
|
20
|
-
**
|
|
20
|
+
1. **Student Packet** (4-6 page DOCX workbook) — Fill-in-the-blank guided notes, station sections with full primary source text and analysis questions, graphic organizer tables, exit ticket with sentence starters. This is what students hold in their hands.
|
|
21
|
+
2. **Admin Lesson Plan** (observation-ready DOCX) — Multi-column table with per-section teacher actions (scripted language), student actions, observer look-fors, and differentiation. Anticipated student responses and misconceptions with teacher corrections. Teacher content knowledge appendix.
|
|
22
|
+
3. **Slideshow** (PPTX) — Subject-themed slides with academic images from your own files first, then Library of Congress and Wikimedia. Vocabulary, source quotes, and section dividers on dedicated slides.
|
|
21
23
|
|
|
22
|
-
**Your files
|
|
24
|
+
**Your files are first-class.** Ingestion extracts images from your PPTX/DOCX files, catalogues YouTube links, and classifies every file by type. The agent tells you what you already have before generating.
|
|
23
25
|
|
|
24
|
-
**
|
|
26
|
+
**Pedagogical fingerprint.** "Teacher voice" means how you teach, not just how you sound. The persona captures source types, activity patterns, scaffolding moves, Do Now style, exit ticket format, and signature moves.
|
|
25
27
|
|
|
26
28
|
---
|
|
27
29
|
|
|
@@ -17,7 +17,7 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
17
17
|
except Exception:
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
|
-
__version__ = "2.
|
|
20
|
+
__version__ = "2.3.1"
|
|
21
21
|
__author__ = "Jon Maccarello & Claw-ED contributors"
|
|
22
22
|
__description__ = "Personal AI teaching agent. Learns your voice, works while you sleep."
|
|
23
23
|
|
|
@@ -361,7 +361,18 @@ class Gateway:
|
|
|
361
361
|
soul_context=soul_context,
|
|
362
362
|
)
|
|
363
363
|
|
|
364
|
-
# 2c.
|
|
364
|
+
# 2c. Cross-session context threading — greet with continuity
|
|
365
|
+
last_session = memory_ctx.get("last_session_summary", "")
|
|
366
|
+
if last_session and not session_history:
|
|
367
|
+
system += (
|
|
368
|
+
"\n\n=== Last Session Context ===\n"
|
|
369
|
+
f"The teacher's last interaction was about: {last_session}\n"
|
|
370
|
+
"If this is a new conversation, greet them with continuity — e.g. "
|
|
371
|
+
'"Last time we worked on [topic]. Want to continue or start something new?"\n'
|
|
372
|
+
"=== End Last Session Context ===\n"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# 2d. Enhance prompt for multi-step planning requests
|
|
365
376
|
from clawed.agent_core.planner import build_planning_prompt, is_planning_request
|
|
366
377
|
|
|
367
378
|
if is_planning_request(message):
|
|
@@ -415,6 +426,14 @@ class Gateway:
|
|
|
415
426
|
except Exception:
|
|
416
427
|
pass
|
|
417
428
|
|
|
429
|
+
# 8. Maybe compress old episodes (runs every COMPRESSION_THRESHOLD episodes)
|
|
430
|
+
try:
|
|
431
|
+
from clawed.memory_engine import maybe_compress_episodes
|
|
432
|
+
|
|
433
|
+
maybe_compress_episodes(teacher_id)
|
|
434
|
+
except Exception:
|
|
435
|
+
pass
|
|
436
|
+
|
|
418
437
|
return result
|
|
419
438
|
|
|
420
439
|
# ------------------------------------------------------------------
|
|
@@ -96,3 +96,53 @@ class EpisodicMemory:
|
|
|
96
96
|
|
|
97
97
|
scored.sort(key=lambda x: x["similarity"], reverse=True)
|
|
98
98
|
return scored[:top_k]
|
|
99
|
+
|
|
100
|
+
def get_latest_episode(self, teacher_id: str) -> dict[str, Any] | None:
|
|
101
|
+
"""Return the most recent episode for a teacher, or None."""
|
|
102
|
+
with sqlite3.connect(self._db_path) as conn:
|
|
103
|
+
conn.row_factory = sqlite3.Row
|
|
104
|
+
row = conn.execute(
|
|
105
|
+
"SELECT text, metadata, created_at FROM episodes "
|
|
106
|
+
"WHERE teacher_id = ? ORDER BY created_at DESC LIMIT 1",
|
|
107
|
+
(teacher_id,),
|
|
108
|
+
).fetchone()
|
|
109
|
+
if not row:
|
|
110
|
+
return None
|
|
111
|
+
return {
|
|
112
|
+
"text": row["text"],
|
|
113
|
+
"metadata": json.loads(row["metadata"]),
|
|
114
|
+
"created_at": row["created_at"],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def count_episodes(self, teacher_id: str) -> int:
|
|
118
|
+
"""Return the total number of episodes stored for a teacher."""
|
|
119
|
+
with sqlite3.connect(self._db_path) as conn:
|
|
120
|
+
row = conn.execute(
|
|
121
|
+
"SELECT COUNT(*) FROM episodes WHERE teacher_id = ?",
|
|
122
|
+
(teacher_id,),
|
|
123
|
+
).fetchone()
|
|
124
|
+
return row[0] if row else 0
|
|
125
|
+
|
|
126
|
+
def get_all_episodes(
|
|
127
|
+
self,
|
|
128
|
+
teacher_id: str,
|
|
129
|
+
limit: int = 500,
|
|
130
|
+
offset: int = 0,
|
|
131
|
+
) -> list[dict[str, Any]]:
|
|
132
|
+
"""Return episodes in chronological order (oldest first)."""
|
|
133
|
+
with sqlite3.connect(self._db_path) as conn:
|
|
134
|
+
conn.row_factory = sqlite3.Row
|
|
135
|
+
rows = conn.execute(
|
|
136
|
+
"SELECT text, metadata, created_at FROM episodes "
|
|
137
|
+
"WHERE teacher_id = ? ORDER BY created_at ASC "
|
|
138
|
+
"LIMIT ? OFFSET ?",
|
|
139
|
+
(teacher_id, limit, offset),
|
|
140
|
+
).fetchall()
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
"text": row["text"],
|
|
144
|
+
"metadata": json.loads(row["metadata"]),
|
|
145
|
+
"created_at": row["created_at"],
|
|
146
|
+
}
|
|
147
|
+
for row in rows
|
|
148
|
+
]
|
|
@@ -96,6 +96,28 @@ def load_memory_context(teacher_id: str, current_message: str) -> dict[str, Any]
|
|
|
96
96
|
except Exception as e:
|
|
97
97
|
logger.debug("Curriculum KB search failed: %s", e)
|
|
98
98
|
|
|
99
|
+
# Layer 6: Last session summary (cross-session context threading)
|
|
100
|
+
last_session_summary = ""
|
|
101
|
+
try:
|
|
102
|
+
from clawed.agent_core.memory.episodes import EpisodicMemory
|
|
103
|
+
|
|
104
|
+
mem = EpisodicMemory()
|
|
105
|
+
latest = mem.get_latest_episode(teacher_id)
|
|
106
|
+
if latest:
|
|
107
|
+
text = latest["text"]
|
|
108
|
+
# Extract teacher's message (first line is usually "Teacher: <msg>")
|
|
109
|
+
first_line = text.split("\n")[0].strip()
|
|
110
|
+
if first_line.startswith("Teacher: "):
|
|
111
|
+
topic = first_line[len("Teacher: "):]
|
|
112
|
+
else:
|
|
113
|
+
topic = first_line
|
|
114
|
+
# Truncate to a concise summary
|
|
115
|
+
if len(topic) > 150:
|
|
116
|
+
topic = topic[:147] + "..."
|
|
117
|
+
last_session_summary = topic
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.debug("Last session summary load failed: %s", e)
|
|
120
|
+
|
|
99
121
|
return {
|
|
100
122
|
"identity_summary": identity_summary,
|
|
101
123
|
"curriculum_summary": curriculum_summary,
|
|
@@ -104,4 +126,5 @@ def load_memory_context(teacher_id: str, current_message: str) -> dict[str, Any]
|
|
|
104
126
|
"preferences_summary": preferences_summary,
|
|
105
127
|
"autonomy_summary": autonomy_summary,
|
|
106
128
|
"curriculum_kb_context": curriculum_kb_context,
|
|
129
|
+
"last_session_summary": last_session_summary,
|
|
107
130
|
}
|
|
@@ -232,6 +232,44 @@ class GenerateLessonBundleTool:
|
|
|
232
232
|
except Exception:
|
|
233
233
|
pass # Review is best-effort, don't block on failure
|
|
234
234
|
|
|
235
|
+
# ── Generate student packet + admin plan in parallel ──────────
|
|
236
|
+
import asyncio
|
|
237
|
+
|
|
238
|
+
from clawed.llm import LLMClient
|
|
239
|
+
|
|
240
|
+
llm_client = LLMClient(config)
|
|
241
|
+
persona_ctx = persona.to_prompt_context()
|
|
242
|
+
student_packet = None
|
|
243
|
+
admin_plan = None
|
|
244
|
+
|
|
245
|
+
async def _gen_packet():
|
|
246
|
+
return await llm_client.generate_student_packet(
|
|
247
|
+
lesson_json_str, persona_context=persona_ctx,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
async def _gen_admin():
|
|
251
|
+
return await llm_client.generate_admin_plan(
|
|
252
|
+
lesson_json_str, persona_context=persona_ctx,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
results = await asyncio.gather(
|
|
257
|
+
_gen_packet(), _gen_admin(), return_exceptions=True,
|
|
258
|
+
)
|
|
259
|
+
if not isinstance(results[0], Exception):
|
|
260
|
+
student_packet = results[0]
|
|
261
|
+
logger.info("Student packet generated: %d stations, %d guided notes",
|
|
262
|
+
len(student_packet.stations), len(student_packet.guided_notes))
|
|
263
|
+
else:
|
|
264
|
+
logger.warning("Student packet generation failed: %s", results[0])
|
|
265
|
+
if not isinstance(results[1], Exception):
|
|
266
|
+
admin_plan = results[1]
|
|
267
|
+
logger.info("Admin lesson plan generated: %d sections", len(admin_plan.sections))
|
|
268
|
+
else:
|
|
269
|
+
logger.warning("Admin plan generation failed: %s", results[1])
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.warning("Parallel generation failed: %s", e)
|
|
272
|
+
|
|
235
273
|
# ── Export all three files ────────────────────────────────────
|
|
236
274
|
output_dir = Path("clawed_output").resolve()
|
|
237
275
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -240,61 +278,39 @@ class GenerateLessonBundleTool:
|
|
|
240
278
|
side_effects: list[str] = []
|
|
241
279
|
errors: list[str] = []
|
|
242
280
|
|
|
243
|
-
# 1.
|
|
281
|
+
# 1. Admin lesson plan DOCX (or fallback to basic lesson plan)
|
|
244
282
|
try:
|
|
245
283
|
from clawed.export_docx import export_lesson_docx
|
|
246
284
|
|
|
247
|
-
docx_path = export_lesson_docx(
|
|
285
|
+
docx_path = export_lesson_docx(
|
|
286
|
+
lesson, persona, output_dir, admin_plan=admin_plan,
|
|
287
|
+
)
|
|
248
288
|
generated_files.append(docx_path)
|
|
249
|
-
|
|
289
|
+
label = "Admin lesson plan" if admin_plan else "Lesson plan"
|
|
290
|
+
side_effects.append(f"{label} DOCX: {docx_path.name}")
|
|
250
291
|
except Exception as e:
|
|
251
292
|
logger.error("Lesson DOCX export failed: %s", e)
|
|
252
293
|
errors.append(f"Lesson plan DOCX failed: {e}")
|
|
253
294
|
|
|
254
|
-
# 2.
|
|
295
|
+
# 2. Student packet DOCX (structured) or fallback to old handout
|
|
255
296
|
try:
|
|
256
|
-
import
|
|
297
|
+
from clawed.export_handout import export_student_packet_docx
|
|
257
298
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
subject=subject,
|
|
265
|
-
grade=grade,
|
|
266
|
-
)
|
|
267
|
-
# Parse handout JSON
|
|
268
|
-
handout_cleaned = handout_raw.strip()
|
|
269
|
-
if handout_cleaned.startswith("```"):
|
|
270
|
-
lines = handout_cleaned.split("\n")
|
|
271
|
-
lines = lines[1:]
|
|
272
|
-
if lines and lines[-1].strip() == "```":
|
|
273
|
-
lines = lines[:-1]
|
|
274
|
-
handout_cleaned = "\n".join(lines)
|
|
275
|
-
try:
|
|
276
|
-
handout_data = json.loads(handout_cleaned)
|
|
277
|
-
except json.JSONDecodeError:
|
|
278
|
-
handout_data = json_repair.loads(handout_cleaned)
|
|
279
|
-
|
|
280
|
-
from clawed.export_handout import export_handout_docx
|
|
281
|
-
|
|
282
|
-
handout_path = export_handout_docx(handout_data, subject=subject)
|
|
283
|
-
if handout_path:
|
|
284
|
-
generated_files.append(Path(handout_path))
|
|
285
|
-
side_effects.append(f"Student handout DOCX: {Path(handout_path).name}")
|
|
286
|
-
except Exception as handout_err:
|
|
287
|
-
logger.warning("LLM handout generation failed, falling back: %s", handout_err)
|
|
288
|
-
# Fallback to regex-based handout
|
|
289
|
-
try:
|
|
299
|
+
if student_packet:
|
|
300
|
+
packet_path = export_student_packet_docx(
|
|
301
|
+
student_packet, subject=subject, output_dir=output_dir,
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
# Fallback: build minimal packet from lesson data
|
|
290
305
|
from clawed.export_docx import export_student_handout
|
|
306
|
+
packet_path = export_student_handout(lesson, persona, output_dir)
|
|
291
307
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
308
|
+
if packet_path:
|
|
309
|
+
generated_files.append(Path(packet_path))
|
|
310
|
+
side_effects.append(f"Student packet DOCX: {Path(packet_path).name}")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.warning("Student packet export failed: %s", e)
|
|
313
|
+
errors.append(f"Student packet failed: {e}")
|
|
298
314
|
|
|
299
315
|
# 3. Slideshow PPTX
|
|
300
316
|
try:
|
|
@@ -170,11 +170,20 @@ def export_lesson_docx(
|
|
|
170
170
|
persona: "TeacherPersona",
|
|
171
171
|
output_dir: Path | None = None,
|
|
172
172
|
agent_name: str = "Claw-ED",
|
|
173
|
+
admin_plan: Any = None,
|
|
173
174
|
) -> Path:
|
|
174
175
|
"""Generate a Word document from a lesson plan with embedded academic images.
|
|
175
176
|
|
|
177
|
+
If an AdminLessonPlan is provided, generates an observation-ready document
|
|
178
|
+
with per-section teacher/student actions, observer look-fors, anticipated
|
|
179
|
+
responses, and teacher content knowledge.
|
|
180
|
+
|
|
176
181
|
Returns the path to the saved .docx file.
|
|
177
182
|
"""
|
|
183
|
+
# If we have an admin plan, use the enriched export
|
|
184
|
+
if admin_plan is not None:
|
|
185
|
+
return _export_admin_lesson_docx(lesson, persona, admin_plan, output_dir, agent_name)
|
|
186
|
+
|
|
178
187
|
from docx import Document
|
|
179
188
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
180
189
|
from docx.shared import Pt, RGBColor
|
|
@@ -813,3 +822,203 @@ def _remove_table_borders(table: Any) -> None:
|
|
|
813
822
|
)
|
|
814
823
|
borders.append(element)
|
|
815
824
|
tblPr.append(borders)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
# ── Admin lesson plan export ──────────────────────────────────────────
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def _export_admin_lesson_docx(
|
|
831
|
+
lesson: "DailyLesson",
|
|
832
|
+
persona: "TeacherPersona",
|
|
833
|
+
admin_plan: Any,
|
|
834
|
+
output_dir: Path | None = None,
|
|
835
|
+
agent_name: str = "Claw-ED",
|
|
836
|
+
) -> Path:
|
|
837
|
+
"""Generate an observation-ready lesson plan DOCX with multi-column tables.
|
|
838
|
+
|
|
839
|
+
This produces the format administrators expect: per-section teacher actions,
|
|
840
|
+
student actions, observer look-fors, and differentiation in a table layout,
|
|
841
|
+
plus anticipated responses and teacher content knowledge sections.
|
|
842
|
+
"""
|
|
843
|
+
from docx import Document
|
|
844
|
+
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
845
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
846
|
+
from docx.oxml.ns import qn
|
|
847
|
+
from docx.shared import Inches, Pt, RGBColor
|
|
848
|
+
|
|
849
|
+
from clawed.sanitize import sanitize_text
|
|
850
|
+
|
|
851
|
+
doc = Document()
|
|
852
|
+
|
|
853
|
+
# Page setup
|
|
854
|
+
for section in doc.sections:
|
|
855
|
+
section.top_margin = Inches(0.6)
|
|
856
|
+
section.bottom_margin = Inches(0.5)
|
|
857
|
+
section.left_margin = Inches(0.75)
|
|
858
|
+
section.right_margin = Inches(0.75)
|
|
859
|
+
|
|
860
|
+
style = doc.styles["Normal"]
|
|
861
|
+
style.font.name = "Calibri"
|
|
862
|
+
style.font.size = Pt(10)
|
|
863
|
+
|
|
864
|
+
theme = get_color_theme(persona.subject_area or "")
|
|
865
|
+
primary_hex = theme["primary"]
|
|
866
|
+
primary_rgb = RGBColor(int(primary_hex[:2], 16), int(primary_hex[2:4], 16), int(primary_hex[4:6], 16))
|
|
867
|
+
|
|
868
|
+
def _shade(cell, hex_color):
|
|
869
|
+
tc = cell._tc
|
|
870
|
+
tcPr = tc.get_or_add_tcPr()
|
|
871
|
+
shading = tcPr.makeelement(
|
|
872
|
+
qn("w:shd"), {qn("w:val"): "clear", qn("w:color"): "auto", qn("w:fill"): hex_color},
|
|
873
|
+
)
|
|
874
|
+
tcPr.append(shading)
|
|
875
|
+
|
|
876
|
+
# ── I. Header ─────────────────────────────────────────────────────
|
|
877
|
+
title_para = doc.add_paragraph()
|
|
878
|
+
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
879
|
+
r = title_para.add_run("LESSON PLAN")
|
|
880
|
+
r.bold = True
|
|
881
|
+
r.font.size = Pt(18)
|
|
882
|
+
r.font.color.rgb = primary_rgb
|
|
883
|
+
|
|
884
|
+
# Overview table
|
|
885
|
+
teacher_name = getattr(admin_plan, "teacher_name", "") or persona.name or "Teacher"
|
|
886
|
+
overview_data = [
|
|
887
|
+
("Teacher", sanitize_text(teacher_name)),
|
|
888
|
+
("Course", sanitize_text(getattr(admin_plan, "course", persona.subject_area or ""))),
|
|
889
|
+
("Date", sanitize_text(getattr(admin_plan, "date", ""))),
|
|
890
|
+
("Topic", sanitize_text(getattr(admin_plan, "topic", lesson.title))),
|
|
891
|
+
("Grade Level", sanitize_text(getattr(admin_plan, "grade_level", ""))),
|
|
892
|
+
("Duration", f"{getattr(admin_plan, 'duration_minutes', 40)} Minutes"),
|
|
893
|
+
("Aim", sanitize_text(getattr(admin_plan, "aim", lesson.objective))),
|
|
894
|
+
]
|
|
895
|
+
standards = getattr(admin_plan, "standards", []) or lesson.standards
|
|
896
|
+
if standards:
|
|
897
|
+
overview_data.append(("Standards", sanitize_text(", ".join(standards[:3]))))
|
|
898
|
+
materials = getattr(admin_plan, "materials", []) or lesson.materials_needed
|
|
899
|
+
if materials:
|
|
900
|
+
overview_data.append(("Materials", sanitize_text(", ".join(materials[:6]))))
|
|
901
|
+
|
|
902
|
+
overview_table = doc.add_table(rows=len(overview_data), cols=2)
|
|
903
|
+
overview_table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
904
|
+
for i, (label, value) in enumerate(overview_data):
|
|
905
|
+
label_cell = overview_table.rows[i].cells[0]
|
|
906
|
+
value_cell = overview_table.rows[i].cells[1]
|
|
907
|
+
label_cell.text = ""
|
|
908
|
+
lp = label_cell.paragraphs[0]
|
|
909
|
+
lr = lp.add_run(label)
|
|
910
|
+
lr.bold = True
|
|
911
|
+
lr.font.size = Pt(10)
|
|
912
|
+
_shade(label_cell, theme.get("bg_light", "F5F5F5").lstrip("#"))
|
|
913
|
+
value_cell.text = value
|
|
914
|
+
for p in value_cell.paragraphs:
|
|
915
|
+
for run in p.runs:
|
|
916
|
+
run.font.size = Pt(10)
|
|
917
|
+
_set_table_borders(overview_table)
|
|
918
|
+
|
|
919
|
+
# ── II. Section-by-Section Breakdown ──────────────────────────────
|
|
920
|
+
sections = getattr(admin_plan, "sections", [])
|
|
921
|
+
if sections:
|
|
922
|
+
doc.add_paragraph("")
|
|
923
|
+
h = doc.add_heading("Lesson Plan & Pacing", level=2)
|
|
924
|
+
for run in h.runs:
|
|
925
|
+
run.font.color.rgb = primary_rgb
|
|
926
|
+
|
|
927
|
+
# 5-column table: Section | Teacher Actions | Student Actions | Look-Fors | Differentiation
|
|
928
|
+
col_headers = [
|
|
929
|
+
"Section & Timing", "Teacher Actions", "Student Actions",
|
|
930
|
+
"Observer Look-Fors", "Differentiation",
|
|
931
|
+
]
|
|
932
|
+
section_table = doc.add_table(rows=1 + len(sections), cols=5)
|
|
933
|
+
section_table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
934
|
+
|
|
935
|
+
# Header row
|
|
936
|
+
for ci, header in enumerate(col_headers):
|
|
937
|
+
cell = section_table.rows[0].cells[ci]
|
|
938
|
+
cell.text = ""
|
|
939
|
+
p = cell.paragraphs[0]
|
|
940
|
+
r = p.add_run(header)
|
|
941
|
+
r.bold = True
|
|
942
|
+
r.font.size = Pt(9)
|
|
943
|
+
r.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
|
|
944
|
+
_shade(cell, primary_hex)
|
|
945
|
+
|
|
946
|
+
# Data rows
|
|
947
|
+
for ri, sec in enumerate(sections):
|
|
948
|
+
row = section_table.rows[ri + 1]
|
|
949
|
+
if hasattr(sec, "section_name"):
|
|
950
|
+
fields = [
|
|
951
|
+
f"{sec.section_name}\n({sec.timing_minutes} min)",
|
|
952
|
+
sanitize_text(sec.teacher_actions),
|
|
953
|
+
sanitize_text(sec.student_actions),
|
|
954
|
+
sanitize_text(sec.observer_look_fors),
|
|
955
|
+
sanitize_text(sec.differentiation),
|
|
956
|
+
]
|
|
957
|
+
else:
|
|
958
|
+
fields = [str(sec)] + [""] * 4
|
|
959
|
+
for ci, text in enumerate(fields):
|
|
960
|
+
cell = row.cells[ci]
|
|
961
|
+
cell.text = text
|
|
962
|
+
for p in cell.paragraphs:
|
|
963
|
+
for run in p.runs:
|
|
964
|
+
run.font.size = Pt(9)
|
|
965
|
+
# Alternate row shading
|
|
966
|
+
if ri % 2 == 0:
|
|
967
|
+
for cell in row.cells:
|
|
968
|
+
_shade(cell, "F9F9F9")
|
|
969
|
+
|
|
970
|
+
_set_table_borders(section_table)
|
|
971
|
+
|
|
972
|
+
# ── III. Anticipated Responses & Misconceptions ───────────────────
|
|
973
|
+
responses = getattr(admin_plan, "anticipated_responses", [])
|
|
974
|
+
if responses:
|
|
975
|
+
doc.add_paragraph("")
|
|
976
|
+
h = doc.add_heading("Anticipated Student Responses & Misconceptions", level=2)
|
|
977
|
+
for run in h.runs:
|
|
978
|
+
run.font.color.rgb = primary_rgb
|
|
979
|
+
|
|
980
|
+
for resp in responses:
|
|
981
|
+
is_mis = getattr(resp, "is_misconception", False) if hasattr(resp, "is_misconception") else False
|
|
982
|
+
text = sanitize_text(getattr(resp, "response_or_misconception", str(resp)))
|
|
983
|
+
correction = sanitize_text(getattr(resp, "teacher_correction", ""))
|
|
984
|
+
|
|
985
|
+
p = doc.add_paragraph()
|
|
986
|
+
prefix = "MISCONCEPTION: " if is_mis else "EXPECTED: "
|
|
987
|
+
pr = p.add_run(prefix)
|
|
988
|
+
pr.bold = True
|
|
989
|
+
pr.font.size = Pt(10)
|
|
990
|
+
pr.font.color.rgb = RGBColor(0xCC, 0x33, 0x33) if is_mis else RGBColor(0x33, 0x99, 0x33)
|
|
991
|
+
tr = p.add_run(f'"{text}"')
|
|
992
|
+
tr.italic = True
|
|
993
|
+
tr.font.size = Pt(10)
|
|
994
|
+
|
|
995
|
+
if correction:
|
|
996
|
+
cp = doc.add_paragraph()
|
|
997
|
+
cp.paragraph_format.left_indent = Inches(0.3)
|
|
998
|
+
cr = cp.add_run(f"Redirect: {correction}")
|
|
999
|
+
cr.font.size = Pt(9)
|
|
1000
|
+
cr.font.color.rgb = RGBColor(0x44, 0x44, 0x44)
|
|
1001
|
+
|
|
1002
|
+
# ── IV. Teacher Content Knowledge ─────────────────────────────────
|
|
1003
|
+
tck = sanitize_text(getattr(admin_plan, "teacher_content_knowledge", ""))
|
|
1004
|
+
if tck:
|
|
1005
|
+
doc.add_paragraph("")
|
|
1006
|
+
h = doc.add_heading("Teacher Content Knowledge", level=2)
|
|
1007
|
+
for run in h.runs:
|
|
1008
|
+
run.font.color.rgb = primary_rgb
|
|
1009
|
+
p = doc.add_paragraph(tck)
|
|
1010
|
+
p.paragraph_format.space_after = Pt(6)
|
|
1011
|
+
for run in p.runs:
|
|
1012
|
+
run.font.size = Pt(10)
|
|
1013
|
+
|
|
1014
|
+
# ── Footer ────────────────────────────────────────────────────────
|
|
1015
|
+
doc.add_paragraph("")
|
|
1016
|
+
gen_footer = doc.add_paragraph()
|
|
1017
|
+
gen_footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
1018
|
+
fr = gen_footer.add_run(f"Generated by {agent_name}")
|
|
1019
|
+
fr.font.size = Pt(8)
|
|
1020
|
+
fr.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
|
|
1021
|
+
|
|
1022
|
+
out = _resolve_output(output_dir, lesson, ".docx")
|
|
1023
|
+
doc.save(str(out))
|
|
1024
|
+
return out
|