feat(library-traffic): Library Traffic Averaging Endpoints#413
Open
estao1 wants to merge 26 commits into
Open
Conversation
…eting old records
…rom 13 to 15 months
… and entry structures
…rn library traffic history queries
…ypes for aggregation
…e date examples for consistency
…le date ranges and improve cursor handling
…pes and queries to 1800 seconds
…Id examples for consistency
… and simplify date handling
…e and endDate optional
…Date optional and refine validation logic
…ror handling and update period to bucketStart
…ing redundant error checks
… descriptions for historical data routes
…artDate, and endDate fields in query schemas
…ive and clarify granularity handling
…urate bucket calculations
laggycomputer
requested changes
May 23, 2026
Comment on lines
+235
to
+236
| ? sql`EXISTS (SELECT 1 FROM calendar_term ct WHERE ${libraryTrafficHistory.timestamp} BETWEEN ct.finals_start AND ct.finals_end${yearClause}${quarterClause})` | ||
| : sql`EXISTS (SELECT 1 FROM calendar_term ct WHERE ${libraryTrafficHistory.timestamp} BETWEEN ct.instruction_start AND ct.instruction_end${yearClause}${quarterClause})`; |
Member
There was a problem hiding this comment.
We can't use any less raw SQL for this?
Comment on lines
+6
to
+22
| const la = new Intl.DateTimeFormat("sv-SE", { | ||
| timeZone: "America/Los_Angeles", | ||
| year: "numeric", | ||
| month: "2-digit", | ||
| day: "2-digit", | ||
| hour: "2-digit", | ||
| minute: "2-digit", | ||
| second: "2-digit", | ||
| hour12: false, | ||
| }).format(utcDate); | ||
| const localStr = la.replace(" ", "T"); | ||
| const offsetMin = (Date.parse(`${localStr}Z`) - utcDate.getTime()) / 60000; | ||
| const sign = offsetMin >= 0 ? "+" : "-"; | ||
| const abs = Math.abs(offsetMin); | ||
| const offset = `${sign}${String(Math.floor(abs / 60)).padStart(2, "0")}:${String(abs % 60).padStart(2, "0")}`; | ||
| return `${localStr}${offset}`; | ||
| } |
Member
There was a problem hiding this comment.
I suspected this is duplicated with study rooms code. Make sure this is new or consolidate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds three new REST + GraphQL endpoints under
/v2/rest/libraryTrafficfor accessing historical occupancy data, alongside small consistency improvements to the existing snapshot endpoint.New endpoints
GET /historyGET /history/aggregatedhour/day/week/month). Scope with(startDate + endDate)or(year + quarter). Range cap per granularity to bound response size.GET /history/patternEquivalent GraphQL queries (
libraryTrafficHistory,libraryTrafficHistoryAggregated,libraryTrafficHistoryPattern) are exposed with matching shapes and@cacheControl(maxAge: 1800).Implementation notes
year + quarter + periodare convenience filters that resolve viacalendarTerm. Raw and aggregated overwrite the date range from the term; pattern joins ontimestamp BETWEEN periodStart AND periodEndso each row gets its term context.floor((ts - periodStart)/604800) + 1), so week numbers are 1–10 (term-relative) rather than ISO weeks of the year. Whengranularity=weekwith an explicit quarter filter, rows are separated per-term and includeyear/quarterin the response; otherwise data is combined across terms.(timestamp > cursor.ts) OR (timestamp = cursor.ts AND id > cursor.id)predicate to keep pagination stable across duplicate timestamps (multiple locations scraped at the same second).productionCache({ maxAge: 1800 }), matching the 30-minute scraper cadence and the GraphQL@cacheControlTTLs.HTTPException(400)instead ofError, so users get proper status codes instead of 500s.200with an empty array/items, matching the codebase convention (courses, instructors, enrollment-history). The pre-existing snapshot endpoint keeps its400-on-emptybehavior.Related Issue
Closes #243
Motivation and Context
The existing snapshot endpoint only exposes current occupancy. Consumers (study-spot recommendations, dashboards, etc.) need historical views — both raw records for export/analysis and aggregated/pattern views for charts.
How Has This Been Tested?
Manually verified each endpoint via
curlagainst a local dev server with backfilled history:GET /libraryTraffic— snapshot regressionGET /libraryTraffic/history— pagination terminates correctly; composite cursor handles duplicate timestampsGET /libraryTraffic/history/aggregatedwith(startDate + endDate),(year + quarter), and missing both (422)GET /libraryTraffic/history/patternwithgranularity=hour|day|week|month, with and withoutyear/quarterfilters, verifying week-of-term buckets are 1–10 and labels are clean ("2pm","Monday","Week 5")pnpm check:typesandpnpm check:biomeclean forapps/apiScreenshots (if appropriate):
Types of changes
Checklist: