Major additions: - Multi-state expansion: states table, StateSelector, NationalDashboard, StateHeatmap, CrossStateCompare - Orchestrator services: trace service, error taxonomy, retry manager, proxy rotator - Discovery system: dutchie discovery service, geo validation, city seeding scripts - Analytics infrastructure: analytics v2 routes, brand/pricing/stores intelligence pages - Local development: setup-local.sh starts all 5 services (postgres, backend, cannaiq, findadispo, findagram) - Migrations 037-056: crawler profiles, states, analytics indexes, worker metadata Frontend pages added: - Discovery, ChainsDashboard, IntelligenceBrands, IntelligencePricing, IntelligenceStores - StateHeatmap, CrossStateCompare, SyncInfoPanel Components added: - StateSelector, OrchestratorTraceModal, WorkflowStepper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
163 lines
6.0 KiB
Markdown
163 lines
6.0 KiB
Markdown
# Platform Slug Mapping
|
|
|
|
## Overview
|
|
|
|
To avoid trademark issues in public-facing API URLs, CannaiQ uses neutral two-letter slugs instead of vendor names in route paths.
|
|
|
|
**Important**: The actual `platform` value stored in the database remains the full name (e.g., `'dutchie'`). Only the URL paths use neutral slugs.
|
|
|
|
## Platform Slug Reference
|
|
|
|
| Slug | Platform | DB Value | Status |
|
|
|------|----------|----------|--------|
|
|
| `dt` | Dutchie | `'dutchie'` | Active |
|
|
| `jn` | Jane | `'jane'` | Future |
|
|
| `wm` | Weedmaps | `'weedmaps'` | Future |
|
|
| `lf` | Leafly | `'leafly'` | Future |
|
|
| `tz` | Treez | `'treez'` | Future |
|
|
| `bl` | Blaze | `'blaze'` | Future |
|
|
| `fl` | Flowhub | `'flowhub'` | Future |
|
|
|
|
## API Route Patterns
|
|
|
|
### Discovery Routes
|
|
|
|
```
|
|
/api/discovery/platforms/:platformSlug/locations
|
|
/api/discovery/platforms/:platformSlug/locations/:id
|
|
/api/discovery/platforms/:platformSlug/locations/:id/verify-create
|
|
/api/discovery/platforms/:platformSlug/locations/:id/verify-link
|
|
/api/discovery/platforms/:platformSlug/locations/:id/reject
|
|
/api/discovery/platforms/:platformSlug/locations/:id/unreject
|
|
/api/discovery/platforms/:platformSlug/locations/:id/match-candidates
|
|
/api/discovery/platforms/:platformSlug/cities
|
|
/api/discovery/platforms/:platformSlug/summary
|
|
```
|
|
|
|
### Orchestrator Routes
|
|
|
|
```
|
|
/api/orchestrator/platforms/:platformSlug/promote/:id
|
|
```
|
|
|
|
## Example Usage
|
|
|
|
### Fetch Discovered Locations (Dutchie)
|
|
|
|
```bash
|
|
# Using neutral slug 'dt' instead of 'dutchie'
|
|
curl "https://api.cannaiq.co/api/discovery/platforms/dt/locations?status=discovered&state_code=AZ"
|
|
```
|
|
|
|
### Verify and Create Dispensary
|
|
|
|
```bash
|
|
curl -X POST "https://api.cannaiq.co/api/discovery/platforms/dt/locations/123/verify-create" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"verifiedBy": "admin"}'
|
|
```
|
|
|
|
### Link to Existing Dispensary
|
|
|
|
```bash
|
|
curl -X POST "https://api.cannaiq.co/api/discovery/platforms/dt/locations/123/verify-link" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"dispensaryId": 456, "verifiedBy": "admin"}'
|
|
```
|
|
|
|
### Promote to Crawlable
|
|
|
|
```bash
|
|
curl -X POST "https://api.cannaiq.co/api/orchestrator/platforms/dt/promote/123"
|
|
```
|
|
|
|
### Get Discovery Summary
|
|
|
|
```bash
|
|
curl "https://api.cannaiq.co/api/discovery/platforms/dt/summary"
|
|
```
|
|
|
|
## Migration Guide
|
|
|
|
### Old Routes (DEPRECATED)
|
|
|
|
| Old Route | New Route |
|
|
|-----------|-----------|
|
|
| `/api/discovery/dutchie/locations` | `/api/discovery/platforms/dt/locations` |
|
|
| `/api/discovery/dutchie/locations/:id` | `/api/discovery/platforms/dt/locations/:id` |
|
|
| `/api/discovery/dutchie/locations/:id/verify-create` | `/api/discovery/platforms/dt/locations/:id/verify-create` |
|
|
| `/api/discovery/dutchie/locations/:id/verify-link` | `/api/discovery/platforms/dt/locations/:id/verify-link` |
|
|
| `/api/discovery/dutchie/locations/:id/reject` | `/api/discovery/platforms/dt/locations/:id/reject` |
|
|
| `/api/discovery/dutchie/locations/:id/unreject` | `/api/discovery/platforms/dt/locations/:id/unreject` |
|
|
| `/api/discovery/dutchie/locations/:id/match-candidates` | `/api/discovery/platforms/dt/locations/:id/match-candidates` |
|
|
| `/api/discovery/dutchie/cities` | `/api/discovery/platforms/dt/cities` |
|
|
| `/api/discovery/dutchie/summary` | `/api/discovery/platforms/dt/summary` |
|
|
| `/api/discovery/dutchie/nearby` | `/api/discovery/platforms/dt/nearby` |
|
|
| `/api/discovery/dutchie/geo-stats` | `/api/discovery/platforms/dt/geo-stats` |
|
|
| `/api/discovery/dutchie/locations/:id/validate-geo` | `/api/discovery/platforms/dt/locations/:id/validate-geo` |
|
|
| `/api/orchestrator/dutchie/promote/:id` | `/api/orchestrator/platforms/dt/promote/:id` |
|
|
|
|
### API Client Changes
|
|
|
|
| Old Method | New Method |
|
|
|------------|------------|
|
|
| `getDutchieDiscoverySummary()` | `getPlatformDiscoverySummary('dt')` |
|
|
| `getDutchieDiscoveryLocations(params)` | `getPlatformDiscoveryLocations('dt', params)` |
|
|
| `getDutchieDiscoveryLocation(id)` | `getPlatformDiscoveryLocation('dt', id)` |
|
|
| `verifyCreateDutchieLocation(id)` | `verifyCreatePlatformLocation('dt', id)` |
|
|
| `verifyLinkDutchieLocation(id, dispId)` | `verifyLinkPlatformLocation('dt', id, dispId)` |
|
|
| `rejectDutchieLocation(id, reason)` | `rejectPlatformLocation('dt', id, reason)` |
|
|
| `unrejectDutchieLocation(id)` | `unrejectPlatformLocation('dt', id)` |
|
|
| `getDutchieLocationMatchCandidates(id)` | `getPlatformLocationMatchCandidates('dt', id)` |
|
|
| `getDutchieDiscoveryCities(params)` | `getPlatformDiscoveryCities('dt', params)` |
|
|
| `getDutchieNearbyLocations(lat, lon)` | `getPlatformNearbyLocations('dt', lat, lon)` |
|
|
| `getDutchieGeoStats()` | `getPlatformGeoStats('dt')` |
|
|
| `validateDutchieLocationGeo(id)` | `validatePlatformLocationGeo('dt', id)` |
|
|
| `promoteDutchieDiscoveryLocation(id)` | `promotePlatformDiscoveryLocation('dt', id)` |
|
|
|
|
## Adding New Platforms
|
|
|
|
When adding support for a new platform:
|
|
|
|
1. **Assign a slug**: Choose a two-letter neutral slug
|
|
2. **Update validation**: Add to `validPlatforms` array in `backend/src/index.ts`
|
|
3. **Create routes**: Implement platform-specific discovery routes
|
|
4. **Update docs**: Add to this document
|
|
|
|
### Example: Adding Jane Support
|
|
|
|
```typescript
|
|
// backend/src/index.ts
|
|
const validPlatforms = ['dt', 'jn']; // Add 'jn' for Jane
|
|
|
|
// Create Jane discovery routes
|
|
const jnDiscoveryRoutes = createJaneDiscoveryRoutes(getPool());
|
|
app.use('/api/discovery/platforms/jn', jnDiscoveryRoutes);
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
The `platform` column in discovery tables stores the **full platform name** (not the slug):
|
|
|
|
```sql
|
|
-- dutchie_discovery_locations table
|
|
SELECT * FROM dutchie_discovery_locations WHERE platform = 'dutchie';
|
|
|
|
-- dutchie_discovery_cities table
|
|
SELECT * FROM dutchie_discovery_cities WHERE platform = 'dutchie';
|
|
```
|
|
|
|
This keeps the database schema clean and allows for future renaming of URL slugs without database migrations.
|
|
|
|
## Safe Naming Conventions
|
|
|
|
### DO
|
|
- Use neutral two-letter slugs in URLs: `dt`, `jn`, `wm`
|
|
- Use generic terms in user-facing text: "platform", "menu provider"
|
|
- Store full platform names in the database for clarity
|
|
|
|
### DON'T
|
|
- Use trademarked names in URL paths
|
|
- Use vendor names in public-facing error messages
|
|
- Expose vendor-specific identifiers in consumer APIs
|