Skip to content

feat(events-crawler): Capilano University adapter, staged disabled - #94

Merged
ltanafranca1004 merged 2 commits into
mainfrom
feat/events-crawler-capilano
Aug 3, 2026
Merged

feat(events-crawler): Capilano University adapter, staged disabled#94
ltanafranca1004 merged 2 commits into
mainfrom
feat/events-crawler-capilano

Conversation

@ltanafranca1004

@ltanafranca1004 ltanafranca1004 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What & why

Second of the three deferred orgs. The deferral rationale again didn't survive re-checking — each li.event-item carries month, day, optional end date, optional times, venue, link and image inline, so there is no N+1.

Ships enabled: false. No deploy, no enable.

🛑 One page only — a robots decision, not an oversight

capilanou.ca/robots.txt has a User-agent: * block disallowing /*?search=* and /*&search=*. Every calendar-navigation URL the page offers is of the form ?day=14&month=08&year=2026&search=day, so the month and day views are off-limits to us. This adapter reads only the unparameterized listing — about ten upcoming items, no pagination.

That's stated prominently in the adapter header, because the obvious reaction to a low count here is "add the query parameters", and that would be knowingly violating the site's stated policy.

The low yield is correct, not broken

The calendar is largely academic administrivia — fee deadlines, grade-submission dates, campus closures — which relevanceFilter is right to drop. Measured 1 of 10 passing:

✅ kept Fall 2026 New International Student Orientation — caught by relevance.ts's international student
dropped Guitar festival · Pure Imagination · Last Day of Classes · Fee Payment Deadline · Grade Submission Deadline · End-of-Term Processing · Registration Re-opens · Labour Day closure · Fall Term Commences

One genuinely on-mission event is the realistic yield. Unlike NVCL, that's not zero — and it's exactly the audience this app serves.

Decisions baked in

All-day items get 09:00 local. 8 of the 10 have no datelisting span at all — including the one relevant event. events.event_datetime is NOT NULL, so skipping timeless items would have dropped the only row worth having. Storing 09:00 asserts an hour the source doesn't state, which is a real if small cost; 09:00 rather than midnight because the row renders as a time and "12:00 AM" reads as a bug.

Year is carried forward monotonically. No weekday is published, so it can't be checked the way nvcl.ts checks it. The listing is chronological instead, so the year rolls as soon as a month/day goes backwards — which is what stops a December-to-January listing filing January in the past.

Venues> prefix stripped from the venue text, and multi-day end dates come from date-stamp2 (independent of end times, since a multi-day item often has no times and a concert has an end time but no second stamp).

Dedupe is defensive here, not load-bearing — 10 distinct links in 10 blocks, with none of the repeated featured section nvcl.ts has to handle. Kept so a future recurring-event grouping can't produce a duplicate-key insert.

Also fixes a shared gap this surfaced

decodeEntities handled numeric entities and a handful of named ones, but not – — so Last Day of Classes for Summer 2026 – Session II would have been stored verbatim in a title. Added the named forms of punctuation already handled numerically (ndash, mdash, curly quotes, hellip).

This is shared code, so it was regression-tested rather than assumed: mosaic 25, nvdpl 16, surrey 25 after the change — the same counts recorded in #86.

Changes

  • adapters/capilano.ts (new)
  • lib/types.tscapilano added to SourceKind (the ADAPTERS record is exhaustive, so a missing implementation is a compile error)
  • lib/sources.ts — import, ADAPTERS entry, registry block
  • lib/text.ts — named punctuation entities in decodeEntities
  • next.config.ts — allowlist www.capilanou.ca (it serves real event images; NVCL needed no entry, since its listing uses an icon font)

⚠️ Coordination

enabled: false; no migration, cron, Vault secret or DB object touched; not deployed; no writes to shared prod. Enabling needs Savar's sign-off, as the Phase-2 flip did.

Verification

Check Result
deno check (CI's invocation) clean, exit 0
npx tsc --noEmit clean
npm run lint clean — 0 errors (1 warning pre-existing in opengraph-image.tsx)
dryrun --source capilano (shipped) 1 row — the International Student Orientation
dryrun, filter off 10 rows, all fields correct
Regression after text.ts change mosaic 25 · nvdpl 16 · surrey 25 — unchanged

Filter-off output, showing times, multi-day ranges and real covers all resolving:

1. Vancouver Classical Guitar Festival Concerts
   2026-08-15T02:30Z → 2026-08-16T04:30Z   CapU Centre for Performing Arts
   cover …/LAT_2423---editada-y-reducida-copy.jpg
2. Last Day of Classes for Summer 2026 – Session II and Full Term   ← – decoded
   2026-08-21T16:00Z (all-day → 09:00 local)
6. Summer 2026 … End-of-Term Processing
   2026-08-25T16:00Z → 2026-08-26T16:00Z   (multi-day via date-stamp2)

Reviewer notes

  • Structural-miss tracking (title / date stamp) is carried over from nvcl.ts, with the relevance and window filters deliberately excluded from the count — they reject on merit, and dropping most of an academic calendar is their job, so folding them in would fire the diagnostic on every healthy run.
  • date-stamp2 month is parsed rather than assumed equal to the start month, so a range spanning a month or year boundary resolves correctly. No such range exists in the current listing.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for collecting Capilano University events, including dates, venues, links, relevance filtering, and event images.
    • Added Capilano University as a configurable event source, currently disabled by default.
    • Enabled images hosted on the Capilano University website.
  • Bug Fixes

    • Improved text decoding for punctuation such as en dashes, em dashes, curly quotes, and ellipses.
    • Improved handling of multi-day events, month transitions, malformed data, and individual event errors.

Second of the deferred orgs, and the deferral rationale again did not survive the
re-check: each li.event-item carries month, day, optional end date, optional
times, venue, link and image inline, so there is no N+1.

ONE PAGE ONLY, and that is a robots decision rather than an oversight.
capilanou.ca/robots.txt has a `User-agent: *` block disallowing /*?search=* and
/*&search=*, and every calendar-navigation URL the page offers is of the form
?day=14&month=08&year=2026&search=day. So the month and day views are off-limits
and this reads only the unparameterized listing — about ten upcoming items. The
adapter header says so explicitly, because the obvious "fix" for a low count here
is to add the query parameters, and that would be the wrong thing to do.

The low yield is correct, not broken. The calendar is largely academic
administrivia — fee deadlines, grade-submission dates, campus closures — which
relevanceFilter is right to drop. Measured 1 of 10 passing: "Fall 2026 New
International Student Orientation", caught by relevance.ts's `international
student` term. One genuinely on-mission event is the realistic yield.

Most items have no time. 8 of those 10 are all-day entries with no datelisting
span at all, INCLUDING the one relevant event, so they are stored at 09:00 local.
That asserts an hour the source does not state, which is a real if small cost;
skipping timeless items instead would have dropped the only event worth having.

No weekday is published, so the year cannot be checked the way nvcl.ts checks it.
The listing is chronological instead, so the year is carried forward monotonically
and rolls as soon as a month/day goes backwards — which is what stops a
December-to-January listing filing January in the past.

Also fixes a shared gap this surfaced: decodeEntities handled numeric entities and
a few named ones but not –, so "Last Day of Classes for Summer 2026 –
Session II" would have been stored verbatim. Added the named forms of punctuation
already handled numerically (ndash, mdash, curly quotes, hellip). Regression
dry-run across other adapters after the change: mosaic 25, nvdpl 16, surrey 25 —
the same counts recorded in #86.

Verified with the real harness: deno check clean; dryrun parses all 10 blocks with
correct times, multi-day ranges, stripped "Venues>" prefixes and real cover images
for the two concerts, and yields exactly the 1 relevant row as shipped. tsc and
lint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-app Ready Ready Preview Aug 3, 2026 3:42am

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c4b8d27f-0252-49e0-a88a-65f08db5830d

📥 Commits

Reviewing files that changed from the base of the PR and between 40ca9e3 and 1f62068.

📒 Files selected for processing (5)
  • next.config.ts
  • supabase/functions/events-crawler/adapters/capilano.ts
  • supabase/functions/events-crawler/lib/sources.ts
  • supabase/functions/events-crawler/lib/text.ts
  • supabase/functions/events-crawler/lib/types.ts

Walkthrough

Adds a Capilano University events adapter with date, relevance, venue, image, deduplication, and error handling. Registers the disabled source, adds its source kind, decodes additional HTML entities, and allowlists its image host.

Changes

Capilano events

Layer / File(s) Summary
Shared source contracts and text decoding
supabase/functions/events-crawler/lib/types.ts, supabase/functions/events-crawler/lib/text.ts
Adds capilano to SourceKind and decodes additional named HTML entities.
Capilano event fetch and transformation
supabase/functions/events-crawler/adapters/capilano.ts
Fetches the fixed listing, parses chronological dates, filters and normalizes candidates, deduplicates results, resolves images, and creates EventRow records.
Source registration and image host configuration
supabase/functions/events-crawler/lib/sources.ts, next.config.ts
Registers the disabled Capilano source and adapter, and allows images from www.capilanou.ca.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the Capilano University events adapter and states that it is staged as disabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/events-crawler-capilano

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ltanafranca1004

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/functions/events-crawler/adapters/capilano.ts`:
- Around line 84-90: Extend StructuralMisses and PageParse to track venue
failures and postFilterAttempts separately from the pre-filter structural count.
Increment postFilterAttempts for each candidate reaching venue extraction,
increment misses.venue when location is absent before continuing, and update the
final diagnostic check to alert when every post-filter attempt failed venue
extraction while preserving existing title/stamp handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: baa2e958-f325-4652-aa9b-87ab8bd6bd14

📥 Commits

Reviewing files that changed from the base of the PR and between 40ca9e3 and 911721c.

📒 Files selected for processing (5)
  • next.config.ts
  • supabase/functions/events-crawler/adapters/capilano.ts
  • supabase/functions/events-crawler/lib/sources.ts
  • supabase/functions/events-crawler/lib/text.ts
  • supabase/functions/events-crawler/lib/types.ts

Comment thread supabase/functions/events-crawler/adapters/capilano.ts
…l miss

Valid finding, and the same silent-zero hazard as nvcl.ts's third round — which I
said I would carry forward into this adapter and then missed one layer of. Venue
extraction sat after the relevance and window filters, and its `continue` was
counted by nothing: if VENUE_RE stopped matching, every row would drop there and
the source would go to zero with no diagnostic, looking exactly like a university
with nothing on.

Fixed by moving venue extraction ahead of the filters rather than adding a second
counter for post-filter attempts. Ordering was the actual defect: a structural
failure sitting downstream of merit-based ones cannot be compared against the
block count, because filtered rows never reach it. With the whole extraction chain
— title, stamp, venue — ahead of the filters, the existing single comparison
covers all three, and there is no second counter to keep in sync. Cost is a regex
on rows about to be discarded, across roughly ten blocks.

Verified by breaking VENUE_RE against the live feed: "matched 10 block(s) but
extracted none of them (title 0, date stamp 0, venue 10)". Silent before this.
Restored, the shipped config still yields its 1 row. deno check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ltanafranca1004

Copy link
Copy Markdown
Collaborator Author

Applied in 1f62068 — valid, and a fair catch. This is the same silent-zero hazard as nvcl.ts's third round, which I said I'd carry forward into this adapter and then missed one layer of.

Venue extraction sat after the relevance and window filters, and its continue was counted by nothing. If VENUE_RE stopped matching, every row would drop there and the source would go to zero with no diagnostic — indistinguishable from a university with nothing on.

Fixed by reordering rather than adding a second counter. The suggestion was to track postFilterAttempts alongside the structural count and compare venue failures against that. That works, but the ordering is the defect: a structural failure sitting downstream of merit-based ones can never be compared against the block count, because filtered rows never reach it. Moving venue extraction ahead of the filters puts the whole extraction chain — title → stamp → venue — in one place, so the existing single comparison covers all three and there's no second counter to keep in sync as the adapter changes.

The cost is a regex on rows about to be discarded, across roughly ten blocks. Documented inline so the ordering doesn't look accidental and get "tidied" back later.

Verified by breaking VENUE_RE against the live feed:

matched 10 block(s) but extracted none of them (title 0, date stamp 0, venue 10)
  — the row markup has probably changed

Silent before this change. Restored, the shipped config still yields its 1 row (parsed 10 block(s), 1 in-window + relevant). deno check clean, source still enabled: false.

@ltanafranca1004

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@ltanafranca1004
ltanafranca1004 merged commit 383367e into main Aug 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant