Finland's easiest overall database – a unique way to explore Finnish student culture through colors.
Find out what color overall a student in a specific field wears.
-
What it does:
It helps you identify students' fields based on their signature overall colors. -
Explore culture:
Discover and learn about the colorful traditions of Finnish student life.
Check out the live project at: haalarikone.fi
Live analytics: https://app.databuddy.cc/public/Uu3N9TuBuUAa3wAS4pHNw
- Framework: Next.js 15 (App Router) with React 18
- Language: TypeScript
- Styling: Tailwind CSS with Radix UI components (Shadcn/ui)
- Internationalization: next-intl (Finnish, English, Swedish)
- Search: Deterministic in-memory filtering + fuzzy ranking, with AI fallback only on zero-result deterministic queries
- Search logging: Turso (libSQL) + Drizzle ORM
- AI/ML: Vercel AI SDK with Anthropic Claude 3 Haiku (zero-result fallback only)
- Feedback: Discord webhook
- Analytics: Databuddy
- Testing: Vitest only — search API + advanced filters against real
data/overall_data.json; CI on non-draft PRs - Package Manager: pnpm
- Deployment: Vercel
- Live Site: haalarikone.fi
- Contributing Guidelines: CONTRIBUTING.md
- Issue Tracker:
Issueson GitHub - License: LICENSE
student-overall-app/
├── app/ # Next.js App Router pages
│ ├── [locale]/ # Localized routes (fi, en, sv)
│ │ ├── ala/ # Field pages
│ │ ├── alue/ # Area pages
│ │ ├── blog/ # Blog posts
│ │ ├── haalari/ # Overall detail pages
│ │ ├── oppilaitos/ # University pages
│ │ ├── vari/ # Color pages
│ │ └── page.tsx # Home page
│ ├── api/ # API routes (search)
├── components/ # React components
│ ├── ui/ # Reusable UI components (Radix UI)
│ ├── search-container.tsx # Home/hub search
│ ├── language-switcher.tsx # i18n language switcher
│ ├── theme-switcher.tsx # Dark/light mode toggle
│ └── ... # Feature components
├── content/ # Blog post content (JSON)
├── data/ # Static data files
├── i18n/ # Internationalization config
│ ├── routing.ts # Route configuration
│ └── request.ts # Request locale handling
├── lib/ # Utility functions and helpers
│ ├── route-translations.ts # Route segment translations
│ ├── slug-translations.ts # Entity slug translations
│ ├── translate-path-client.ts # Client-side path translation
│ ├── build-search-response.ts # Deterministic in-memory fuzzy search
│ ├── university-filters.ts # Advanced filter matching (shared UI/API)
│ ├── query-understanding.ts # AI fallback query parsing
│ ├── load-color-data.ts # Dynamic color data from JSON
│ └── ... # Other utilities
├── messages/ # Translation files (fi.json, en.json, sv.json)
├── types/ # TypeScript type definitions
└── utils/ # Utility functions
- Multi-language Support: Finnish (default), English, and Swedish
- Deterministic Search: In-memory fuzzy search with exact color/filter matching
- Route Translation: Automatic translation of route segments and slugs
- Blog System: Static blog posts with multi-language support
- Theme Support: Dark and light mode
- Responsive Design: Mobile-first approach with Tailwind CSS
- SEO Optimized: Dynamic metadata, sitemap, and structured data
The search system is deterministic-first and fully in-memory by default. Text search filters against local university and color data, then ranks candidates with Fuse fuzzy search. Advanced filters (color / area / field / school) are applied client-side via shared matching in lib/university-filters.ts. AI parsing is used only if deterministic text search returns 0 results.
flowchart TD
A[User Query] --> B[Load In-Memory Data]
B --> C[Build Deterministic Filters]
C --> D[Apply Deterministic Filtering]
D --> E[Rank with Fuzzy Search]
E --> F{Any Results?}
F -->|Yes| G[Return Deterministic Results]
F -->|No| H[AI Query Understanding Fallback]
H --> I[Re-run Deterministic Filtering + Ranking]
I --> J[Return Fallback Results]
-
Deterministic Query Interpretation
- Extracts likely filters (color, area, school, field, organization) from the query using local in-memory values.
- Detects color aliases directly from color metadata loaded from local data files.
-
Deterministic Filtering
- Applies all extracted filters over the local dataset in memory.
- Includes color-base matching and organization/slug matching.
- Uses a field-relaxation fallback only when
organization + fieldcombination would otherwise produce no results.
-
Fuzzy Ranking
- Ranks filtered candidates with Fuse.js.
- Prioritizes exact organization/slug token matches before fuzzy tie-breaking.
- Includes color text fields in fuzzy keys so color wording contributes to ranking quality.
-
Color Variant Equivalence
- Color singular/plural and common aliases are treated as equivalent in fuzzy matching.
- Examples (all base colors): valkoinen ↔ valkoiset, musta ↔ mustat, punainen ↔ punaiset, sininen ↔ siniset, vihreä ↔ vihreät, keltainen ↔ keltaiset, oranssi ↔ oranssit, violetti ↔ violetit, pinkki ↔ pinkit, harmaa ↔ harmaat, ruskea ↔ ruskeat, turkoosi ↔ turkoosit.
-
AI Fallback (Only On Miss)
- If deterministic search returns zero results, API route calls AI query understanding and retries deterministic filtering/ranking.
- This keeps AI usage low while preserving recovery for ambiguous or noisy queries.
- Lower Cost: Most queries are resolved without AI calls.
- Predictable Results: One deterministic pipeline handles normal search traffic.
- Natural Color Queries: Finnish singular/plural color forms match reliably.
- Fast Runtime: Local in-memory filtering + fuzzy ranking avoids network/model latency on common paths.
Completed search intents are stored in Turso (libSQL) via Drizzle. Each row is a full criteria snapshot: text query plus optional advanced filters.
Env vars (local .env / .env.local, and Cloudflare Worker secrets):
TURSO_DATABASE_URL— Turso database URLTURSO_AUTH_TOKEN— Turso auth token
# apply schema to Turso
pnpm db:push
# Cloudflare production secrets
npx wrangler secret put TURSO_DATABASE_URL
npx wrangler secret put TURSO_AUTH_TOKENLogging runs server-side inside POST /api/search (fire-and-forget insert). Missing env or insert failures are silent and never affect search UX.
Primary suite is Vitest (search + filters + search-log). Turso pipeline + Playwright search-log are local-only (same TURSO_* as the app; skip if unset; not in CI).
| Suite | Role |
|---|---|
app/api/search/route.test.ts |
Text search API integration (AI mocked) |
lib/university-filters.test.ts |
Advanced filters (+ text ∩ filters) |
lib/reconcile-field-organization.test.ts |
Guild vs field reconcile unit tests |
lib/log-search-pipeline.test.ts |
Local: insertSearchLog → Turso |
e2e/search-log.spec.ts |
Local Playwright: settled search → Turso |
pnpm test
pnpm test:watch
pnpm test:e2e # local; needs TURSO_* + Chromium (`pnpm exec playwright install chromium`)Full policy: CONTRIBUTING.md.
We welcome contributions! Please see CONTRIBUTING.md for guidelines on how to contribute to this project.
For any questions or feedback, feel free to reach out:
- Email: savonen.emppu@gmail.com
- GitHub: @valtterisa
See LICENSE file for details.
Made by @valtterisa