Skip to content

aryank09/UW-Compass

Repository files navigation

UW Compass

An AI-powered resource finder for University of Washington students. Describe your situation in plain language and get pointed to the right official UW resources, with explanations and a clear next step.

CSS 382 — Introduction to Artificial Intelligence · DYOP group project

Quick start

npm install
cp .env.example .env.local        # then put your OpenAI key in .env.local
npm run seed                      # generates embeddings for the curated resource set
npm run dev                       # http://localhost:3000

You need an OpenAI API key with billing enabled. Seeding all 31 resources costs a fraction of a cent on text-embedding-3-small.

What it does

  1. Student types something like "I'm overwhelmed, behind in math, and need a quiet place to study."
  2. The app extracts structured needs (academic, study_space) with intensities and supporting evidence using gpt-4o-mini + function calling.
  3. In parallel, it embeds the same input with text-embedding-3-small.
  4. It computes a combined score against every resource — embedding similarity, category match, tag overlap, urgency boost — then takes the top 5 with category diversification.
  5. gpt-4o-mini writes a short per-resource explanation and a 2–4 step action plan.
  6. The UI shows the extracted needs, ranked recommendations with links to official UW pages, and the next-step plan.

Architecture

flowchart TB
    Browser["Browser (page.tsx)"]
    API["app/api/recommend/route.ts"]
    Embed["lib/ai.ts → text-embedding-3-small"]
    Extract["lib/ai.ts → gpt-4o-mini<br/>(need extraction, function calling)"]
    DB["lib/db.ts<br/>data/resources.embedded.json"]
    Rank["lib/recommend.ts<br/>cosine + tags + category + urgency"]
    Summarize["lib/ai.ts → gpt-4o-mini<br/>(per-resource why + next steps)"]

    Browser -- "POST { input }" --> API
    API --> Embed
    API --> Extract
    Embed --> Rank
    Extract --> Rank
    DB --> Rank
    Rank -- "top 5 + needs" --> Summarize
    Summarize -- "{ needs, recommendations, next_steps }" --> Browser
Loading

Three OpenAI calls per request: 1 embedding + 2 chat completions (embed and need extraction run in parallel). Resource embeddings are computed once at seed time. Optional power-user flags (two_pass, use_ai_ranker) add 1–2 extra calls.

Project layout

app/
  layout.tsx              root layout
  page.tsx                client-side form + results UI
  about/page.tsx          project website (overview, AI, user guide)
  globals.css             tailwind entrypoint
  api/recommend/route.ts  POST endpoint
lib/
  types.ts                Resource, ExtractedNeed, Recommendation, Category
  db.ts                   reads data/resources.embedded.json at build time
  ai.ts                   OpenAI calls: embed, extractNeeds, summarize
  recommend.ts            cosine similarity + ranking + diversification
data/
  resources.json          curated UW resources (edit this to add/update)
  resources.embedded.json generated by `npm run seed` — committed so Vercel builds work
scripts/
  seed.ts                 reads resources.json, computes embeddings, writes embedded JSON
  check-links.ts          HEAD-checks every URL in resources.json
tests/
  recommend.test.ts       ranker unit tests
  resources.test.ts       schema validation + UW-domain check
  scenarios.test.ts       the 5 proposal evaluation scenarios

Updating the resource set

Resources live in data/resources.json. Each entry has:

{
  id: string;            // stable slug, used as primary key
  name: string;          // display name
  category: 'academic' | 'wellness' | 'basic_needs' | 'transportation'
          | 'study_space' | 'career' | 'financial';
  campus: 'seattle' | 'bothell' | 'tacoma' | 'all';
  description: string;   // 1–3 sentences; this is what gets embedded
  url: string;           // official UW page
  tags: string[];        // snake_case fine-grained tags
  urgent: boolean;       // true for crisis-time resources (SafeCampus, Husky HelpLine, etc.)
}

After editing, re-run npm run seed to refresh embeddings, then npm run check-links to verify URLs.

Important: data/resources.embedded.json is a generated artefact that is committed to the repository so Vercel builds work without an OpenAI key. If you edit data/resources.json and forget to run npm run seed, the deployed app will silently use stale embeddings — the resource list will look correct but semantic search quality degrades. Always commit the updated resources.embedded.json alongside any resources.json changes.

Tuning the ranker

Weights live in lib/recommend.ts:

weights: {
  embedding:     0.5,   // semantic similarity (cosine of student input vs. resource description)
  categoryMatch: 0.25,  // does the resource's category match an extracted need?
  tagOverlap:    0.15,  // share of extracted tags that appear in the resource's tag list
  urgencyBoost:  0.1,   // student appears urgent AND resource is flagged urgent
}

Adjust and re-run npm test to check that all 5 evaluation scenarios still pass.

API reference

POST /api/recommend

Returns a streaming NDJSON response (one JSON object per line). Non-success responses (4xx / 5xx) are plain JSON.

Request body

Field Type Default Description
input string (3–2000 chars) required Student's free-text description
campus "all" | "seattle" | "bothell" | "tacoma" "all" Filter resources by campus
advisor boolean false Include per-signal scores and full ranked list
shareQuery boolean false Anonymously log the query to the gallery
prior_needs ExtractedNeed[] Needs from a previous turn; merged with fresh extraction
student_context { year?, commuter?, first_gen? } Persona hints for need extraction
two_pass boolean false Run a second self-critique pass on extracted needs
use_ai_ranker boolean false Add an LLM re-ranking pass on top of cosine

Stream events

type Payload When
needs { needs: ExtractedNeed[] } After embed + extract complete (~1 s)
done Full RecommendResponse fields After summarization completes
error { error: string } If the pipeline fails mid-stream

Advisor mode — send ?advisor=1 in the browser URL or advisor: true in the body to receive advisorData.allResults (all resources with per-signal scores) and meta (token counts, latency, ranker label).

POST /api/feedback

Field Type
resourceId string
query string
campus string
helpful boolean

Rate-limited to 50 votes/min per IP.

GET /api/gallery

Returns { queries: string[] } — the last 50 anonymously shared queries (requires KV; returns [] without it).


Testing

npm test            # full test suite (Vitest)
npm run check-links # HEAD-checks every URL in resources.json
npm run typecheck   # tsc --noEmit
npm run lint        # ESLint
npm run format      # Prettier (auto-fix)

The scenario tests bypass OpenAI by hand-crafting expected ExtractedNeed[] for each of the proposal's five evaluation scenarios (§9) and asserting the ranker surfaces matching resources in the top 5.

Manual smoke test against a running dev server:

curl -s -X POST http://localhost:3000/api/recommend \
  -H "content-type: application/json" \
  -d '{"input":"I am failing calculus and need help."}' | jq

Deployment (Vercel)

The app deploys directly to Vercel — no database server needed because resource embeddings are bundled as data/resources.embedded.json at build time.

  1. Push to main.
  2. Import the repo at vercel.com/new (you need push access).
  3. Add OPENAI_API_KEY under Environment Variables.
  4. Deploy. Every subsequent push to main auto-deploys.

If your GitHub account doesn't own the repo, the CLI alternative (vercel login && vercel --prod) works from any clone.

Set a monthly spend cap at platform.openai.com/settings/organization/limits so a runaway script can't drain your account.

Milestone status (proposal §13)

  • Week 1 — repo + seeded resource DB + working /api/recommend
  • Week 2 — end-to-end AI pipeline + frontend + 5 scenarios verified
  • Week 3 — public deployment, project website, final polish

Done

  • 31 curated resources across all 7 proposal categories
  • OpenAI embedding + need-extraction + summarization pipeline
  • Ranked recommendations with multi-signal scoring + category diversification
  • Single-page React UI with example prompts, urgent banner, next-step plan
  • Project website at /about (overview, impact, architecture, AI explanation, user guide)
  • Vitest test suite — 60+ tests covering ranker, schema, scenarios, i18n, iCal, and rate limiter
  • Link-health checker script (mitigates the "resources go stale" risk from §15)

Stretch goals from §5

  • Campus filter (All / Seattle / Bothell / Tacoma) — pill selector above the input
  • Feedback buttons ("helpful" / "not helpful") with anonymous logging (KV or local .jsonl)
  • Last result persisted in localStorage — automatically restored on next visit
  • Multilingual UI (en / es / zh / vi / ko); AI summarization also responds in the student's language

License

Educational project. Resource content links to official UW pages and is not a substitute for them. For emergencies call 911 or contact SafeCampus.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors