• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
pchinjr

pchinjr

notion_weekly_report

Public
Like
notion_weekly_report
Home
Code
12
.vscode
2
collectors
2
services
2
shared
6
storage
1
tests
4
.env.example
.gitignore
PLAN.md
README.md
TUTORIAL.md
deno.json
Environment variables
12
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
TUTORIAL.md
Code
/
TUTORIAL.md
Search
1/18/2026
Viewing readonly version of main branch: v371
View latest version
TUTORIAL.md

Notion Life Tracker Tutorial (Val Town)

This walkthrough shows how to build, deploy, and operate the Notion-backed logging app in this repo. It is based on the commit log, README.md, and PLAN.md, and it explains why the architecture evolves as features are added.

0) What you are building

You will end up with three Val Town scripts:

  • A weekly blood sugar report that emails a summary and persists rollups to SQLite.
  • A weekly food log enricher that estimates macros with OpenAI and writes them back to Notion.
  • A monthly report page that renders HTML from the stored weekly rollups.

The design is intentionally layered so the system stays fast and predictable as data grows.

1) Architecture overview (and why it is shaped this way)

The code is split by responsibility:

  • collectors/: scheduled jobs that pull the last 7 days of Notion data and compute a weekly rollup.
  • shared/: reusable parsing and rollup logic shared across collectors.
  • storage/: SQLite persistence so reports do not have to re-scan Notion.
  • services/: HTTP endpoints for reporting (monthly report page) and optional rollup ingestion.

Why this architecture works as the app grows:

  1. Short Notion queries stay fast. Each collector only asks Notion for a one-week window. This keeps API calls cheap and predictable even as your Notion database grows.
  2. Rollups make reporting cheap. Once a weekly rollup is computed, it is stored in SQLite (weekly_rollups_1). Monthly pages and future yearly summaries can use SQLite instead of Notion.
  3. Idempotent writes avoid duplicates. Rollups use a deterministic runId (for example blood_sugar-YYYY-MM-DD-YYYY-MM-DD) and SQLite has a unique constraint on run_id, so re-running a week does not double-count.
  4. Collectors stay focused. Each collector normalizes its own Notion database, then hands off common logic to shared/ and storage to storage/.
  5. HTTP services scale reporting. The monthly report page is just a read-only HTTP val that reads SQLite and renders HTML. This keeps interactive reporting separate from scheduled ingestion.

2) Set up Notion databases

Create two Notion databases (table view recommended).

Blood Sugar Log

Properties (exact names expected):

  • Entry (Title)
  • Created Time (Created time) or Created Time (Text)
  • Measurement Date (Date)
  • Blood Sugar Level (Number)

Food Log (Macro Enrichment)

Properties (exact names expected):

  • food (Title or Text)
  • Created time (Created time)
  • calories (Number)
  • protein (Number)
  • carbs (Number)
  • fats (Number)
  • fiber (Number)
  • sugar (Number)
  • sodium (Number)

Only food and Created time are required to read; the rest will be written by the food enricher.

3) Create a Notion integration

  1. Go to https://www.notion.so/my-integrations and create a new integration.
  2. Copy the Internal Integration Token.
  3. Open each database, click Share, and invite the integration.
  4. Copy each database ID (the 32-character ID in the URL).

4) Create Val Town vals and deploy

Create vals for each script:

vt create blood_sugar_report vt create food_report vt create monthly_report_page

Replace the contents of the new vals with these repo files:

  • collectors/blood_sugar_report.cron.tsx
  • collectors/food_report.cron.tsx
  • services/monthly_report_page.http.tsx

Push everything:

vt push

5) Configure secrets in Val Town

Shared:

  • NOTION_TOKEN

Blood sugar report:

  • NOTION_BLOOD_SUGAR_DB_ID

Food report:

  • NOTION_FOOD_DB_ID

Notes:

  • On the free tier, the blood sugar report always emails the account owner; sender options are not configurable.
  • The food val uses Val Town's std/openai proxy with gpt-5-nano, so you do not need an OpenAI API key.

6) Schedule the weekly collectors

Schedule both collectors in Val Town. A typical schedule is Tuesday at 9:30am EST.

30 9 * * 2

Cron fields:

minute hour day-of-month month day-of-week

7) Operate and verify

Validate after the first run

  • Blood sugar val should send an email and write a rollup row to SQLite.
  • Food val should enrich any missing macros and write a rollup row to SQLite.
  • Monthly report page should render without errors.

If you need to manually trigger a week, run the vals once in Val Town. The rollup table is idempotent because run_id is unique.

Where the data goes

  • Weekly rollups: storage/rollups.ts stores them in weekly_rollups_1.
  • Monthly report page: services/monthly_report_page.http.tsx reads from SQLite and renders the month.

Monthly report page usage

The monthly report page supports query parameters:

  • ?month=YYYY-MM (defaults to previous month)
  • ?partial=true to include weeks that overlap the month boundary

8) Why the architecture evolves (from commit history)

The project grew in stages:

  1. Weekly Notion email: the first goal was a friendly weekly report without heavy infrastructure.
  2. Tests and logic extraction: core rollup logic moved into shared/ with tests, keeping collectors small.
  3. Macro enrichment: a second collector was added for food logs, reusing the same weekly-window approach.
  4. SQLite persistence: weekly rollups were persisted so monthly/annual summaries could be built without re-reading Notion.
  5. Monthly report page: an HTTP val renders a full HTML report from SQLite data.

Each step keeps the system simple while expanding capabilities:

  • We never need to scan all of Notion.
  • Scheduled jobs do a single week of work.
  • Reports are built from stored rollups, not raw entries.

9) Extend the system (pattern for new trackers)

To add a new tracker:

  1. Create a new Notion database and define its properties.
  2. Add a new collector val in collectors/ that:
    • pulls a weekly window from Notion,
    • maps it into a shared rollup format,
    • persists to SQLite using upsertWeeklyRollup.
  3. Add new aggregations in shared/ and extend the monthly report page if desired.

This keeps the system consistent and avoids one-off logic when more categories are added.

10) Local development

Run tests:

deno test --allow-import --reporter=dot

Run lint:

deno lint
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.