Semantic + spatial + timeline search for vessel logs. Every entry is time-stamped and location-stamped. Search by meaning, by proximity, or by date range.
Runs on Cloudflare Workers (free tier) or locally with wrangler dev.
- Semantic search — "good chumming near Cape Edgecumbe" finds entries about successful baiting near that area, even if the exact words don't match
- Spatial search — "what happened within 50km of 56.6, -134.0?" returns entries sorted by distance
- Timeline search — "all maintenance logs from July 2026" returns time-ordered entries filtered by category
- Quick log — POST a single entry with text, category, lat/lon, and timestamp
| Category | Example |
|---|---|
catch |
"32 sockeye, 600 fath on the slack, 45 min soak" |
maintenance |
"Port hydraulic ram weeping — topped off fluid, ordered seal kit" |
weather |
"NW 25 knots, 6-foot swell at the banks. Barometer dropping." |
observation |
"Humpbacks working the tide rip. Bait balls on the sounder at 25 fathoms." |
navigation |
"Anchored in Port Bazant. Good holding in mud. 60 feet." |
# 1. Create the Vectorize index (one time)
npm run create-index
# 2. Deploy to Cloudflare
npm run deploy
# 3. Or run locally
npm run dev{
"text": "Chummed 45 min on the slack. 32 sockeye, 12 pinks. 600 fath.",
"category": "catch",
"lat": 56.6043,
"lon": -134.4120,
"location_name": "Cape Edgecumbe"
}{
"documents": [
{
"id": "log-001",
"text": "Port engine overheating. Shut down, inspected raw water intake. Cleared debris.",
"metadata": {
"timestamp": "2026-07-15T14:30:00Z",
"lat": 56.6043,
"lon": -134.4120,
"category": "maintenance",
"location_name": "Cape Edgecumbe"
}
}
]
}GET /api/search?q=hydraulic+failure&category=maintenance&from=2026-06-01T00:00:00Z&k=20
Params:
q— search query (required)category— filter by category (catch, maintenance, weather, observation, navigation)from— ISO timestamp, entries after thisto— ISO timestamp, entries before thisk— max results (1-50, default 20)
GET /api/nearby?lat=56.6&lon=-134.0&radius=50&k=20
Returns entries within radius km of lat, lon, sorted by distance.
GET /api/timeline?from=2026-07-01T00:00:00Z&to=2026-07-31T23:59:59Z&category=catch&k=50
Returns entries in the time range, most recent first.
{
"entries": 1500,
"model": "@cf/baai/bge-small-en-v1.5",
"endpoints": ["/api/search", "/api/nearby", "/api/timeline", "/api/log", "/api/ingest"]
}- 10M vectors (you'll never hit this with log entries)
- 100K requests/day
- Workers AI embedding: free
# Install wrangler
npm install
# Run locally (connects to Cloudflare for AI + Vectorize)
npx wrangler dev
# Open http://localhost:8787If you have existing fishing logs (CSV, spreadsheet, paper notes), write a script to:
- Parse each entry
- Build the
textfield (concatenate description, conditions, catch info) - Add
timestamp,lat,lon,categoryas metadata - POST to
/api/ingestin batches of 100
Example Python script:
import csv, requests, json
API = "https://your-ship-log.workers.dev/api/ingest"
docs = []
with open('fishing_logs.csv') as f:
for row in csv.DictReader(f):
text = f"{row['description']} | {row['catch_summary']} | {row['conditions']}"
docs.append({
"id": row['id'],
"text": text,
"metadata": {
"timestamp": row['datetime'],
"lat": float(row['latitude']),
"lon": float(row['longitude']),
"category": row.get('category', 'observation'),
"location_name": row.get('location', ''),
}
})
# POST in batches of 100
for i in range(0, len(docs), 100):
batch = docs[i:i+100]
r = requests.post(API, json={"documents": batch})
print(f"Batch {i//100}: {r.json()}")MIT