{ "name": "Bettersight โ€” Embedding + Comparable Matching (Sunday night)", "nodes": [ { "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 23 * * 0" }] } }, "id": "schedule-trigger", "name": "Schedule Trigger (Sunday 23:00)", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [0, 0] }, { "parameters": { "method": "GET", "url": "={{ $env.POCKETBASE_URL }}/api/collections/tenants/records", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "filter", "value": "status = 'active' || status = 'trial'" }, { "name": "perPage", "value": "200" } ] }, "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth" }, "id": "list-active-tenants", "name": "List active tenants (PocketBase)", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [220, 0], "credentials": { "httpHeaderAuth": { "id": "pocketbase-admin-auth", "name": "PocketBase Admin Auth" } } }, { "parameters": { "fieldToSplitOut": "items" }, "id": "split-tenants", "name": "Split into items", "type": "n8n-nodes-base.splitOut", "typeVersion": 1, "position": [440, 0] }, { "parameters": { "batchSize": 1 }, "id": "loop-tenants", "name": "Loop Over Tenants", "type": "n8n-nodes-base.splitInBatches", "typeVersion": 3, "position": [660, 0] }, { "parameters": { "method": "POST", "url": "={{ $env.BETTERSIGHT_API_BASE_URL }}/internal/embed-products/{{ $json.id }}", "sendHeaders": true, "headerParameters": { "parameters": [{ "name": "X-API-Key", "value": "={{ $env.API_SECRET_KEY }}" }] } }, "id": "embed-products", "name": "POST /internal/embed-products/{tenant_id}", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [880, 0] }, { "parameters": { "method": "POST", "url": "={{ $env.BETTERSIGHT_API_BASE_URL }}/internal/match-comparable/{{ $('Loop Over Tenants').item.json.id }}", "sendHeaders": true, "headerParameters": { "parameters": [{ "name": "X-API-Key", "value": "={{ $env.API_SECRET_KEY }}" }] } }, "id": "match-comparable", "name": "POST /internal/match-comparable/{tenant_id}", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1100, 0] } ], "connections": { "Schedule Trigger (Sunday 23:00)": { "main": [[{ "node": "List active tenants (PocketBase)", "type": "main", "index": 0 }]] }, "List active tenants (PocketBase)": { "main": [[{ "node": "Split into items", "type": "main", "index": 0 }]] }, "Split into items": { "main": [[{ "node": "Loop Over Tenants", "type": "main", "index": 0 }]] }, "Loop Over Tenants": { "main": [ [], [{ "node": "POST /internal/embed-products/{tenant_id}", "type": "main", "index": 0 }] ] }, "POST /internal/embed-products/{tenant_id}": { "main": [[{ "node": "POST /internal/match-comparable/{tenant_id}", "type": "main", "index": 0 }]] }, "POST /internal/match-comparable/{tenant_id}": { "main": [[{ "node": "Loop Over Tenants", "type": "main", "index": 0 }]] } }, "active": false, "settings": { "executionOrder": "v1" }, "meta": { "description": "CLAUDE.md ยง19 Phase 6 task 51 โ€” Sunday night, per active tenant: embed any un-embedded competitor products AND client_trips (POST /internal/embed-products), then run semantic comparable-trip matching (POST /internal/match-comparable). Order matters โ€” match-comparable needs both sides already embedded. client_trips itself is populated continuously by every /research submission (embedding_service.save_client_trip()), not by this workflow. Not yet imported/run against a live n8n instance." } }