{"id":50932338,"url":"https://github.com/im-codebreaker/brand-radar","last_synced_at":"2026-06-17T05:31:00.964Z","repository":{"id":359654137,"uuid":"1228205291","full_name":"im-codebreaker/brand-radar","owner":"im-codebreaker","description":"Self-hosted brand intelligence pipeline to discover and track niche brands across Reddit, Grailed and forums. Node.js · PostgreSQL · Telegram.","archived":false,"fork":false,"pushed_at":"2026-05-22T19:26:59.000Z","size":434,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T22:29:17.831Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/im-codebreaker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-03T18:24:31.000Z","updated_at":"2026-05-22T19:27:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/im-codebreaker/brand-radar","commit_stats":null,"previous_names":["im-codebreaker/brand-radar"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/im-codebreaker/brand-radar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/im-codebreaker%2Fbrand-radar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/im-codebreaker%2Fbrand-radar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/im-codebreaker%2Fbrand-radar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/im-codebreaker%2Fbrand-radar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/im-codebreaker","download_url":"https://codeload.github.com/im-codebreaker/brand-radar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/im-codebreaker%2Fbrand-radar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34435978,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-17T05:31:00.878Z","updated_at":"2026-06-17T05:31:00.955Z","avatar_url":"https://github.com/im-codebreaker.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Brand Radar\n\n\u003e **Discover emerging brands before everyone else.**\n\nA pipeline-driven, event-sourced intelligence platform that discovers, resolves, and ranks emerging fashion and perfume brands from social platforms and the web — with full traceability from discovery to insight.\n\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n\n---\n\n## Why Brand Radar?\n\n### The Problem\nTrend agencies, fashion scouts, and brand intelligence teams manually track emerging brands across Instagram, TikTok, Reddit, and niche forums. It's **slow, expensive, and incomplete**.\n\n### The Solution\nBrand Radar automates discovery, resolution, and scoring with a fully traceable pipeline:\n\n- ✅ **Event-sourced from day one** — every pipeline action is recorded, replay any stage at any time\n- ✅ **Idempotent workers** — safe retries, no duplicate processing, cost-controlled\n- ✅ **Production-ready** — built for scale with Drizzle, BullMQ, Meilisearch, and pgvector\n- ✅ **Deterministic before AI** — core pipeline works without AI; embeddings and classification are optional acceleration\n- ✅ **Full traceability** — `trace_id` propagation from discovery to scoring, debug any entity's full lineage\n\n### Why Not Just Use...?\n\n| Alternative | Why Brand Radar is Different |\n|-------------|------------------------------|\n| **Manual tracking** | Automated discovery across 4+ sources, processes 1000+ brands/day |\n| **Generic web scrapers** | Purpose-built for brand intelligence with entity resolution, anti-bot stealth, and quality scoring |\n| **Social listening tools** | Goes beyond mentions — resolves entities, tracks ecommerce signals, scores uniqueness |\n| **Build it yourself** | Production-ready with event sourcing, idempotency, cost governance, and backfill engine built in |\n\n---\n\n## Architecture Highlights\n\n### Event-Sourced Core\nEvery mutation emits to the `system_events` backbone. Never lose context. Always reproducible.\n\n```\nDiscovery → Extraction → Normalization → Resolution → Enrichment → Scoring → Indexing\n     ↓          ↓              ↓              ↓            ↓           ↓          ↓\n  event      event          event          event        event       event      event\n```\n\nEvery stage:\n- ✅ Checks `processed_jobs` for idempotency\n- ✅ Emits to `system_events` with `trace_id` + `pipeline_version`\n- ✅ Propagates trace context to downstream jobs\n- ✅ Safe to retry, replay, or backfill\n\n### Pipeline Versioning\nChange your pipeline logic without losing history. The `pipeline_versions` table tracks DAG definitions. Run v1 and v2 in parallel, compare outputs, then cutover.\n\n### Cost Governance\nAI is expensive. The `cost_events` table tracks every OpenAI/proxy call. Daily budgets prevent runaway costs. AI enrichment is **always async, always cost-gated**.\n\n### Data Quality First\nThe `data_quality_scores` table tracks completeness, freshness, consistency, and source reliability per entity. Quality gates scoring — stale data scores lower.\n\n---\n\n## Quick Start\n\n### Prerequisites\n- **Node.js** 22+ and **pnpm** 9+\n- **Docker** 24+ and **Docker Compose** 2.20+\n\n### 1. Clone \u0026 Install\n\n```bash\ngit clone git@github.com:im-codebreaker/brand-radar.git\ncd brand-radar\npnpm install\n```\n\n### 2. Start Full Stack with Docker\n\n```bash\ndocker compose up -d\n```\n\nThis starts the complete stack with **Traefik** as reverse proxy:\n- **PostgreSQL 16** with pgvector (port 5432)\n- **Redis 7** with appendonly persistence (port 6379)\n- **Meilisearch v1.10** for full-text search (port 7700)\n- **API** (Fastify) via Traefik → http://localhost/api\n- **Web** (Vue 3) via Traefik → http://localhost\n- **Workers** (BullMQ) background processing\n- **Scheduler** cron-based jobs\n- **Traefik Dashboard** → http://localhost:8080\n\n**Access the application:**\n- **Frontend:** http://localhost\n- **API:** http://localhost/api\n- **API Health:** http://localhost/api/health\n- **Traefik Dashboard:** http://localhost:8080\n\n### 3. Setup Database\n\n```bash\n# Access API container\ndocker compose exec api sh\n\n# Run migrations and seed\npnpm db:push        # Create tables from Drizzle schema\npnpm db:seed        # Seed demo brands and categories\n\n# Exit container\nexit\n```\n\n### 4. Verify Setup\n\n```bash\n# Check API health\ncurl http://localhost/api/health\n\n# Check Traefik dashboard\nopen http://localhost:8080\n\n# Check queue status\ndocker compose exec redis redis-cli\n\u003e LLEN bull:discovery:wait\n\n# View logs\ndocker compose logs -f api\ndocker compose logs -f workers\n```\n\n**✅ Done! Access the application at http://localhost**\n\n---\n\n## Alternative: Local Development (Host Run)\n\nIf you prefer running apps on your host with hot reload:\n\n### 1. Start Infrastructure Only\n\n```bash\ndocker compose up -d postgres redis meilisearch\n```\n\n### 2. Configure Environment\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env`:\n\n```bash\nDATABASE_URL=postgresql://postgres:postgres@localhost:5432/brand_radar\nREDIS_URL=redis://localhost:6379\nMEILISEARCH_URL=http://localhost:7700\nACTIVE_PIPELINE_VERSION=1.0.0\n```\n\n### 3. Setup Database\n\n```bash\npnpm db:push        # Create tables\npnpm db:seed        # Seed data\n```\n\n### 4. Start Development\n\n```bash\npnpm dev\n```\n\nThis starts all apps in parallel:\n- **API** (Fastify) → http://localhost:3000\n- **Web** (Vue 3) → http://localhost:5173\n- **Workers** (BullMQ) → background processing\n- **Scheduler** → cron-based discovery jobs\n\n**Access:**\n- **Frontend:** http://localhost:5173\n- **API:** http://localhost:3000\n- **API Health:** http://localhost:3000/health\n\n---\n\n## Tech Stack\n\n### Core\n\n| Layer | Technology | Why |\n|-------|-----------|-----|\n| **Frontend** | Vue 3.5 + Vite 7 + Pinia + Tailwind v4 | Reactive, fast, composable |\n| **API** | Fastify 5 + Zod + Drizzle | Type-safe, fast, clean architecture |\n| **Workers** | BullMQ + Playwright + Redis | Reliable job queues, stealth scraping |\n| **Database** | PostgreSQL 16 + pgvector | ACID + vector embeddings |\n| **Search** | Meilisearch | Fast, typo-tolerant, faceted search |\n| **Cache** | Redis 7 (appendonly) | Job queues + rate limiting |\n| **Storage** | S3/MinIO | Immutable raw HTML/JSON for replay |\n| **Auth** | better-auth | Session-based, extensible |\n\n### Phase 2 (AI)\n\n| Layer | Technology | Why |\n|-------|-----------|-----|\n| **Embeddings** | OpenAI `text-embedding-3-small` | 1536-dim vectors, cost-efficient |\n| **Semantic Search** | pgvector HNSW index | Fast cosine similarity search |\n| **Classification** | LLM-based (GPT-4o-mini) | Auto-categorization with confidence |\n\n---\n\n## Project Structure\n\n```\nbrand-radar/\n├── apps/\n│   ├── api/                    # Fastify REST API\n│   │   ├── modules/            # DDD modules (brands, discovery, admin)\n│   │   ├── plugins/            # Fastify plugins (db, redis, search, auth)\n│   │   └── server.ts\n│   ├── web/                    # Vue 3 SPA\n│   │   ├── views/              # Discovery feed, search, brand detail, admin\n│   │   ├── components/         # Reusable UI components\n│   │   ├── stores/             # Pinia stores (brands, events, search)\n│   │   └── composables/        # useSearch, useBrands, etc.\n│   ├── workers/                # BullMQ job processors\n│   │   └── src/workers/\n│   │       ├── discovery.worker.ts       # Crawl sources\n│   │       ├── extraction.worker.ts      # Parse raw data\n│   │       ├── normalization.worker.ts   # Clean \u0026 dedupe\n│   │       ├── resolution.worker.ts      # Entity matching\n│   │       ├── enrichment.worker.ts      # Deterministic enrichment\n│   │       ├── ai-enrichment.worker.ts   # Async AI processing\n│   │       ├── scoring.worker.ts         # Compute scores\n│   │       └── indexing.worker.ts        # Sync to Meilisearch\n│   └── scheduler/              # Cron-based job orchestration\n├── packages/\n│   ├── db/                     # Drizzle ORM + PostgreSQL schema\n│   │   ├── src/schema/\n│   │   │   ├── canonical-entities.ts     # Core brand entities\n│   │   │   ├── system-events.ts          # Event backbone\n│   │   │   ├── processed-jobs.ts         # Idempotency tracking\n│   │   │   ├── pipeline-versions.ts      # DAG definitions\n│   │   │   ├── cost-events.ts            # Budget tracking\n│   │   │   └── data-quality-scores.ts    # Quality metrics\n│   │   └── drizzle/            # Migrations (committed to git)\n│   ├── search/                 # Meilisearch client\n│   ├── redis/                  # Redis client + queue definitions\n│   ├── adapters/               # Scraping adapters (Instagram, TikTok, web)\n│   ├── shared/                 # Zod schemas, types, utilities\n│   ├── ai/                     # Embeddings, NLP (Phase 2)\n│   ├── taxonomy/               # Brand classification\n│   └── auth/                   # better-auth wrapper\n└── .claude/                    # Documentation \u0026 agents\n    ├── docs/\n    │   ├── architecture.md\n    │   ├── brand-platform/\n    │   │   ├── overview.md             # Strategic vision\n    │   │   ├── pipeline.md             # Worker orchestration\n    │   │   ├── schema.md               # Database design\n    │   │   └── adapters.md             # Scraping patterns\n    │   └── decisions/          # ADRs\n    └── agents/                 # Specialized Claude agents\n```\n\n---\n\n## Key Concepts\n\n### Event Sourcing\n\nEvery pipeline action emits an event to `system_events`:\n\n```typescript\nawait emitEvent({\n  traceId: job.data.traceId,\n  eventType: 'entity.resolved',\n  entityId: entity.id,\n  pipelineVersion: job.data.pipelineVersion,\n  payload: { confidence: 0.95, matchedVia: 'url_domain' },\n})\n```\n\n**Benefits:**\n- Full replay from raw data\n- Debug entity lineage (where did this brand come from?)\n- ML training data (resolution decisions, merge signals)\n- Auditable intelligence (every score has a trail)\n\n### Idempotency\n\nEvery worker checks `processed_jobs` before execution:\n\n```typescript\nconst idempotencyKey = `scoring:${entityId}:${pipelineVersion}`\nconst alreadyProcessed = await db.query.processedJobs.findFirst({\n  where: eq(processedJobs.idempotencyKey, idempotencyKey),\n})\n\nif (alreadyProcessed) return  // Skip — already scored\n```\n\n**Benefits:**\n- Safe retries (network failures, rate limits)\n- Backfills don't double-write\n- Manual re-triggers are safe\n\n### Trace Propagation\n\nEvery job carries `traceId` + `pipelineVersion` from discovery to indexing:\n\n```typescript\n// Discovery worker generates trace_id\nconst traceId = generateTraceId()  // \"tr_abc123xyz\"\n\n// Downstream workers inherit it\nawait extractionQueue.add('extract', {\n  candidateUrl,\n  traceId: job.data.traceId,\n  pipelineVersion: job.data.pipelineVersion,\n})\n```\n\n**Benefits:**\n- Filter logs by `traceId` — see full entity lifecycle\n- Query `system_events` by `traceId` — find where pipeline broke\n- Grafana traces for end-to-end latency\n\n### Pipeline Versioning\n\nThe `pipeline_versions` table stores DAG definitions:\n\n```json\n{\n  \"name\": \"core-pipeline\",\n  \"version\": \"2.1.0\",\n  \"dag\": {\n    \"stages\": [\n      { \"id\": \"discovery\", \"next\": \"extraction\" },\n      { \"id\": \"extraction\", \"next\": \"normalization\" },\n      ...\n    ]\n  }\n}\n```\n\n**Benefits:**\n- Run v1 and v2 in parallel (blue/green deployment)\n- Backfill with old pipeline version for comparison\n- Never lose reproducibility when logic changes\n\n---\n\n## Development Workflow\n\n### Adding a New Worker\n\n1. **Define job schema** in `packages/shared`:\n\n```typescript\nexport const MyJobSchema = z.object({\n  entityId: z.string(),\n  traceId: z.string(),\n  pipelineVersion: z.string(),\n})\n```\n\n2. **Create worker** in `apps/workers/src/workers/my-job.worker.ts`:\n\n```typescript\nexport function createMyJobWorker() {\n  return new Worker('my_job', async (job) =\u003e {\n    const { entityId, traceId, pipelineVersion } = MyJobSchema.parse(job.data)\n\n    // Idempotency check\n    const key = `my_job:${entityId}:${pipelineVersion}`\n    if (await isProcessed(key)) return\n\n    // Do work...\n\n    // Mark processed\n    await markProcessed(key)\n\n    // Emit event\n    await emitEvent({ traceId, eventType: 'my_job.completed', ... })\n  })\n}\n```\n\n3. **Register queue** in `apps/workers/src/index.ts`\n\n4. **Write integration test** that verifies idempotency + event emission\n\nSee [Pipeline Architecture](/.claude/docs/brand-platform/pipeline.md) for full patterns.\n\n### Adding a New Scraping Adapter\n\n1. **Create adapter** in `packages/adapters/\u003csource\u003e/`:\n\n```typescript\nexport const myAdapter: ScraperAdapter = {\n  id: 'my-source',\n  sourceType: 'website',\n\n  async discover(query: DiscoveryQuery): AsyncGenerator\u003cRawCandidate\u003e {\n    // Yield candidates...\n  },\n\n  async extract(url: string): Promise\u003cExtractedBrand\u003e {\n    // Extract structured data...\n  },\n\n  async probe(): Promise\u003cAdapterHealth\u003e {\n    // Health check...\n  },\n}\n```\n\n2. **Register** in `packages/adapters/src/registry.ts`\n\n3. **Configure** via admin UI → Sources → Add Source\n\nSee [Adapter Strategy](/.claude/docs/brand-platform/adapters.md) for anti-bot patterns.\n\n### Adding a New API Module\n\n1. **Define schema** in `packages/shared/src/schemas/\u003cdomain\u003e/`:\n\n```typescript\nexport const CreateItemSchema = z.object({ name: z.string() })\n```\n\n2. **Create module** in `apps/api/src/modules/\u003cdomain\u003e/`:\n\n```\nmodules/items/\n├── items.routes.ts        # Fastify plugin, autoPrefix = '/items'\n├── items.handlers.ts      # HTTP layer (request/response)\n├── items.service.ts       # Business logic\n└── items.repository.ts    # Drizzle queries\n```\n\n3. **Register repository** in `apps/api/src/plugins/app/repositories.ts`\n\n4. **Add types** to `apps/api/src/types/fastify.d.ts`\n\nSee [Architecture](/.claude/docs/architecture.md) for DDD patterns.\n\n---\n\n## Commands Reference\n\n### Development\n\n```bash\npnpm dev                    # Start all apps (api, web, workers, scheduler)\npnpm dev:api                # Start API only\npnpm dev:web                # Start web only\npnpm dev:workers            # Start workers only\n```\n\n### Database\n\n```bash\npnpm db:generate            # Generate migration from schema diff\npnpm db:migrate             # Apply pending migrations\npnpm db:push                # Push schema directly (dev only)\npnpm db:reset               # Drop all tables + push\npnpm db:seed                # Seed demo data\npnpm db:studio              # Open Drizzle Studio\n```\n\n### Quality\n\n```bash\npnpm lint                   # ESLint across workspace\npnpm lint:fix               # Auto-fix issues\npnpm type-check             # TypeScript across workspace\npnpm test                   # Vitest (api + web)\npnpm test:watch             # Watch mode\npnpm build                  # Build all apps\n```\n\n### Docker\n\n```bash\ndocker compose up -d                        # Start infra only\ndocker compose up --build --watch           # Full stack with hot reload\ndocker compose logs -f api                  # Follow API logs\ndocker compose exec postgres psql -U postgres -d brand_radar\n```\n\n---\n\n## Observability\n\n### Trace ID Explorer\n\nQuery `system_events` by `trace_id` to see full entity lineage:\n\n```sql\nSELECT event_type, entity_id, created_at, payload\nFROM system_events\nWHERE trace_id = 'tr_abc123xyz'\nORDER BY created_at;\n```\n\n### Event Debug Viewer (Admin UI)\n\n- Navigate to **Admin → Events**\n- Search by `trace_id`, `entity_id`, or `event_type`\n- See full pipeline execution for any entity\n\n### Prometheus Metrics\n\n```\nworker_jobs_processed_total{queue=\"scoring\", status=\"success\"}\nworker_job_duration_seconds{queue=\"scoring\"}\nqueue_depth{queue=\"discovery\"}\nadapter_success_rate{adapter=\"instagram\"}\ncost_events_total{service=\"openai\"}\n```\n\n### Logs\n\nAll logs include `traceId` and `pipelineVersion`:\n\n```json\n{\n  \"level\": \"info\",\n  \"time\": \"2026-05-27T09:15:00Z\",\n  \"traceId\": \"tr_abc123xyz\",\n  \"pipelineVersion\": \"2.1.0\",\n  \"event\": \"job.completed\",\n  \"worker\": \"scoring\",\n  \"entityId\": \"123\",\n  \"score\": 87.5\n}\n```\n\nFilter logs: `jq 'select(.traceId == \"tr_abc123xyz\")'`\n\n---\n\n## Production Deployment\n\n### Environment Variables\n\n```bash\n# Required\nDATABASE_URL=postgresql://user:pass@host:5432/brand_radar\nREDIS_URL=redis://host:6379\nMEILISEARCH_URL=http://host:7700\nMEILISEARCH_MASTER_KEY=your-master-key-here\nBETTER_AUTH_SECRET=your-secret-key-here\n\n# Optional\nACTIVE_PIPELINE_VERSION=2.1.0\nS3_ENDPOINT=https://s3.amazonaws.com\nS3_BUCKET=brand-radar-raw\nOPENAI_API_KEY=sk-...                      # Phase 2\nMEILI_ENV=production\n```\n\n### Docker Compose (Production)\n\n```yaml\nservices:\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_DB: brand_radar\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: ${DB_PASSWORD}\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n\n  redis:\n    image: redis:7-alpine\n    command: redis-server --appendonly yes  # CRITICAL: persistence\n    volumes:\n      - redis_data:/data\n\n  meilisearch:\n    image: getmeili/meilisearch:v1.10\n    environment:\n      MEILI_ENV: production\n      MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}\n      MEILI_NO_ANALYTICS: true\n    volumes:\n      - meilisearch_data:/meili_data\n\n  api:\n    build:\n      context: .\n      target: api-prod\n    environment:\n      DATABASE_URL: ${DATABASE_URL}\n      REDIS_URL: redis://redis:6379\n      MEILISEARCH_URL: http://meilisearch:7700\n      MEILISEARCH_MASTER_KEY: ${MEILI_MASTER_KEY}\n      ACTIVE_PIPELINE_VERSION: ${ACTIVE_PIPELINE_VERSION:-2.1.0}\n    depends_on:\n      - postgres\n      - redis\n      - meilisearch\n\n  workers:\n    build:\n      context: .\n      target: workers-prod\n    environment:\n      DATABASE_URL: ${DATABASE_URL}\n      REDIS_URL: redis://redis:6379\n      ACTIVE_PIPELINE_VERSION: ${ACTIVE_PIPELINE_VERSION:-2.1.0}\n    depends_on:\n      - postgres\n      - redis\n    deploy:\n      replicas: 3  # Scale per queue\n```\n\n### Health Checks\n\n- **API:** `GET /health` → `{ status: \"ok\", uptime: 12345 }`\n- **Workers:** Check queue depth via Redis CLI\n- **Database:** Query `system_events` — should see recent events\n\n---\n\n## Cost Governance\n\n### Budget Enforcement\n\nAI enrichment is **always gated**:\n\n```typescript\nif (!await checkDailyBudget('openai')) {\n  await pauseQueue('ai_enrichment')\n  await emitAlert('daily_ai_budget_exceeded')\n  return\n}\n```\n\n### Cost Tracking\n\nQuery `cost_events` for spend breakdown:\n\n```sql\nSELECT service, SUM(cost_usd) as total_cost\nFROM cost_events\nWHERE created_at \u003e= NOW() - INTERVAL '7 days'\nGROUP BY service;\n```\n\n### Confidence Thresholds\n\nAI only fires when confidence \u003e 0.6:\n\n```typescript\nif (entity.discoveryConfidence \u003c AI_CONFIDENCE_THRESHOLD) {\n  logger.info('Skipping AI enrichment — low confidence')\n  return\n}\n```\n\n---\n\n## Documentation\n\n- **[Architecture Overview](/.claude/docs/architecture.md)** — System design, event sourcing, trace propagation\n- **[Pipeline Architecture](/.claude/docs/brand-platform/pipeline.md)** — Workers, queues, scoring, idempotency\n- **[Schema Design](/.claude/docs/brand-platform/schema.md)** — Database tables, event backbone, pgvector\n- **[Adapter Strategy](/.claude/docs/brand-platform/adapters.md)** — Anti-bot tactics, Playwright config\n- **[Style Guide](/.claude/docs/style-guide.md)** — Code conventions, naming, TypeScript patterns\n- **[Commit Conventions](/.claude/docs/commit-conventions.md)** — Conventional Commits for monorepo\n\n### Architecture Decision Records (ADRs)\n\n- [ADR-001: Playwright Only](/.claude/docs/decisions/001-playwright-only.md)\n- [ADR-002: Meilisearch Not OpenSearch](/.claude/docs/decisions/002-meilisearch-not-opensearch.md)\n- [ADR-003: Adapter vs Source Separation](/.claude/docs/decisions/003-adapter-vs-source-separation.md)\n\n---\n\n## Specialized Agents\n\nDelegate to Claude Code agents for domain-specific work:\n\n- **`drizzle-expert`** — Schema changes, queries, migrations, pgvector\n- **`fastify-expert`** — Routes, plugins, hooks, error handling\n- **`vue-expert`** — Components, composables, Pinia stores\n- **`pipeline-expert`** — Workers, event emission, idempotency, scoring\n- **`scraping-expert`** — Adapters, anti-bot evasion, Playwright stealth\n\nSee [`.claude/agents/`](/.claude/agents/) for full definitions.\n\n---\n\n## Contributing\n\n1. **Read the docs** — Start with [Architecture Overview](/.claude/docs/architecture.md)\n2. **Pick an issue** — Check [open issues](https://github.com/im-codebreaker/brand-radar/issues)\n3. **Create a branch** — `feat/your-feature` or `fix/your-fix`\n4. **Follow conventions** — See [Commit Conventions](/.claude/docs/commit-conventions.md)\n5. **Write tests** — Integration tests for workers, unit tests for services\n6. **Open a PR** — Link to the issue, describe changes, add screenshots if UI\n\n---\n\n## Roadmap\n\n### ✅ Phase 1 — Stable Event-Sourced Pipeline (Current)\n- Event backbone (`system_events`) with `trace_id` propagation\n- Idempotent workers via `processed_jobs`\n- Pipeline versioning and backfill support\n- Entity resolution with merge review queue\n- Basic scoring (social + ecommerce + data quality)\n- Meilisearch keyword search\n\n### 🚧 Phase 2 — Intelligence Layer (Next)\n- AI enrichment (embeddings, classification)\n- pgvector semantic search\n- Hybrid search (keyword + semantic)\n- Data quality scoring and dashboard\n- Cost governance UI\n- Trend detection and sparklines\n\n### 🔮 Phase 3 — Graph \u0026 Prediction (Future)\n- Brand relationship graph (Cytoscape.js)\n- Backfill UI (admin-triggered replay)\n- Natural language search\n- Recommendation engine\n- Viral prediction models\n\n---\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n\n---\n\n## Questions?\n\n- **Issues:** [GitHub Issues](https://github.com/im-codebreaker/brand-radar/issues)\n- **Docs:** [`.claude/docs/`](/.claude/docs/)\n- **Email:** [mbouchard@antidots-group.com](mailto:mbouchard@antidots-group.com)\n\n**Built with ❤️ by [@im-codebreaker](https://github.com/im-codebreaker)**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fim-codebreaker%2Fbrand-radar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fim-codebreaker%2Fbrand-radar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fim-codebreaker%2Fbrand-radar/lists"}