# Brand Presence Monitoring - Implementation Plan

## Phase 1: Database Schema (3 migrations)

### Migration 1: `create_brand_channels_table`
Stores connected channels/accounts per project.

| Column | Type | Notes |
|--------|------|-------|
| id | bigint PK | |
| project_id | FK → projects | cascadeOnDelete |
| platform | string(50) | twitter, facebook, linkedin, instagram, pinterest, threads, youtube, tiktok, vimeo, reddit, quora, stackoverflow, discord, github, medium, substack, podcast, forum |
| channel_type | string(30) | social, video, podcast, forum, code, blog |
| name | string | Display name (e.g., "@brandname") |
| url | string | Profile/channel URL |
| feed_url | string nullable | RSS/Atom feed URL (for podcasts, forums, Medium, Substack, vBulletin) |
| platform_id | string nullable | Platform-specific ID (YouTube channel ID, Reddit username, etc.) |
| is_active | boolean default true | Enable/disable fetching |
| settings_json | JSON nullable | Platform-specific config (subreddit list, hashtags to track, etc.) |
| last_fetched_at | timestamp nullable | |
| timestamps | | |
| **unique** | (project_id, platform, platform_id) | |

### Migration 2: `create_brand_channel_snapshots_table`
Daily metrics snapshots for trend tracking.

| Column | Type | Notes |
|--------|------|-------|
| id | bigint PK | |
| channel_id | FK → brand_channels | cascadeOnDelete |
| project_id | FK → projects | cascadeOnDelete |
| snapshot_date | date | |
| followers_count | int nullable | Followers/subscribers/members |
| posts_count | int nullable | Total posts/videos/episodes cumulative |
| engagement_rate | decimal(5,2) nullable | Avg engagement % |
| views_count | int nullable | Total views (YouTube, TikTok) |
| metrics_json | JSON nullable | Platform-specific extras (watch_time, avg_duration, ratings, reviews_count, stars, forks, etc.) |
| timestamps | | |
| **unique** | (channel_id, snapshot_date) | |

### Migration 3: `create_brand_activities_table`
Individual posts/episodes/articles across all channels.

| Column | Type | Notes |
|--------|------|-------|
| id | bigint PK | |
| channel_id | FK → brand_channels | cascadeOnDelete |
| project_id | FK → projects | cascadeOnDelete |
| platform_post_id | string nullable | Platform's unique ID for the post |
| title | string nullable | Post title or first line |
| content_preview | text nullable | Truncated content (first 500 chars) |
| url | string nullable | Direct link to post |
| published_at | timestamp nullable | |
| likes | int default 0 | |
| comments | int default 0 | |
| shares | int default 0 | |
| views | int default 0 | |
| metrics_json | JSON nullable | Platform extras (retweets, watch_time, listen_count, upvotes, downvotes, etc.) |
| timestamps | | |
| **unique** | (channel_id, platform_post_id) | |
| **index** | (project_id, published_at) | For calendar/heatmap queries |

---

## Phase 2: Backend Models

### Models to create:
1. **BrandChannel** — belongsTo Project, hasMany Snapshots, hasMany Activities
2. **BrandChannelSnapshot** — belongsTo BrandChannel
3. **BrandActivity** — belongsTo BrandChannel

---

## Phase 3: Backend Controller (`BrandPresenceController`)

### Endpoints:

| Method | Route | Action | Role |
|--------|-------|--------|------|
| GET | /brand/channels | List all channels with latest snapshot | any |
| POST | /brand/channels | Add a channel | admin |
| PUT | /brand/channels/{channel} | Update channel config | admin |
| DELETE | /brand/channels/{channel} | Remove channel | admin |
| GET | /brand/dashboard | Unified dashboard (all channels summary, authority score, stale alerts) | any |
| GET | /brand/activity | Activity feed across all channels, paginated, filterable by platform/date | any |
| GET | /brand/heatmap | Activity counts grouped by date × platform for calendar heatmap | any |
| GET | /brand/channels/{channel}/snapshots | Historical metrics for a channel (trend charts) | any |
| POST | /brand/fetch | Manually trigger fetch for all channels | admin |

---

## Phase 4: Fetch Service (`BrandFetchService`)

A service that dispatches per-channel fetch jobs. Each platform adapter handles:

### Platform Adapters (implement incrementally):

**RSS-based (easiest — no API keys needed):**
- `RssFeedAdapter` — Generic RSS/Atom parser for: podcasts, Medium, Substack, vBulletin forums, any blog
- Parses feed → creates BrandActivity records + updates snapshot

**Public API / Scraping:**
- `YouTubeAdapter` — YouTube Data API v3 (channel stats, latest videos)
- `GitHubAdapter` — GitHub API (repo stars, forks, issues, latest releases)
- `RedditAdapter` — Reddit JSON API (subreddit/user posts, no auth needed for public data)

**Authenticated API (later phase):**
- `TwitterAdapter` — X API v2
- `LinkedInAdapter` — LinkedIn API
- `FacebookAdapter` — Meta Graph API
- `InstagramAdapter` — Meta Graph API
- `TikTokAdapter` — TikTok API

**Implementation pattern:**
```
interface BrandPlatformAdapter {
    function fetchLatestActivity(BrandChannel $channel): Collection;
    function fetchSnapshot(BrandChannel $channel): array;
}
```

Each adapter returns a standardized format. The service picks the right adapter based on `$channel->platform`.

---

## Phase 5: Job & Scheduler

### Job: `FetchBrandChannelJob`
- Queue: `ingestion`
- Tries: 2, timeout: 120s
- Calls the appropriate adapter for the channel
- Creates/updates BrandActivity records (upsert by platform_post_id)
- Creates daily BrandChannelSnapshot
- Updates channel's `last_fetched_at`
- Creates SyncLog entry

### Command: `SyncBrandChannels`
- Signature: `seoos:sync-brand-channels {--project=} {--channel=}`
- Loops active projects → active channels → dispatches FetchBrandChannelJob

### Schedule:
- `dailyAt('09:00')` — once daily for social/video channels
- RSS feeds could run `twiceDaily('08:00', '20:00')` like other syncs

---

## Phase 6: Dashboard Calculations

### Unified Dashboard endpoint returns:

```json
{
  "channels": [
    {
      "id": 1,
      "platform": "youtube",
      "name": "Brand Channel",
      "followers": 12500,
      "followers_change_30d": +340,
      "last_post_at": "2026-03-20T...",
      "days_since_post": 2,
      "is_stale": false,
      "post_frequency_30d": 8,
      "engagement_rate": 3.2
    }
  ],
  "authority_score": 72,
  "total_channels": 6,
  "active_channels": 5,
  "stale_channels": 1,
  "total_posts_30d": 45,
  "top_post": { ... },
  "platform_breakdown": [
    { "platform": "twitter", "posts_30d": 20, "engagement": 2.1 },
    { "platform": "youtube", "posts_30d": 4, "engagement": 5.8 }
  ]
}
```

### Authority Score calculation:
- Social reach (total followers across platforms, weighted) — 30%
- Content velocity (posts per week across all channels) — 25%
- Engagement rate (avg engagement across channels) — 25%
- Channel diversity (number of active platform types) — 10%
- Consistency (no stale channels penalty) — 10%

### Stale Channel Alert:
- Social: no post in 14+ days
- YouTube: no upload in 30+ days
- Podcast: no episode in 30+ days
- Blog/Medium: no post in 21+ days
- Forum/Reddit: no activity in 7+ days

---

## Phase 7: Frontend Pages

### 1. Brand Presence Dashboard (`/projects/{id}/brand`)
- **Top stats**: Authority Score (gauge), Total Channels, Active/Stale count, Posts last 30d
- **Channels grid**: Cards per channel showing platform icon, name, followers, last post, stale indicator, trend sparkline
- **Stale alerts**: Warning banner for channels that haven't posted
- **Activity heatmap**: GitHub-style calendar grid (last 90 days), color intensity = total posts across all channels per day

### 2. Activity Feed (`/projects/{id}/brand/activity`)
- Chronological feed of all posts across all platforms
- Filter by platform, date range
- Shows: platform icon, title/preview, engagement metrics, published date
- Link to original post

### 3. Channel Detail (`/projects/{id}/brand/channels/{id}`)
- Follower/subscriber trend chart (line chart, last 90 days)
- Engagement rate trend
- Posting frequency chart
- Recent posts list
- Platform-specific metrics

### 4. Channel Management (in Settings or inline)
- Add channel form: select platform → enter URL/username/feed URL
- Edit channel config
- Toggle active/inactive
- Delete channel

### Sidebar:
- Add "Brand" item under `analysis` group (between Competitors and Opportunities)

---

## Phase 8: Cron Status Integration

Add `brand_sync` to the SyncLog latest endpoint so the Cron Report card on the overview shows:
- Brand Sync — last run, status, channels fetched

---

## Implementation Order

| Step | What | Files |
|------|------|-------|
| 1 | 3 migrations | `database/migrations/` |
| 2 | 3 models | `app/Models/` |
| 3 | Controller + routes | `app/Http/Controllers/BrandPresenceController.php`, `routes/api.php` |
| 4 | RSS adapter (covers podcasts, forums, Medium, Substack) | `app/Services/Brand/RssFeedAdapter.php` |
| 5 | YouTube + GitHub + Reddit adapters | `app/Services/Brand/` |
| 6 | Fetch job + command + scheduler | `app/Jobs/`, `app/Console/Commands/`, `routes/console.php` |
| 7 | Dashboard calculation service | `app/Services/Brand/BrandDashboardService.php` |
| 8 | Frontend: dashboard page | `frontend/app/(dashboard)/projects/[projectId]/brand/page.tsx` |
| 9 | Frontend: activity feed | `frontend/app/(dashboard)/projects/[projectId]/brand/activity/page.tsx` |
| 10 | Frontend: channel management | Inline in dashboard or settings |
| 11 | Sidebar nav item | `frontend/components/layout/Sidebar.tsx` |
| 12 | SyncLog integration | `SyncLogController`, overview cron section |
