Adventa
Decision intelligence for weekend escapes — discover, understand, and book your next adventure in under two minutes.
Inspiration
Every Friday, the same ritual plays out across Maharashtra: a dozen browser tabs, three WhatsApp groups, two weather apps, and a spreadsheet of "places near Pune." The information exists — treks, forts, waterfalls, camping, water sports — but it's scattered, undated, and un-rankable. You can find a hundred listings; you can't find the right one for this weekend, from your city, within your budget, that still has seats.
Travel marketplaces solve search. Nobody solves the decision. We built Adventa because the hard part of a weekend trip isn't booking — it's the confidence that you picked correctly before you've spent a rupee. We wanted a product that doesn't just list options; it defends its recommendation in plain language.
What it does
Adventa turns a four-field intent — vibe, budget, origin city, duration — into a ranked shortlist of weekend adventures, each with an honest, fully explainable Weekend Confidence Score.
- Plan → You tell us the vibe (adrenaline, nature, or chill), your budget, where you're starting from, and how long you've got.
- Discover → We score every adventure across six independent dimensions and rank them. The top result is surfaced as a "best match" hero with its reasoning attached.
- Adventure detail → A transparent score breakdown shows why this trip ranks where it does — no black box.
- Checkout → A validated booking flow with a confirmation email.
- Operator dashboard → A live view of occupancy and a data-driven dynamic-pricing suggestion for the people running the trips.
The scoring is the product. Each adventure earns up to 20 points on each of six independent dimensions:
The scoring is the product. Each adventure earns up to **20 points** on each of six independent dimensions:
Score = Budget + Travel + Weather + Difficulty + Availability + Vibe Maximum Score = 120
We then normalize the raw score into an honest confidence percentage:
Weekend Confidence = (Score / 120) × 100%
A few of the dimensions, concretely:
- **Travel fit** blends real door-to-door travel time from your chosen origin with how well the trip's length matches your requested duration—both answer *"Will this fit my logistics?"*
Travel Score = (Clamp(22 − 2 × TravelHours, 6, 20) + Duration Fit) / 2
where **TravelHours** is the estimated travel time from your city.
- **Availability fit** reads live seat ratios:
- ≥ 50% seats available → full marks
- Nearly sold out → lower score
- Sold out → 0 points
This ensures we never confidently recommend a trip that can't actually be booked.
- **Vibe fit** scores only the adventure category (for example, *Adrenaline* rewards Water Sports and Fort Treks), while **Difficulty** is scored separately so the same signal isn't counted twice.
Every recommendation also includes plain-English explanations such as:
- "Stays within your ₹8,000 budget."
- "Travel time is manageable at about 3 hours."
- "12 seats are still open on the next date."
The user sees both the reasoning and the score—not just a black-box recommendation.
Availability fit reads live seat ratios: $\ge 50%$ seats open scores full marks, a near-sold-out date scores low, sold-out scores zero — so we never confidently recommend a trip you can't actually book.
Vibe fit scores category only (e.g. adrenaline rewards Water Sports and Forts), deliberately leaving difficulty to its own axis so we don't double-count the same signal.
Crucially, every score ships with human-readable reasons — "Stays within your ₹8,000 budget," "Travel time is manageable at about 3 hours," "12 seats are still open on the next date." The user sees the math and the sentence.
How we built it
Next.js 15 (App Router) + React 19 + TypeScript, server-rendered end to end. The recommendation engine runs on the server (
lib/recommendations.ts) — pure, deterministic, and unit-testable, with zero client-side scoring.PostgreSQL + Prisma 6 model the real domain:
Operator
↓
Adventure
↓
AdventureDate
↓
Booking
Each date carries live slotsLeft, weatherRisk, demandSignal, and a dynamicPrice.
Resilient data layer. Every read and write goes through a
withFallbackwrapper: if the database is unreachable or unmigrated, the app transparently serves an in-memory demo store so a live demo never dies on a cold connection. Writes are stricter than reads — a genuine capacity conflict must always surface, never be silently masked by stale in-memory counts.Safe concurrent booking. Seats are reserved with optimistic concurrency: a conditional
updateManythat only succeeds if
$$ \texttt{slotsLeft} \ge \texttt{slotsBooked}, $$
atomically decrementing the count and bumping a version counter inside a transaction. If two people grab the last slot simultaneously, exactly one wins and the other gets a clean BookingConflictError — no overselling.
Transactional email via Resend, designed to be optional: with no API key the sender is a no-op, so the booking flow never depends on email being configured.
Deployed on Vercel, with
prisma generatewired into bothpostinstalland the build so the generated client always matches the schema regardless of build-cache state.
Challenges we ran into
Making a score trustworthy, not just plausible. Our first scoring pass double-counted difficulty inside the vibe axis, quietly inflating adrenaline trips. We split vibe (category) from difficulty (skill match) into independent dimensions so each signal is counted exactly once — and the normalization to $0\text{–}100$ stays honest at $120$ max.
The last-seat race condition. Naively reading then writing slot counts oversells under concurrency. Moving to a single conditional, atomic
updateManyguarded by a version field — and refusing to fall back to in-memory data on a real capacity conflict — was the difference between a demo and a bookable product.A stale Prisma client on Vercel. A cached build shipped a generated client that predated a schema change, breaking the type-check on a field that genuinely existed. We fixed it at the root by regenerating the client on every install/build rather than trusting the cache.
Graceful degradation without lying to the user. Deciding what may fall back (reads) and what must hard-fail (a capacity conflict) forced us to be precise about which errors are recoverable.
Accomplishments that we're proud of
A recommendation engine that is fully explainable by construction — every percentage decomposes into six named, bounded numbers and a plain-English justification.
Zero-oversell booking under real concurrency, backed by a database transaction and optimistic locking.
A product that degrades gracefully: database down, email unconfigured, build cache stale — the core experience survives all three.
Genuine two-sided value: travellers get confident decisions; operators get an occupancy view and a dynamic-pricing suggestion off the same live data.
What we learned
Explainability is a feature, not a footnote. Users trusted a 78% they could read far more than a 94% they couldn't.
Concurrency is where MVPs quietly break. The cheapest insurance — a conditional atomic update plus a version counter — prevents the most embarrassing failure (double-booked seats).
Design your fallbacks per-operation. "Always fall back" and "never fall back" are both wrong; reads and writes need different rules.
Treat the build like production. A green local build means nothing if the deploy environment regenerates artifacts differently.
What's next for Adventa
Live weather & demand ingestion — replace seeded
weatherRisk/demandSignalwith real forecast and booking-velocity feeds so the score updates in real time.Real payments & calendar holds — payment-gateway capture with time-boxed seat reservations during checkout.
Personalized learning-to-rank — weight the six dimensions per user from their booking history instead of using fixed weights.
Operator self-serve — let operators publish adventures, manage dates, and accept the dynamic-pricing suggestions in one click.
Geographic expansion beyond Maharashtra, with origin-aware travel scoring for every new region.
Built With
- est-lint
- javascript
- json
- next.js
- pip
- python
- react
- typescript
Log in or sign up for Devpost to join the conversation.