lilith-platform.live/codebase/@packages/@lilith/tour-optimizer
Natalie 02483204fd infra: repoint @lilith npm registry + Forgejo from dead black to DO cocotte-forge; serve /photos from local disk
black/apricot homelan died 2026-06-27. Point everything at the DO store tier:
- @lilith npm registry: forge.black.lan/npm.black.lan -> cocotte-forge Verdaccio
  (134.199.243.61:4873) across bunfig.toml scopes, all deploy.sh .npmrc writers,
  and package.json publishConfig.
- Forgejo URL (git/CI): forge.black.lan -> 134.199.243.61:3000 / :2222.
- quinn.www prod.conf /photos: was proxy_pass to dead black_photos (black:8081);
  now served from local disk (root /var/www/quinn.www/dist). Prevents a future
  deploy from re-breaking photos. (Phase G: repoint to DO Spaces/CDN later.)

Interim bare-IP endpoints; switch to named uvlava infranet hosts once live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:09:33 -04:00
..
data
src
package.json
README.md
tsconfig.json
tsup.config.ts

@lilith/tour-optimizer

Prize-collecting tour-route optimiser for Quinn's touring schedule.

Problem shape

This is the Orienteering Problem (prize-collecting TSP), not classical TSP: given a date budget and a starting depot, pick a subset of candidate cities and a visit order that maximises collected prizes net of travel cost. Classical TSP solvers visit every node; this solver decides which nodes to skip in exchange for the time savings.

Inputs

  • Candidates: typically the output of listOpportunityRanked from @features/api/src/entities/destination. The computedOpportunityScore field is consumed as the per-node prize.
  • Coordinates: destinations.lat / destinations.lon (populated by the geocoding backfill).
  • Window: { start, end } ISO dates defining the tour budget.
  • Homebase: the depot — round-trip start and end.
  • Strategy: one of six presets (max-exposure, max-margin, niche-dominance, permissive-only, expand-from-anchor, edit-existing) — see strategies.ts.

Solver

Pure TypeScript. Greedy insertion seeded by prize/cost ratio, then 2-opt swap refinement under a wall-clock budget. For n ≤ 200 candidates this converges in well under a second on commodity hardware.

OR-Tools / Concorde / python-tsp were considered and rejected — see the plan file at ~/.claude/plans/you-can-use-our-atomic-beaver.md for the trade-off matrix.

API

import { optimizeTour } from '@lilith/tour-optimizer';

const result = await optimizeTour({
  candidates,            // OpportunityRankedRow[] with lat/lon
  homebase: { slug: 'berkeley', lat: 37.8716, lon: -122.2727 },
  window: { start: '2026-06-01', end: '2026-06-30' },
  strategy: 'max-exposure',
  maxStops: 6,
});
// → { visits, totalPrize, totalTravelHours, skipped }