Initial commit: Holiday Travel App with resort comparison, trip management, and multi-provider search

This commit is contained in:
2025-10-29 16:22:35 -04:00
commit 74f8e268c3
167 changed files with 18721 additions and 0 deletions

70
lib/score.ts Normal file
View File

@@ -0,0 +1,70 @@
import type { Deal, TravelPreferences } from "./types";
// Destination feature profiles (0-10 ratings for common destinations)
const DESTINATION_PROFILES: Record<string, TravelPreferences> = {
// Caribbean
"CUN": { beach: 10, pool: 9, golf: 6, spa: 8, food: 8, nightlife: 9, shopping: 7, culture: 5, outdoor: 8, family: 9 },
"PUJ": { beach: 10, pool: 9, golf: 7, spa: 8, food: 7, nightlife: 8, shopping: 6, culture: 4, outdoor: 7, family: 9 },
"MBJ": { beach: 10, pool: 8, golf: 5, spa: 7, food: 8, nightlife: 7, shopping: 5, culture: 6, outdoor: 9, family: 8 },
"NAS": { beach: 9, pool: 8, golf: 6, spa: 6, food: 7, nightlife: 8, shopping: 7, culture: 5, outdoor: 8, family: 8 },
// Europe
"LHR": { beach: 2, pool: 5, golf: 6, spa: 7, food: 9, nightlife: 8, shopping: 10, culture: 10, outdoor: 6, family: 7 },
"CDG": { beach: 1, pool: 6, golf: 5, spa: 8, food: 10, nightlife: 9, shopping: 10, culture: 10, outdoor: 5, family: 7 },
"FCO": { beach: 3, pool: 6, golf: 4, spa: 7, food: 10, nightlife: 8, shopping: 9, culture: 10, outdoor: 7, family: 8 },
"BCN": { beach: 8, pool: 7, golf: 5, spa: 7, food: 9, nightlife: 10, shopping: 8, culture: 9, outdoor: 7, family: 7 },
"AMS": { beach: 1, pool: 5, golf: 4, spa: 6, food: 8, nightlife: 9, shopping: 8, culture: 9, outdoor: 6, family: 7 },
// USA
"LAX": { beach: 8, pool: 7, golf: 7, spa: 8, food: 9, nightlife: 9, shopping: 9, culture: 8, outdoor: 8, family: 7 },
"LAS": { beach: 0, pool: 9, golf: 8, spa: 10, food: 9, nightlife: 10, shopping: 10, culture: 5, outdoor: 5, family: 6 },
"MIA": { beach: 9, pool: 8, golf: 7, spa: 8, food: 8, nightlife: 10, shopping: 9, culture: 7, outdoor: 8, family: 7 },
"MCO": { beach: 5, pool: 10, golf: 8, spa: 7, food: 7, nightlife: 7, shopping: 8, culture: 6, outdoor: 6, family: 10 },
"HNL": { beach: 10, pool: 8, golf: 9, spa: 8, food: 8, nightlife: 7, shopping: 7, culture: 8, outdoor: 10, family: 9 },
// Default fallback
"DEFAULT": { beach: 5, pool: 5, golf: 5, spa: 5, food: 5, nightlife: 5, shopping: 5, culture: 5, outdoor: 5, family: 5 }
};
function calculatePreferenceMatch(deal: Deal, preferences?: TravelPreferences): number {
if (!preferences || !deal.destination) return 0;
const destProfile = DESTINATION_PROFILES[deal.destination.toUpperCase()] || DESTINATION_PROFILES["DEFAULT"];
let matchScore = 0;
let totalWeight = 0;
// Calculate how well the destination matches user preferences
for (const [feature, userRating] of Object.entries(preferences)) {
if (userRating && userRating > 0) {
const destRating = destProfile[feature as keyof TravelPreferences] || 5;
// Higher user rating + higher destination rating = better match
matchScore += (userRating * destRating);
totalWeight += (userRating * 10); // max possible for this feature
}
}
// Normalize to 0-1000 scale
if (totalWeight > 0) {
return (matchScore / totalWeight) * 1000;
}
return 0;
}
export function scoreDeal(d: Deal, preferences?: TravelPreferences): number {
// Very simple scoring: prefer lower price, direct, reasonable nights
let score = 0;
if (typeof d.price === "number") score += Math.max(0, 10000 - d.price);
if (d.stops === 0) score += 500;
if (typeof d.nights === "number") {
const target = 7;
score += Math.max(0, 300 - Math.abs(d.nights - target) * 40);
}
if (d.source.includes("Deals")) score += 150; // curated deal sites
// Add preference matching bonus
const preferenceBonus = calculatePreferenceMatch(d, preferences);
score += preferenceBonus;
return score;
}