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> |
||
|---|---|---|
| .. | ||
| 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
listOpportunityRankedfrom@features/api/src/entities/destination. ThecomputedOpportunityScorefield 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) — seestrategies.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 }