#!/usr/bin/env python3
"""
Ingest 2nd Grade Math curriculum into the brain system.
Parses Curriculum_32_Weeks.md (table-based format), creates:
- 1 umbrella entity (math) β updates existing if present
- 5 phase entities
- 32 topic entities with key skills
- Edges: part_of, precedes
Usage:
python3 ~/wiki/ingest_math_knowledge.py # Full ingest
python3 ~/wiki/ingest_math_knowledge.py --dry # Preview only
python3 ~/wiki/ingest_math_knowledge.py --query # Run test queries
"""
import sys
import os
import re
from pathlib import Path
from datetime import date
sys.path.insert(0, str(Path.home() / '.hermes' / 'brain'))
import brain
brain._db_connection = None
brain.init_db()
TODAY = date.today().isoformat()
PLAN_PATH = Path.home() / 'Home_School' / '2nd_Grade' / 'Math' / 'Curriculum_32_Weeks.md'
def parse_plan():
"""Parse the Curriculum_32_Weeks.md (table-based format)."""
if not PLAN_PATH.exists():
print(f"[warn] Plan not found: {PLAN_PATH}")
return {}
content = PLAN_PATH.read_text()
# Extract phase blocks β phases have descriptive names after the colon
phase_blocks = re.findall(
r'^## Phase (\d+): (.+?)\s*\n\n(.+?)(?=^## |\Z)',
content, re.MULTILINE | re.DOTALL
)
topics = {}
phase_teaching_notes = {}
for phase_num_str, phase_name, body in phase_blocks:
phase_num = int(phase_num_str)
phase_name = phase_name.strip()
# Extract teaching notes (if present)
notes_match = re.search(r'\*\*Teaching Notes:\*\*\s*\n((?:(?!^\-).)*?)', body, re.MULTILINE | re.DOTALL)
if notes_match:
phase_teaching_notes[phase_num] = notes_match.group(1).strip()
# Parse table rows β handle both formats:
# | Week | Topic | Key Skills |
# | Week | Topic | Key Skills | Visual Elements |
table_rows = re.findall(r'^\|\s*(\d+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*(?:\|.*)?\s*\|?\s*$', body, re.MULTILINE)
for week_str, topic, skills in table_rows:
week = int(week_str)
topic = topic.strip()
skills = skills.strip()
slug = re.sub(r'[^\w\s-]', '', topic.lower().strip())
slug = re.sub(r'[\s_]+', '-', slug).strip('-')
topics[week] = {
"week": week,
"topic": topic,
"slug": slug,
"phase": phase_num,
"phase_name": phase_name,
"key_skills": skills,
}
# Also extract the weekly lesson structure
lesson_structure = ""
ls_match = re.search(r'## Weekly Lesson Structure\s*\n\n(.+?)(?=^## |\Z)', content, re.MULTILINE | re.DOTALL)
if ls_match:
lesson_structure = ls_match.group(1).strip()
# Extract standards alignment
standards = ""
std_match = re.search(r'## Standards Alignment.*?\n\n(.+?)(?=^## |\Z)', content, re.MULTILINE | re.DOTALL)
if std_match:
standards = std_match.group(1).strip()
# Extract materials
materials = ""
mat_match = re.search(r'## Materials & Tools\s*\n\n(.+?)(?=^## |\Z)', content, re.MULTILINE | re.DOTALL)
if mat_match:
materials = mat_match.group(1).strip()
# Extract pacing guidance
pacing = ""
pace_match = re.search(r'## Pacing Guidance\s*\n\n(.+?)(?=^## |\Z)', content, re.MULTILINE | re.DOTALL)
if pace_match:
pacing = pace_match.group(1).strip()
return topics, {
"phase_teaching_notes": phase_teaching_notes,
"lesson_structure": lesson_structure,
"standards": standards,
"materials": materials,
"pacing": pacing,
}
def create_entity(content, category, entity_type="memory", pinned=False, strength="moderate", metadata=None):
return brain.create_entity(
content=content,
entity_type=entity_type,
category=category,
strength=strength,
pinned=pinned,
metadata=metadata or {},
log=True,
)
def ingest_umbrella(extras):
"""Create/update math umbrella entity."""
content = f"""## Math Curriculum β 2nd Grade
Complete 32-week, 5-phase math curriculum for second grade. Each week covers
a focused topic with daily lessons building toward mastery. Designed for
homeschool use with hands-on practice, word problems, and visual activities.
**Structure:** 5 phases, 32 weeks, MonβFri lessons per week.
**Format:** 10 problems per lesson, horizontal math format.
### Phases
- **Phase 1:** Number Sense & Foundations (Weeks 1β6)
- **Phase 2:** Addition & Subtraction to 100 (Weeks 7β14)
- **Phase 3:** Measurement, Time & Money (Weeks 15β20)
- **Phase 4:** Geometry & Fractions (Weeks 21β26)
- **Phase 5:** Multiplication, Division & Patterns (Weeks 27β32)
### Standards Alignment (Common Core 2nd Grade)
{extras['standards']}
### Weekly Lesson Structure
{extras['lesson_structure']}
### Materials & Tools
{extras['materials']}
### Pacing Guidance
{extras['pacing']}
**Ingested:** {TODAY}
**Source:** Home_School/2nd_Grade/Math/Curriculum_32_Weeks.md
"""
eid = create_entity(
content=content,
category="curriculum/math",
entity_type="curriculum_umbrella",
pinned=True,
strength="strong",
metadata={"source": "math_curriculum_ingest", "date": TODAY},
)
print(f" β
Umbrella: curriculum/math β {eid}")
return eid
def ingest_phases(topics, umbrella_id, extras):
phase_weeks = {}
for week, data in sorted(topics.items()):
pn = data["phase"]
if pn not in phase_weeks:
phase_weeks[pn] = {"name": data["phase_name"], "weeks": []}
phase_weeks[pn]["weeks"].append(week)
phase_ids = {}
for pn, pdata in sorted(phase_weeks.items()):
week_range = f"Weeks {min(pdata['weeks'])}β{max(pdata['weeks'])}"
topics_list = ""
for w in sorted(pdata['weeks']):
topics_list += f"- Week {w}: {topics[w]['topic']} β {topics[w]['key_skills']}\n"
teaching_notes = extras['phase_teaching_notes'].get(pn, "")
notes_section = f"\n### Teaching Notes\n{teaching_notes}\n" if teaching_notes else ""
content = f"""## Math Phase {pn}: {pdata['name']}
{week_range} β {len(pdata['weeks'])} weeks of 2nd grade math curriculum.
### Topics
{topics_list}{notes_section}
"""
category = f"curriculum/math/phase-{pn}"
eid = create_entity(
content=content,
category=category,
entity_type="curriculum_phase",
pinned=pn <= 2,
strength="strong" if pn <= 2 else "moderate",
metadata={"phase": pn, "weeks": sorted(pdata['weeks']), "source": "math_curriculum_ingest"},
)
phase_ids[pn] = eid
print(f" β
Phase {pn}: {pdata['name']} β {eid[:12]}...")
brain.add_edge(eid, "part_of", umbrella_id)
return phase_ids
def ingest_topics(topics, phase_ids):
topic_ids = {}
for week, data in sorted(topics.items()):
# Pin checkpoint weeks and foundational topics
pinned = week <= 6 or week in [14, 20, 26, 32]
content = f"""## Math: Week {week} β {data['topic']}
**Phase:** {data['phase_name']}
**Key Skills:** {data['key_skills']}
"""
category = f"curriculum/math/topic/week-{week}"
eid = create_entity(
content=content,
category=category,
entity_type="curriculum_topic",
pinned=pinned,
strength="moderate",
metadata={
"week": week,
"phase": data["phase"],
"topic": data["topic"],
"key_skills": data["key_skills"],
"source": "math_curriculum_ingest",
},
)
topic_ids[week] = eid
print(f" β
Wk {week}: {data['topic']} β {eid[:12]}...")
return topic_ids
def create_edges(topics, phase_ids, topic_ids):
edge_count = 0
for week, data in topics.items():
phase = data["phase"]
if phase in phase_ids and week in topic_ids:
if brain.add_edge(topic_ids[week], "part_of", phase_ids[phase]):
edge_count += 1
for week in range(1, 32):
if week in topic_ids and (week + 1) in topic_ids:
if brain.add_edge(topic_ids[week], "precedes", topic_ids[week + 1]):
edge_count += 1
for p in range(1, 5):
if p in phase_ids and (p + 1) in phase_ids:
if brain.add_edge(phase_ids[p], "precedes", phase_ids[p + 1]):
edge_count += 1
print(f" β
{edge_count} edges created")
return edge_count
def run_test_queries():
queries = [
("math curriculum phases", "phases"),
("place value tens ones", "place value"),
("addition subtraction regrouping", "regrouping"),
("measurement time money", "measurement"),
("geometry shapes fractions", "geometry"),
("multiplication division equal groups", "multiplication"),
("telling time clock", "time"),
("area perimeter square units", "area"),
("fraction equal parts", "fractions"),
("addition facts to 20 fluency", "fluency"),
]
print("\n=== TEST QUERIES ===")
passed = 0
for q, _ in queries:
result = brain.search(q, limit=3)
hits = len(result) if result else 0
status = "β" if hits > 0 else "β"
print(f" {status} '{q}' β {hits} hits")
if hits > 0:
passed += 1
print(f"\n Results: {passed}/{len(queries)} queries returned hits")
return passed >= len(queries) * 0.8
def main():
mode = sys.argv[1] if len(sys.argv) > 1 else "--ingest"
if mode == "--query":
run_test_queries()
return
print("=== Math Curriculum Brain Ingestion ===")
print("\n[1] Parsing Curriculum_32_Weeks.md...")
topics, extras = parse_plan()
print(f" Found {len(topics)} topics across 5 phases")
if extras['phase_teaching_notes']:
print(f" Teaching notes for {len(extras['phase_teaching_notes'])} phases")
if extras['standards']:
print(f" Standards alignment: {len(extras['standards'])} chars")
if not topics:
print("[error] No topics found")
return
if mode == "--dry":
print("\n--- DRY RUN ---")
phases_seen = set()
for week, data in sorted(topics.items()):
if data['phase'] not in phases_seen:
print(f"\n Phase {data['phase']}: {data['phase_name']}")
phases_seen.add(data['phase'])
print(f" Wk {week}: {data['topic']}")
print(f"\n Total: {len(topics)} topics")
return
print("\n[2] Creating umbrella entity...")
umbrella_id = ingest_umbrella(extras)
print("\n[3] Ingesting phase entities...")
phase_ids = ingest_phases(topics, umbrella_id, extras)
print("\n[4] Ingesting topic entities...")
topic_ids = ingest_topics(topics, phase_ids)
print("\n[5] Creating relationship edges...")
edge_count = create_edges(topics, phase_ids, topic_ids)
total = 1 + 5 + len(topics)
print(f"\n=== DONE ===")
print(f" Entities: {total} (1 umbrella + 5 phases + {len(topics)} topics)")
print(f" Edges: {edge_count}")
print("\n[6] Running test queries...")
run_test_queries()
if __name__ == "__main__":
main()