A personal learning diagnostic tool: ingests grades, assignments, calendar events, and Obsidian vault notes, then generates a local analytics dashboard.
Made for my own classes. It answers one question: am I actually learning this, or just writing it down?
Requires Python 3.7+. The core (grades, assignments, dashboard) uses only the standard library. The calendar command additionally needs the Google client libs (pip install -r requirements.txt). No other dependencies.
- Grade ingestion from Infinite Campus, either from an
_Ingest.mdnote or piped via stdin - Assignment ingestion from Google Classroom
- Calendar fetch/add via Google Calendar
- Vault analysis over an Obsidian school vault: note counts, study materials, concept coverage, weak spots (classes with notes but no study), and cross-class concepts
- Dashboard generation as a single self-contained HTML file with Chart.js visualizations
doctorcommand that checks your install and tells you what's missing
python analytics.py <command>| Command | Description |
|---|---|
setup |
First-run scaffold: creates config.json, the vault folders, and ingest templates |
chat-ingest grades |
Pipe grade data via stdin and ingest |
chat-ingest assignments |
Pipe assignment data via stdin and ingest |
sync-vault |
Scan vault: notes, concepts, study gaps, cross-class links |
calendar |
Fetch upcoming events (requires auth first run) |
calendar-add 'Summary|2026-07-29T14:32:00|2026-07-29T15:00:00|[calendar]' |
Add a calendar event |
build |
Generate the analytics dashboard |
open |
Build the dashboard and open it in a browser |
doctor |
Check the install: config, vault, data, calendar auth |
Legacy ingest mode (reads from _Ingest.md instead of stdin):
python analytics.py ingest grades
python analytics.py ingest assignmentsanalytics.py— CLI entry point and dashboard renderersources/— ingest modules:infinite_campus.py— grade parsinggoogle_classroom.py— assignment parsingcalendar.py— Google Calendar read/writevault.py— Obsidian vault analysis
dashboard/— generated HTML outputdata/— JSON store (grades, assignments, calendar events, vault insights), created on first runconfig.json— your local settings (gitignored)
Copy config.example.json to config.json and set vault_path to your Obsidian vault root. Or skip the file and use export SCHOOL_VAULT=/path/to/vault or --vault <path> per run.
{
"vault_path": "C:/Users/YOU/Path/To/Your/Obsidian/Vault",
"timezone": "America/Denver"
}timezoneinconfig.jsondefaults toAmerica/Denver; change it if you're elsewhere.- Calendar auth stores a local
token.jsonon first run (kept out of git via.gitignore).
The core (grades, assignments, dashboard) needs only Python stdlib. The calendar command additionally needs the Google client libs:
pip install -r requirements.txtThen scaffold everything for zero setup:
python analytics.py setupsetup creates config.json (defaulting the vault to a local ./school_vault folder), the vault folder skeleton, a blank Course Registry.md, and a blank _Ingest.md. Nothing else to configure:
python analytics.py doctor
python analytics.py sync-vault
python analytics.py openTo use your real Obsidian vault instead of the local default, edit config.json and set vault_path to your vault root (or export SCHOOL_VAULT=/path/to/vault, or pass --vault <path> per run):
{
"vault_path": "C:/Users/YOU/Path/To/Your/Obsidian/Vault",
"timezone": "America/Denver"
}Then run setup again to create the folders inside your real vault.
doctor prints a checklist of what's present and what's missing, so a broken setup fails loudly instead of silently.
The vault must use this exact folder layout (setup creates it for you):
<Vault>/
01 Classes/<ClassName>/
Notes/ # .md notes
Study/ # .md study materials
03 Museum/
04 Meta/
Analytics/
_Ingest.md # legacy ingest reads its tables from here
Course Registry.md
05 Tags/
06 Concepts/
If the layout is wrong, sync-vault will print exactly which folders it looked for. Concept extraction reads a line like concepts: [x, y, z] from the top of each note.
Grades/assignments can be piped in without any vault setup:
echo "| class | assignment | score |" | python analytics.py chat-ingest gradesThe calendar commands talk to the Google Calendar API and need your own OAuth client:
- Go to Google Cloud Console, create a project, and enable the Google Calendar API.
- Under APIs & Services → Credentials, create an OAuth client ID of type Desktop app.
- Download the JSON and save it as
client_secret.jsonin this folder. - Run
python analytics.py calendar. A browser window opens; authorize the app. Atoken.jsonis saved locally and reused from then on.
Both client_secret.json and token.json are gitignored. The redirect goes to http://127.0.0.1:42813/callback, so the OAuth client must allow that port.
config.json,client_secret.json,token.json, and everything indata/are gitignored. Your vault path and credentials never land in the repo.- Ingests dedupe by class + assignment: pasting the same table twice won't duplicate rows, and a re-ingest with a corrected score overwrites the old one.