Skip to content

Instantly share code, notes, and snippets.

@dmccreary
Created June 19, 2026 17:47
Show Gist options
  • Select an option

  • Save dmccreary/ca9a391bc781a3862fee62debb3f93ab to your computer and use it in GitHub Desktop.

Select an option

Save dmccreary/ca9a391bc781a3862fee62debb3f93ab to your computer and use it in GitHub Desktop.
Intelligent Textbooks — Site Analytics report automated monthly via Claude Code Routines

Automating the Intelligent Textbook "Site Analytics" Report with Claude Code Routines

This note documents how the Site Analytics MicroSim (https://dmccreary.github.io/intelligent-textbooks/sims/site-analytics/) is refreshed and republished automatically once a month using the Claude Code Routines (scheduled-tasks) feature.

The MicroSim renders a ranked bar chart of Google Analytics (GA4) page views over the last 90 days for every published intelligent textbook. The chart is driven by a CSV; this routine regenerates that CSV and redeploys the site so the numbers — and the "Last Update" date shown at the top of the chart — stay current without anyone remembering to do it by hand.


At a glance

Routine ID refresh-site-analytics-report
Schedule First day of every month at 9:00 AM local time
Cron 0 9 1 * * (5-field cron, evaluated in local time, not UTC)
Where it's stored ~/.claude/scheduled-tasks/refresh-site-analytics-report/SKILL.md
Where to manage it the Scheduled section in the Claude Code sidebar
Repo it operates on ~/Documents/ws/intelligent-textbooks
Runs fully autonomously — report → verify → commit → push → gh-deploy

How Claude Code Routines work

A "Routine" is a saved prompt that Claude Code runs on a cron schedule, locally on this Mac (not in a remote cloud sandbox). That locality matters here: the job needs the local service-account key, the local conda environments, and read access to every ~/Documents/ws/*/mkdocs.yml — none of which a cloud runner would have.

Key behaviors:

  • Each run starts fresh with no memory of the conversation that created it, so the routine's prompt is fully self-contained.
  • It runs while the Claude Code app is open. If the app is closed at 9:00 AM on the 1st, the run fires the next time the app is launched — so it is never skipped, only (occasionally) delayed.
  • A small deterministic dispatch delay (a few minutes) is applied to recurring tasks to balance load.
  • Tool approvals are remembered. The first time the routine runs git push and mkdocs gh-deploy, Claude asks for permission. Approving once (e.g. via Run now) stores the approval on the task so future unattended runs don't pause on a prompt.
  • Manage it from the Scheduled sidebar: Run now, pause/resume, edit the schedule or prompt, or delete.

What each run does (the pipeline)

  1. Run the report with the google-analytics conda env's Python and --refresh (so newly-registered textbooks get mapped):

    /usr/local/Caskroom/miniforge/base/envs/google-analytics/bin/python \
        src/site-analytics/ga4-pageviews-report.py --refresh --csv

    The script scans every ~/Documents/ws/*/mkdocs.yml for a property: G-XXXXX measurement ID, maps each to its numeric GA4 property via the Analytics Admin API (cached in ga4-property-map.json), then queries the Analytics Data API for screenPageViews over the last 90 days. It writes src/site-analytics/ga4-pageviews-results.csv (and refreshes the property-map cache).

  2. Sanity-check the output before publishing — the CSV must exist, its header must contain page_views and generated, it must have > 10 data rows, and the page-view total must be > 0. If the report fails or the CSV looks wrong, the run stops without committing or deploying and reports the error.

  3. Copy the refreshed CSV into the MicroSim directory:

    cp src/site-analytics/ga4-pageviews-results.csv \
       docs/sims/site-analytics/ga4-pageviews-results.csv
  4. Publish — stage only the three data files (never a blanket git add -A), commit, push, and deploy with the mkdocs conda env:

    git add docs/sims/site-analytics/ga4-pageviews-results.csv \
            src/site-analytics/ga4-pageviews-results.csv \
            src/site-analytics/ga4-property-map.json
    git commit -m "Refresh site-analytics GA4 page-views data (automated monthly run)"
    git push
    conda activate mkdocs && mkdocs gh-deploy -m "Refresh site-analytics data [skip ci]"

    If nothing is staged (data unchanged), it skips the commit/deploy.

  5. Self-heal on divergence. If git push is rejected as non-fast-forward, it runs git pull --rebase and retries. If mkdocs gh-deploy is rejected pushing to gh-pages, it syncs the local branch (git fetch origin gh-pages && git branch -f gh-pages origin/gh-pages) and retries once.

  6. Report a short summary: number of textbooks, total page views, and whether the MicroSim was published.


Prerequisites / environment

These must exist on the machine for the routine to succeed (they are already set up):

  • Two conda environments:
    • google-analytics — runs the report. Has google-analytics-data and google-analytics-admin installed.
    • mkdocs — runs mkdocs gh-deploy. Has mkdocs + mkdocs-material.
  • A GA service account key at ~/.config/gcloud/textbook-analytics-key.json (chmod 600). The script auto-detects it at that path, or honors GOOGLE_APPLICATION_CREDENTIALS.
  • The service account textbook-analytics-reader@textbook-analytics.iam.gserviceaccount.com must be a Viewer at the GA account level so it can see every property under the account in one grant.
  • Both the Analytics Admin API and Analytics Data API enabled in the textbook-analytics Google Cloud project.
  • git push credentials for github.com/dmccreary/*.

⚠️ Never commit the service-account key. It is gitignored (*-key.json) and the repos are public. If it is ever exposed, rotate it in the Cloud Console.


Files involved

Path (relative to intelligent-textbooks/) Role
src/site-analytics/ga4-pageviews-report.py The report generator (scan → map → query → CSV).
src/site-analytics/ga4-property-map.json Cache of G-XXXXX → numeric property ID.
src/site-analytics/ga4-pageviews-results.csv Report output. Columns: rank, slug, measurement_id, numeric_id, page_views, generated.
src/site-analytics/README.md Full setup + troubleshooting reference.
docs/sims/site-analytics/ga4-pageviews-results.csv The MicroSim's copy of the data (what the chart fetches).
docs/sims/site-analytics/site-analytics.js Chart.js sketch; reads the CSV and shows the Last Update date from the generated column.
docs/sims/site-analytics/index.md Lesson page that embeds the chart in an iframe.

The "Last Update: DD/MM/YYYY" line on the chart comes from the CSV's generated column, which the report stamps with its run date — so it always reflects when the report last ran, not when the site last deployed.


The routine definition

Stored verbatim at ~/.claude/scheduled-tasks/refresh-site-analytics-report/SKILL.md:

---
name: refresh-site-analytics-report
description: Automated monthly run: refresh the GA4 site-analytics page-views report and publish the MicroSim (report, commit, push, gh-deploy)
---

Automated MONTHLY job — run fully autonomously, end to end. Objective: refresh the
Google Analytics page-views data that powers the "Site Analytics" MicroSim ...
(STEP 1 run report --refresh --csv; STEP 2 sanity-check; STEP 3 copy CSV;
 STEP 4 commit only the 3 data files, push, mkdocs gh-deploy; STEP 5 handle
 non-fast-forward divergence; STEP 6 report a summary)

(The full prompt lives in that file — edit it there or via Scheduled → edit.)


Managing the routine

  • Run it now / pre-approve permissions: Scheduled sidebar → the task → Run now. Do this once so git push / gh-deploy approvals are saved.
  • Change the schedule or prompt: edit it in the Scheduled sidebar (or ask Claude: "update my refresh-site-analytics-report routine to …").
  • Pause / resume: toggle it in the Scheduled sidebar.
  • Cron cheatsheet: 0 9 1 * * = 09:00 on day-of-month 1. 0 9 * * 1 = 09:00 every Monday. 0 9 1 */3 * = 09:00 on the 1st every 3rd month.

Running it manually (without the routine)

From ~/Documents/ws/intelligent-textbooks:

conda activate google-analytics
python src/site-analytics/ga4-pageviews-report.py --csv      # add --refresh after adding new books
cp src/site-analytics/ga4-pageviews-results.csv docs/sims/site-analytics/
conda activate mkdocs
git add docs/sims/site-analytics/ga4-pageviews-results.csv src/site-analytics/ga4-pageviews-results.csv
git commit -m "Refresh site-analytics page-views data"
git push
mkdocs gh-deploy

Troubleshooting

Symptom Cause / fix
Routine never fires Claude Code app was closed at the scheduled time — it runs on next launch.
Run pauses asking permission First-time git push / gh-deploy approval — click Run now once to store it.
DefaultCredentialsError Key missing at ~/.config/gcloud/textbook-analytics-key.json or GOOGLE_APPLICATION_CREDENTIALS unset.
403 SERVICE_DISABLED Admin/Data API not enabled in the textbook-analytics Cloud project.
A book is skipped ("no numeric ID found") Its property is on a GA account the service account can't see — grant Viewer there, then run with --refresh.
push / gh-deploy rejected (non-fast-forward) Remote moved ahead — git pull --rebase, or for gh-pages git fetch origin gh-pages && git branch -f gh-pages origin/gh-pages, then retry.
All bars render as 0 The CSV header lost the page_views column name (e.g. a stray carriage return). Regenerate via the Python report, which writes a clean CSV.

See src/site-analytics/README.md in the repo for the full setup guide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment