Haalarikone is 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
- 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)
- Database: Supabase (PostgreSQL)
- Search: AI-powered query understanding (Anthropic Claude) + deterministic filtering + semantic search (Upstash)
- AI/ML: Vercel AI SDK with Anthropic Claude 3 Haiku
- Rate Limiting: Upstash Redis
- Email: Resend (for feedback forms)
- Analytics: Databuddy
- Testing: Playwright
- Package Manager: pnpm
- Deployment: Vercel
Before you begin, ensure you have the following installed:
- Node.js (LTS version recommended)
- pnpm (package manager)
- Git
git clone https://github.com/YOUR_USERNAME/haalarikone.git
cd haalarikonepnpm installCreate a .env.local file in the root directory with the following variables:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
# Upstash Search
UPSTASH_SEARCH_REST_URL=your_upstash_search_url
UPSTASH_SEARCH_REST_TOKEN=your_upstash_search_token
# Upstash Redis (for rate limiting)
UPSTASH_REDIS_REST_URL=your_upstash_redis_url
UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_token
# Anthropic (for AI-powered search)
ANTHROPIC_API_KEY=your_anthropic_api_key
# Resend (feedback form)
RESEND_API_KEY=your_resend_api_key
FEEDBACK_EMAIL_TO=email_to_send_feedback_toNote:
- Set up your own Supabase and Upstash instances
- Anthropic API key is required for AI-powered search functionality
- Resend API key is optional (only needed for feedback form functionality)
- The app uses
localePrefix: 'as-needed'- Finnish (default) has no prefix, other locales use/enor/sv
pnpm run devOpen http://localhost:3000 in your browser to see the app.
pnpm testTo view the test report:
pnpm exec playwright show-reportstudent-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
│ ├── (auth-pages)/ # Authentication pages (sign-in, sign-up, etc.)
│ ├── api/ # API routes (search, translate-path, upsert)
│ └── auth/ # Auth callbacks
├── components/ # React components
│ ├── ui/ # Reusable UI components (Radix UI)
│ ├── search-modal.tsx # Search functionality
│ ├── 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
│ ├── query-understanding.ts # AI-powered query parsing
│ ├── deterministic-filter.ts # Exact filter matching
│ ├── semantic-search.ts # Vector/semantic search fallback
│ ├── semantic-ranking.ts # Result ranking by relevance
│ ├── 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
│ └── supabase/ # Supabase client utilities
└── tests/ # Playwright tests
pnpm run dev- Start development server with Turbopackpnpm run build- Build production bundle with Turbopackpnpm run start- Start production serverpnpm test- Run Playwright testspnpm exec playwright show-report- View test report after running tests
- Multi-language Support: Finnish (default), English, and Swedish
- AI-Powered Search: Intelligent query understanding with exact result counts
- 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 uses a hybrid approach combining AI-powered query understanding with deterministic filtering and semantic search fallback.
flowchart TD
A[User Query] --> B[Query Understanding AI]
B --> C{Is Gibberish?}
C -->|Yes| D[Return Empty Results]
C -->|No| E[Extract Filters]
E --> F[Apply Deterministic Filters]
F --> G{Exact Matches Found?}
G -->|Yes| H[Return Exact Results]
G -->|No| I[Semantic Search Fallback]
I --> J[Filter Semantic Results]
J --> K{Filtered Results?}
K -->|Yes| L[Return Filtered Semantic Results]
K -->|No| M[Return Empty]
H --> N{Semantic Query?}
N -->|Yes| O[Rank by Semantic Relevance]
N -->|No| P[Sort Deterministically]
O --> Q[Return Final Results]
P --> Q
L --> Q
-
Query Understanding (AI)
- Uses Anthropic Claude 3 Haiku to parse natural language queries
- Extracts structured filters: color, area, field, school
- Handles Finnish morphology (plural forms, case endings, genitive case)
- Detects gibberish queries for early exit
- Returns remaining semantic query text for ranking
-
Deterministic Filtering
- Applies exact filters on local JSON dataset (~6000 records)
- Guarantees exact result counts (no arbitrary limits)
- Filters by: color (with variant matching), area, field, school
- Fast in-memory filtering with caching
-
Semantic Search Fallback
- Only triggered when exact filters return 0 results
- Uses Upstash Search for vector/semantic search
- Applies same filters to semantic results
- Ensures semantic results still match filter criteria
-
Ranking
- If semantic query exists, ranks results by keyword relevance
- Otherwise, sorts deterministically (by school, then field)
- Exact matches are always prioritized
- Exact Counts: Always returns complete result sets, not limited to top N
- Intelligent Parsing: Understands natural language queries in Finnish, English, Swedish
- Fast Performance: Deterministic filtering is ~10-50ms, AI parsing ~200-400ms
- Fallback Safety: Semantic search only when needed, still respects filters
- TypeScript: Strict mode enabled
- Formatting: Prettier (configured)
- Linting: TypeScript compiler checks
- Comments: Minimal comments (per project preferences)
- Use functional components with TypeScript
- Prefer server components when possible (Next.js App Router)
- Client components should be marked with
"use client"directive - Use path aliases (
@/) for imports
- All user-facing text should be in translation files (
messages/*.json) - Use
useTranslationshook for client components - Use
getTranslationsfor server components - Route segments are automatically translated via
route-translations.ts - Entity slugs (universities, fields, colors) are translated via
slug-translations.ts
- Use Tailwind CSS utility classes
- Custom components in
components/ui/use Radix UI primitives - Theme support via
next-themes - Responsive breakpoints:
sm:,md:,lg:,xl:
Tests are written using Playwright and located in the tests/ directory. The test configuration runs tests against Chromium, Firefox, and WebKit.
To run tests in a specific browser:
pnpm exec playwright test --project=chromiumWe 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