Skip to content

Unified Content Tagging & Attribution System

Technical Specification v1.2

Status: Complete (All Phases Implemented) Author: Engineering Team Date: January 2026 Last Updated: January 19, 2026 Target Users: Marketing Teams, Product Managers, Partner Success

Implementation Progress

Phase Status Description
Phase 1 ✅ Complete Foundation - Core data model, APIs, plan-gated features
Phase 2 ✅ Complete Attribution Tracking - UTM, HubSpot, reporting services
Phase 3 ✅ Complete AI Tagging & Backfill - AutoTagCapability, backfill jobs, suggestion queue
Phase 4 ✅ Complete Reporting & Analytics - Export, Audit Logging, Role-based Permissions

Executive Summary

The Problem

AWS Marketplace partners generate significant content—social posts, blog articles, email campaigns, presentations, co-sell narratives—but lack visibility into which content assets actually drive pipeline and revenue. AWS Marketplace provides transaction data but no content attribution. Partners cannot answer:

  • "Which blog post drove the most qualified leads?"
  • "What content influenced this $500K deal?"
  • "Which Knowledge Base produces the highest-converting content?"
  • "How does content from Campaign A compare to Campaign B?"

The Solution

A Unified Content Tagging & Attribution System that:

  1. Tags all content with flexible, hierarchical metadata
  2. Links content to campaigns, deals, and revenue events
  3. Integrates with HubSpot, Salesforce, and AWS Marketplace data
  4. Provides multi-touch attribution modeling
  5. Delivers insights AWS Marketplace cannot provide

Value Proposition

Stakeholder Value Delivered
Marketing Know which content drives pipeline, optimize spend, prove ROI
Product Managers Track feature/messaging performance, inform roadmap
Partner Success Demonstrate value to AWS, justify co-sell investments
Leadership Attribution-backed decisions, not guesswork

1. User Personas & Use Cases

1.1 Marketing Manager (Primary)

Goals: - Track content performance across campaigns - Attribute leads/deals to specific content pieces - Compare KB-generated vs manually-created content - Report on content ROI to leadership

Key Workflows:

Create Campaign → Tag Content → Track Engagement →
Correlate to Deals → Report Attribution

Pain Points Solved: - "I create 50 pieces of content/month but don't know what works" - "Leadership asks for content ROI and I can't prove it" - "I can't compare performance across different content sources"

1.2 Product Manager (Secondary)

Goals: - Track which product messaging resonates - Identify content gaps by product area - Measure launch campaign effectiveness - Feed insights back to product roadmap

Key Workflows:

Tag Content by Product/Feature → Track Deal Influence →
Identify High-Performing Messaging → Inform Positioning

Pain Points Solved: - "I don't know which product narratives close deals" - "Can't measure if our new positioning is working" - "No feedback loop from content to product decisions"

1.3 Partner Success Manager (Tertiary)

Goals: - Demonstrate content-driven pipeline to AWS - Justify co-sell investment with attribution data - Identify top-performing co-sell content - Build case studies with real data

Key Workflows:

Tag Co-Sell Content → Link to AWS Opportunities →
Generate Attribution Reports → Present to AWS PDM

Pain Points Solved: - "AWS asks what content influenced deals—I can't tell them" - "Can't prove ROI of partner content investment" - "No way to compare our attribution vs AWS's limited data"


2. Data Model & Schema Design

2.1 Core Tables

content_tags - Tag Definitions

CREATE TABLE content_tags (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    team_id BIGINT UNSIGNED NOT NULL,

    -- Tag Identity
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) NOT NULL,
    description TEXT NULL,
    color VARCHAR(7) DEFAULT '#6366f1', -- Hex color
    icon VARCHAR(50) NULL, -- Icon identifier

    -- Hierarchy & Organization
    category ENUM('campaign', 'product', 'team', 'source', 'custom') NOT NULL,
    parent_id BIGINT UNSIGNED NULL, -- Self-referential for hierarchy
    sort_order INT DEFAULT 0,

    -- External Mappings (for sync)
    external_mappings JSON NULL,
    -- {
    --   "hubspot_property": "campaign_name",
    --   "salesforce_field": "Campaign__c",
    --   "aws_tag_key": "vell:campaign"
    -- }

    -- Metadata
    is_system BOOLEAN DEFAULT FALSE, -- System tags can't be deleted
    is_active BOOLEAN DEFAULT TRUE,
    usage_count INT UNSIGNED DEFAULT 0, -- Denormalized for performance

    created_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- Indexes
    INDEX idx_team_category (team_id, category),
    INDEX idx_team_slug (team_id, slug),
    INDEX idx_parent (parent_id),
    UNIQUE KEY unique_team_slug (team_id, slug),

    FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
    FOREIGN KEY (parent_id) REFERENCES content_tags(id) ON DELETE SET NULL,
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
);

content_tag_assignments - Tag-to-Content Pivot

CREATE TABLE content_tag_assignments (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,

    -- Polymorphic Content Reference
    taggable_type VARCHAR(100) NOT NULL, -- 'user_openai', 'agent_execution', etc.
    taggable_id BIGINT UNSIGNED NOT NULL,

    -- Tag Reference
    tag_id BIGINT UNSIGNED NOT NULL,

    -- Assignment Metadata
    assigned_by BIGINT UNSIGNED NULL, -- NULL = system/AI assigned
    assignment_source ENUM('manual', 'ai_suggested', 'ai_auto', 'import', 'rule') DEFAULT 'manual',
    confidence_score DECIMAL(3,2) NULL, -- 0.00-1.00 for AI assignments

    -- Audit
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    -- Indexes
    INDEX idx_taggable (taggable_type, taggable_id),
    INDEX idx_tag (tag_id),
    INDEX idx_source (assignment_source),
    UNIQUE KEY unique_assignment (taggable_type, taggable_id, tag_id),

    FOREIGN KEY (tag_id) REFERENCES content_tags(id) ON DELETE CASCADE,
    FOREIGN KEY (assigned_by) REFERENCES users(id) ON DELETE SET NULL
);

content_attribution_events - Attribution Tracking

CREATE TABLE content_attribution_events (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    team_id BIGINT UNSIGNED NOT NULL,

    -- Content Reference
    taggable_type VARCHAR(100) NOT NULL,
    taggable_id BIGINT UNSIGNED NOT NULL,

    -- Event Details
    event_type ENUM(
        'view',           -- Content viewed
        'click',          -- Link clicked (from TrackedUrlCode)
        'download',       -- Asset downloaded
        'share',          -- Content shared
        'form_submit',    -- Lead form submission
        'meeting_booked', -- Meeting scheduled
        'opportunity',    -- Opportunity created/influenced
        'deal_closed'     -- Deal closed-won
    ) NOT NULL,

    -- Attribution Context
    utm_source VARCHAR(100) NULL,
    utm_medium VARCHAR(100) NULL,
    utm_campaign VARCHAR(255) NULL,
    utm_content VARCHAR(255) NULL,
    utm_term VARCHAR(255) NULL,

    -- External References
    tracked_url_code_id BIGINT UNSIGNED NULL,
    hubspot_contact_id VARCHAR(100) NULL,
    hubspot_deal_id VARCHAR(100) NULL,
    salesforce_lead_id VARCHAR(100) NULL,
    salesforce_opportunity_id VARCHAR(100) NULL,
    aws_opportunity_id VARCHAR(100) NULL,

    -- Value Attribution
    attributed_value DECIMAL(15,2) NULL, -- Revenue/pipeline attributed
    attribution_model ENUM('first_touch', 'last_touch', 'linear', 'time_decay', 'position_based') NULL,
    attribution_weight DECIMAL(5,4) NULL, -- 0.0000-1.0000

    -- Event Metadata
    event_metadata JSON NULL,
    -- {
    --   "referrer": "https://linkedin.com/...",
    --   "device": "desktop",
    --   "location": "US",
    --   "session_id": "abc123"
    -- }

    occurred_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    -- Indexes
    INDEX idx_team_event (team_id, event_type),
    INDEX idx_taggable (taggable_type, taggable_id),
    INDEX idx_occurred (occurred_at),
    INDEX idx_hubspot_deal (hubspot_deal_id),
    INDEX idx_salesforce_opp (salesforce_opportunity_id),
    INDEX idx_aws_opp (aws_opportunity_id),
    INDEX idx_utm_campaign (utm_campaign),

    FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
    FOREIGN KEY (tracked_url_code_id) REFERENCES tracked_url_codes(id) ON DELETE SET NULL
);

tag_rules - Automated Tagging Rules

CREATE TABLE tag_rules (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    team_id BIGINT UNSIGNED NOT NULL,

    -- Rule Definition
    name VARCHAR(100) NOT NULL,
    description TEXT NULL,

    -- Trigger Conditions (JSON query builder format)
    conditions JSON NOT NULL,
    -- {
    --   "operator": "AND",
    --   "rules": [
    --     {"field": "content_type", "operator": "equals", "value": "blog"},
    --     {"field": "output", "operator": "contains", "value": "AWS Marketplace"}
    --   ]
    -- }

    -- Actions
    tag_ids JSON NOT NULL, -- Array of tag IDs to apply

    -- Rule Settings
    is_active BOOLEAN DEFAULT TRUE,
    apply_to_existing BOOLEAN DEFAULT FALSE, -- Backfill existing content
    priority INT DEFAULT 0, -- Higher = runs first

    -- Audit
    last_triggered_at TIMESTAMP NULL,
    trigger_count INT UNSIGNED DEFAULT 0,
    created_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
);

2.2 Extended user_openai Metadata Schema

Extend the existing metadata JSON column:

{
  // Existing fields
  "agent_execution_id": "uuid",
  "content_type": "blog|social|email|presentation|...",
  "kanban_status": "generated|review|ready|published",
  "kanban_updated_at": "2026-01-19T10:00:00Z",

  // NEW: Attribution tracking
  "attribution": {
    "primary_campaign_id": 123,
    "utm_params": {
      "source": "linkedin",
      "medium": "social",
      "campaign": "q1-2026-launch",
      "content": "product-announcement",
      "term": null
    },
    "knowledge_base_ids": [1, 5, 12],
    "product_ids": [3],
    "first_published_at": "2026-01-15T14:30:00Z",
    "published_urls": [
      "https://blog.example.com/post-123",
      "https://linkedin.com/posts/abc"
    ]
  },

  // NEW: Performance metrics (denormalized for fast queries)
  "performance": {
    "views": 1250,
    "clicks": 89,
    "shares": 23,
    "leads_influenced": 5,
    "pipeline_influenced": 125000.00,
    "revenue_attributed": 45000.00,
    "last_calculated_at": "2026-01-19T08:00:00Z"
  },

  // NEW: AI classification
  "ai_classification": {
    "topics": ["aws-marketplace", "isv-acceleration", "co-sell"],
    "sentiment": "positive",
    "reading_level": "professional",
    "suggested_tags": [
      {"tag_id": 15, "confidence": 0.92},
      {"tag_id": 23, "confidence": 0.78}
    ],
    "classified_at": "2026-01-19T10:00:00Z"
  }
}

2.3 Entity Relationship Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                           CONTENT TAGGING SYSTEM                            │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────┐       ┌─────────────────────┐       ┌──────────────────┐
│  user_openai │       │ content_tag_assign- │       │   content_tags   │
│  (content)   │◄──────│       ments         │──────►│   (definitions)  │
├──────────────┤       ├─────────────────────┤       ├──────────────────┤
│ id           │       │ taggable_type       │       │ id               │
│ team_id      │       │ taggable_id         │       │ team_id          │
│ user_id      │       │ tag_id              │       │ name             │
│ metadata     │───┐   │ assignment_source   │       │ category         │
│ output       │   │   │ confidence_score    │       │ parent_id ───────┼──┐
└──────────────┘   │   └─────────────────────┘       │ external_mappings│  │
                   │                                 └──────────────────┘  │
                   │                                          ▲            │
                   │   ┌─────────────────────┐                │            │
                   │   │ content_attribution │                └────────────┘
                   │   │      _events        │                (self-ref)
                   │   ├─────────────────────┤
                   └──►│ taggable_type       │       ┌──────────────────┐
                       │ taggable_id         │       │    tag_rules     │
                       │ event_type          │       ├──────────────────┤
                       │ utm_*               │       │ conditions       │
                       │ hubspot_deal_id     │       │ tag_ids          │
                       │ salesforce_opp_id   │       │ apply_to_existing│
                       │ attributed_value    │       └──────────────────┘
                       └─────────────────────┘
              ┌─────────────────┼─────────────────┐
              ▼                 ▼                 ▼
    ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐
    │   HubSpot    │  │  Salesforce  │  │  AWS Marketplace │
    │    Deals     │  │ Opportunities│  │   Transactions   │
    └──────────────┘  └──────────────┘  └──────────────────┘

3. API Design

3.1 Tag Management Endpoints

Tags CRUD

# List tags for team
GET /api/v1/tags
Query Parameters:
  - category: string (campaign|product|team|source|custom)
  - search: string (name search)
  - include_usage: boolean (include usage counts)
  - hierarchical: boolean (return nested structure)
Response:
  {
    "data": [
      {
        "id": 1,
        "name": "Q1 2026 Launch",
        "slug": "q1-2026-launch",
        "category": "campaign",
        "color": "#6366f1",
        "usage_count": 47,
        "children": [...],
        "external_mappings": {
          "hubspot_property": "campaign_name"
        }
      }
    ],
    "meta": {
      "total": 25,
      "by_category": {
        "campaign": 8,
        "product": 6,
        "team": 4,
        "source": 3,
        "custom": 4
      }
    }
  }

# Create tag
POST /api/v1/tags
Body:
  {
    "name": "AWS re:Invent 2026",
    "category": "campaign",
    "color": "#f59e0b",
    "parent_id": null,
    "external_mappings": {
      "hubspot_property": "campaign_name",
      "aws_tag_key": "vell:campaign"
    }
  }

# Update tag
PATCH /api/v1/tags/{id}

# Delete tag
DELETE /api/v1/tags/{id}
Query Parameters:
  - reassign_to: int (optional tag ID to reassign content)

# Merge tags
POST /api/v1/tags/{id}/merge
Body:
  {
    "merge_tag_ids": [5, 8, 12],
    "keep_as_alias": true
  }

3.2 Tag Assignment Endpoints

# Get tags for content
GET /api/v1/content/{type}/{id}/tags
Response:
  {
    "data": [
      {
        "tag": { "id": 1, "name": "Q1 Launch", ... },
        "assignment_source": "manual",
        "assigned_by": { "id": 5, "name": "Jane Doe" },
        "assigned_at": "2026-01-15T10:00:00Z"
      }
    ],
    "suggested": [
      {
        "tag": { "id": 15, "name": "AWS Marketplace", ... },
        "confidence": 0.92,
        "reason": "Content mentions AWS Marketplace 5 times"
      }
    ]
  }

# Assign tags to content
POST /api/v1/content/{type}/{id}/tags
Body:
  {
    "tag_ids": [1, 5, 12],
    "source": "manual"
  }

# Remove tag from content
DELETE /api/v1/content/{type}/{id}/tags/{tagId}

# Bulk tag assignment
POST /api/v1/tags/bulk-assign
Body:
  {
    "tag_ids": [1, 5],
    "content": [
      { "type": "user_openai", "id": 100 },
      { "type": "user_openai", "id": 101 },
      { "type": "agent_execution", "id": 50 }
    ],
    "source": "manual"
  }

# Bulk tag removal
POST /api/v1/tags/bulk-remove
Body:
  {
    "tag_ids": [3],
    "content": [
      { "type": "user_openai", "id": 100 },
      { "type": "user_openai", "id": 101 }
    ]
  }

3.3 Content Filtering by Tags

# Filter content by tags
GET /api/v1/content
Query Parameters:
  - tags: string (comma-separated tag IDs, e.g., "1,5,12")
  - tags_match: string (all|any) default: any
  - tag_categories: string (comma-separated categories)
  - exclude_tags: string (comma-separated tag IDs to exclude)
  - content_type: string
  - date_from: date
  - date_to: date
  - has_attribution: boolean
  - min_pipeline: number
  - sort: string (created_at|performance.views|performance.pipeline_influenced)
  - sort_dir: string (asc|desc)

Response:
  {
    "data": [...],
    "meta": {
      "total": 156,
      "tag_facets": {
        "1": 45,  // tag_id: count
        "5": 32,
        "12": 28
      }
    }
  }

3.4 Attribution Endpoints

# Record attribution event
POST /api/v1/attribution/events
Body:
  {
    "taggable_type": "user_openai",
    "taggable_id": 100,
    "event_type": "click",
    "utm_source": "linkedin",
    "utm_campaign": "q1-2026-launch",
    "tracked_url_code_id": 55,
    "event_metadata": {
      "referrer": "https://linkedin.com/feed",
      "device": "mobile"
    }
  }

# Link content to deal
POST /api/v1/attribution/link-deal
Body:
  {
    "content_ids": [
      { "type": "user_openai", "id": 100 },
      { "type": "user_openai", "id": 105 }
    ],
    "deal": {
      "source": "hubspot",
      "deal_id": "12345",
      "deal_value": 150000.00,
      "stage": "closed_won"
    },
    "attribution_model": "linear"
  }

# Get attribution for content
GET /api/v1/content/{type}/{id}/attribution
Response:
  {
    "summary": {
      "total_views": 1250,
      "total_clicks": 89,
      "leads_influenced": 5,
      "deals_influenced": 2,
      "pipeline_value": 275000.00,
      "revenue_attributed": 45000.00
    },
    "events": [...],
    "deals": [
      {
        "source": "hubspot",
        "deal_id": "12345",
        "deal_name": "Acme Corp - Enterprise",
        "deal_value": 150000.00,
        "attribution_weight": 0.25,
        "attributed_value": 37500.00,
        "touch_points": [
          {
            "event_type": "click",
            "occurred_at": "2026-01-10T14:30:00Z",
            "utm_campaign": "q1-2026-launch"
          }
        ]
      }
    ]
  }

# Attribution report
GET /api/v1/attribution/report
Query Parameters:
  - date_from: date
  - date_to: date
  - group_by: string (tag|campaign|content_type|knowledge_base|team_member)
  - attribution_model: string

Response:
  {
    "summary": {
      "total_content": 156,
      "total_pipeline": 1250000.00,
      "total_revenue": 450000.00,
      "avg_content_value": 2885.00
    },
    "breakdown": [
      {
        "group": { "type": "tag", "id": 1, "name": "Q1 Launch" },
        "content_count": 47,
        "views": 12500,
        "clicks": 890,
        "leads": 45,
        "pipeline": 450000.00,
        "revenue": 125000.00,
        "roi": 2.5
      }
    ],
    "top_performing": [...],
    "attribution_model_used": "linear"
  }

3.5 AI Tagging Endpoints

# Get AI tag suggestions for content
GET /api/v1/content/{type}/{id}/suggest-tags
Response:
  {
    "suggestions": [
      {
        "tag": { "id": 15, "name": "AWS Marketplace" },
        "confidence": 0.92,
        "reason": "Content explicitly discusses AWS Marketplace listing strategies",
        "evidence": ["...AWS Marketplace provides...", "...listing optimization..."]
      },
      {
        "tag": { "id": 8, "name": "Co-Sell" },
        "confidence": 0.78,
        "reason": "References partner collaboration patterns"
      }
    ],
    "new_tag_suggestions": [
      {
        "suggested_name": "ISV Accelerate",
        "category": "campaign",
        "confidence": 0.85,
        "reason": "Multiple references to ISV Accelerate program benefits"
      }
    ]
  }

# Apply AI suggestions
POST /api/v1/content/{type}/{id}/apply-suggestions
Body:
  {
    "accept_tag_ids": [15, 8],
    "reject_tag_ids": [23],
    "create_and_apply": [
      { "name": "ISV Accelerate", "category": "campaign" }
    ]
  }

# Bulk AI tagging (backfill)
POST /api/v1/tags/ai-backfill
Body:
  {
    "filters": {
      "date_from": "2025-01-01",
      "date_to": "2025-12-31",
      "content_types": ["blog", "social"],
      "untagged_only": true
    },
    "auto_apply_threshold": 0.90,
    "tag_categories": ["campaign", "product"],
    "dry_run": false
  }

Response:
  {
    "job_id": "backfill-abc123",
    "status": "queued",
    "estimated_content": 450,
    "estimated_duration_minutes": 15
  }

# Check backfill job status
GET /api/v1/tags/ai-backfill/{jobId}
Response:
  {
    "job_id": "backfill-abc123",
    "status": "completed",
    "processed": 450,
    "auto_tagged": 312,
    "pending_review": 98,
    "failed": 5,
    "skipped": 35,
    "results_summary": {
      "by_tag": {
        "15": { "name": "AWS Marketplace", "applied": 145 },
        "8": { "name": "Co-Sell", "applied": 89 }
      }
    }
  }

4. Integration Architecture

4.1 UTM Parameter Unification

┌─────────────────────────────────────────────────────────────────────────────┐
│                         UTM PARAMETER FLOW                                  │
└─────────────────────────────────────────────────────────────────────────────┘

Content Created          Tag Applied              UTM Generated
      │                       │                        │
      ▼                       ▼                        ▼
┌──────────────┐      ┌──────────────┐      ┌──────────────────┐
│ user_openai  │      │ content_tags │      │ TrackedUrlCode   │
│              │◄────►│              │◄────►│                  │
│ metadata.    │      │ external_    │      │ utm_campaign     │
│ attribution  │      │ mappings     │      │ utm_source       │
└──────────────┘      └──────────────┘      │ utm_medium       │
                                            └──────────────────┘
                              ┌───────────────────────┼───────────────────────┐
                              ▼                       ▼                       ▼
                    ┌──────────────┐      ┌──────────────┐      ┌──────────────┐
                    │   HubSpot    │      │  Salesforce  │      │   Google     │
                    │  Campaigns   │      │  Campaigns   │      │  Analytics   │
                    └──────────────┘      └──────────────┘      └──────────────┘

Implementation:

// app/Extensions/ContentTagging/System/Services/UtmTagService.php

class UtmTagService
{
    /**
     * Generate UTM parameters from content tags
     */
    public function generateUtmFromTags(UserOpenai $content): array
    {
        $tags = $content->tags()->with('definition')->get();

        $utm = [
            'utm_source' => $this->resolveSource($content),
            'utm_medium' => $this->resolveMedium($content),
            'utm_campaign' => null,
            'utm_content' => $content->slug,
            'utm_term' => null,
        ];

        // Find campaign tag
        $campaignTag = $tags->firstWhere('definition.category', 'campaign');
        if ($campaignTag) {
            $utm['utm_campaign'] = $campaignTag->definition->slug;
        }

        // Find product tag for utm_term
        $productTag = $tags->firstWhere('definition.category', 'product');
        if ($productTag) {
            $utm['utm_term'] = $productTag->definition->slug;
        }

        return $utm;
    }

    /**
     * Create TrackedUrlCode with tag-derived UTM
     */
    public function createTrackedUrl(UserOpenai $content, string $destinationUrl): TrackedUrlCode
    {
        $utm = $this->generateUtmFromTags($content);

        return TrackedUrlCode::create([
            'user_id' => $content->user_id,
            'generated_url' => $this->buildUrl($destinationUrl, $utm),
            'utm_source' => $utm['utm_source'],
            'utm_medium' => $utm['utm_medium'],
            'utm_campaign' => $utm['utm_campaign'],
            'utm_content' => $utm['utm_content'],
            'utm_term' => $utm['utm_term'],
            'metadata' => [
                'content_id' => $content->id,
                'content_type' => 'user_openai',
                'tag_ids' => $content->tags->pluck('tag_id')->toArray(),
            ],
        ]);
    }
}

4.2 HubSpot Integration

// app/Extensions/ContentTagging/System/Services/HubSpotTagSyncService.php

class HubSpotTagSyncService
{
    /**
     * Sync tag to HubSpot as custom property
     */
    public function syncTagDefinition(ContentTag $tag): void
    {
        if (!$mapping = $tag->external_mappings['hubspot_property'] ?? null) {
            return;
        }

        // Create/update HubSpot property
        $this->hubspot->createProperty('deals', [
            'name' => $mapping,
            'label' => $tag->name,
            'type' => 'enumeration',
            'fieldType' => 'checkbox',
            'groupName' => 'vell_content_attribution',
            'options' => $this->buildTagOptions($tag),
        ]);
    }

    /**
     * When deal closes, attribute to content
     */
    public function handleDealClosed(array $dealData): void
    {
        // Find content that influenced this deal
        $attributedContent = $this->findAttributedContent(
            $dealData['deal_id'],
            $dealData['contact_ids']
        );

        foreach ($attributedContent as $content) {
            ContentAttributionEvent::create([
                'team_id' => $content->team_id,
                'taggable_type' => 'user_openai',
                'taggable_id' => $content->id,
                'event_type' => 'deal_closed',
                'hubspot_deal_id' => $dealData['deal_id'],
                'attributed_value' => $this->calculateAttribution(
                    $dealData['amount'],
                    count($attributedContent)
                ),
                'attribution_model' => 'linear',
                'occurred_at' => now(),
            ]);
        }
    }
}

4.3 AWS Marketplace Attribution

// app/Extensions/ContentTagging/System/Services/AwsMarketplaceAttributionService.php

class AwsMarketplaceAttributionService
{
    /**
     * When AWS Marketplace transaction occurs, trace back to content
     */
    public function attributeMarketplaceTransaction(array $transactionData): void
    {
        // 1. Find TrackedUrlCodes that led to this customer
        $trackedUrls = TrackedUrlCode::where('utm_source', 'aws_marketplace')
            ->whereJsonContains('metadata->customer_account', $transactionData['customer_account'])
            ->get();

        // 2. Find content linked to those tracked URLs
        foreach ($trackedUrls as $url) {
            if ($contentId = $url->metadata['content_id'] ?? null) {
                ContentAttributionEvent::create([
                    'team_id' => $url->user->team_id,
                    'taggable_type' => $url->metadata['content_type'] ?? 'user_openai',
                    'taggable_id' => $contentId,
                    'event_type' => 'deal_closed',
                    'aws_opportunity_id' => $transactionData['opportunity_id'] ?? null,
                    'attributed_value' => $transactionData['amount'],
                    'attribution_model' => 'last_touch',
                    'event_metadata' => [
                        'aws_product_code' => $transactionData['product_code'],
                        'aws_offer_id' => $transactionData['offer_id'],
                        'agreement_type' => $transactionData['agreement_type'],
                    ],
                    'occurred_at' => $transactionData['transaction_date'],
                ]);
            }
        }
    }

    /**
     * Generate AWS-compatible tags for content export
     */
    public function generateAwsTags(UserOpenai $content): array
    {
        $awsTags = [];

        foreach ($content->tags as $assignment) {
            $tag = $assignment->definition;
            if ($awsKey = $tag->external_mappings['aws_tag_key'] ?? null) {
                $awsTags[$awsKey] = $tag->slug;
            }
        }

        // Add standard Vell attribution tags
        $awsTags['vell:content-id'] = $content->id;
        $awsTags['vell:content-type'] = $content->metadata['content_type'] ?? 'unknown';
        $awsTags['vell:created-date'] = $content->created_at->format('Y-m-d');

        return $awsTags;
    }
}

4.4 Integration Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                    COMPLETE ATTRIBUTION FLOW                                │
└─────────────────────────────────────────────────────────────────────────────┘

     CONTENT CREATION                    DISTRIBUTION                    CONVERSION
     ─────────────────                   ────────────                    ──────────
           │                                  │                              │
           ▼                                  ▼                              ▼
    ┌──────────────┐                  ┌──────────────┐              ┌──────────────┐
    │   AI Agent   │                  │   Social     │              │   Customer   │
    │   Creates    │                  │   Post       │              │   Clicks     │
    │   Content    │                  │   Published  │              │   Link       │
    └──────────────┘                  └──────────────┘              └──────────────┘
           │                                  │                              │
           ▼                                  ▼                              ▼
    ┌──────────────┐                  ┌──────────────┐              ┌──────────────┐
    │ Auto-tag by  │                  │ TrackedUrl   │              │ Attribution  │
    │ KB source,   │                  │ with UTMs    │              │ Event        │
    │ content type │                  │ from tags    │              │ Recorded     │
    └──────────────┘                  └──────────────┘              └──────────────┘
           │                                  │                              │
           ▼                                  ▼                              ▼
    ┌──────────────┐                  ┌──────────────┐              ┌──────────────┐
    │ User adds    │                  │ HubSpot      │              │ Lead Created │
    │ campaign &   │                  │ Campaign     │              │ in HubSpot/  │
    │ product tags │                  │ Synced       │              │ Salesforce   │
    └──────────────┘                  └──────────────┘              └──────────────┘
           │                                  │                              │
           └──────────────────────────────────┼──────────────────────────────┘
                                    ┌──────────────────┐
                                    │   DEAL CLOSES    │
                                    │                  │
                                    │ HubSpot webhook  │
                                    │ or AWS MP event  │
                                    └──────────────────┘
                                    ┌──────────────────┐
                                    │   ATTRIBUTION    │
                                    │    CALCULATED    │
                                    │                  │
                                    │ Content → Tags → │
                                    │ Campaign → Deal  │
                                    └──────────────────┘
                                    ┌──────────────────┐
                                    │   REPORTING      │
                                    │                  │
                                    │ "Blog post X     │
                                    │ influenced $150K │
                                    │ in closed deals" │
                                    └──────────────────┘

5. Permissions Model

5.1 Permission Matrix

┌─────────────────────┬────────────┬────────────┬────────────┬────────────┬────────────┐
│ Action              │ Member     │ Team Lead  │ Team Admin │ Org Admin  │ Platform   │
├─────────────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ View own content    │     ✓      │     ✓      │     ✓      │     ✓      │     ✓      │
│ View team content   │     ✓      │     ✓      │     ✓      │     ✓      │     ✓      │
│ View all content    │     ✗      │     ✗      │     ✗      │     ✓      │     ✓      │
├─────────────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ Tag own content     │     ✓      │     ✓      │     ✓      │     ✓      │     ✓      │
│ Tag team content    │     ✗      │     ✓      │     ✓      │     ✓      │     ✓      │
│ Tag all content     │     ✗      │     ✗      │     ✗      │     ✓      │     ✓      │
├─────────────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ Create tags         │     ✗      │     ✓      │     ✓      │     ✓      │     ✓      │
│ Edit team tags      │     ✗      │     ✗      │     ✓      │     ✓      │     ✓      │
│ Delete tags         │     ✗      │     ✗      │     ✓      │     ✓      │     ✓      │
│ Create system tags  │     ✗      │     ✗      │     ✗      │     ✗      │     ✓      │
├─────────────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ Bulk tag (own)      │     ✓      │     ✓      │     ✓      │     ✓      │     ✓      │
│ Bulk tag (team)     │     ✗      │     ✓      │     ✓      │     ✓      │     ✓      │
│ Bulk tag (all)      │     ✗      │     ✗      │     ✗      │     ✓      │     ✓      │
├─────────────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ AI backfill (own)   │     ✓      │     ✓      │     ✓      │     ✓      │     ✓      │
│ AI backfill (team)  │     ✗      │     ✓      │     ✓      │     ✓      │     ✓      │
│ AI backfill (all)   │     ✗      │     ✗      │     ✗      │     ✓      │     ✓      │
├─────────────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ View attribution    │  own only  │    team    │    team    │    all     │    all     │
│ Configure rules     │     ✗      │     ✗      │     ✓      │     ✓      │     ✓      │
│ Manage integrations │     ✗      │     ✗      │     ✗      │     ✓      │     ✓      │
└─────────────────────┴────────────┴────────────┴────────────┴────────────┴────────────┘

5.2 Permission Implementation

// app/Extensions/ContentTagging/System/Policies/ContentTagPolicy.php

class ContentTagPolicy
{
    public function create(User $user): bool
    {
        return $user->hasAnyRole(['team_lead', 'team_admin', 'org_admin', 'platform_admin']);
    }

    public function update(User $user, ContentTag $tag): bool
    {
        if ($tag->is_system) {
            return $user->hasRole('platform_admin');
        }

        return $user->hasAnyRole(['team_admin', 'org_admin', 'platform_admin'])
            && $user->belongsToTeam($tag->team_id);
    }

    public function assignToContent(User $user, ContentTag $tag, $content): bool
    {
        // Can always tag own content
        if ($content->user_id === $user->id) {
            return true;
        }

        // Team leads can tag team content
        if ($user->hasAnyRole(['team_lead', 'team_admin'])
            && $user->belongsToTeam($content->team_id)) {
            return true;
        }

        // Org admins can tag all org content
        return $user->hasAnyRole(['org_admin', 'platform_admin']);
    }

    public function bulkAssign(User $user, string $scope): bool
    {
        return match($scope) {
            'own' => true,
            'team' => $user->hasAnyRole(['team_lead', 'team_admin', 'org_admin']),
            'all' => $user->hasAnyRole(['org_admin', 'platform_admin']),
            default => false,
        };
    }
}

6. AI-Assisted Tagging

6.1 Auto-Classification Capability

// app/Extensions/ContentTagging/System/Services/Capabilities/AutoTagContentCapability.php

class AutoTagContentCapability extends BaseCapability
{
    public string $name = 'auto_tag_content';
    public string $description = 'Automatically classify and tag content using AI';

    protected array $requiredInputs = ['content_id'];

    protected array $optionalInputs = [
        'tag_categories' => ['campaign', 'product', 'source'],
        'confidence_threshold' => 0.75,
        'auto_apply' => false,
    ];

    public function execute(array $params): array
    {
        $content = UserOpenai::findOrFail($params['content_id']);
        $existingTags = ContentTag::where('team_id', $content->team_id)
            ->whereIn('category', $params['tag_categories'])
            ->get();

        // Build prompt for classification
        $prompt = $this->buildClassificationPrompt($content, $existingTags);

        // Get AI classification
        $response = $this->llm->complete($prompt, [
            'response_format' => ['type' => 'json_object'],
            'temperature' => 0.1,
        ]);

        $classification = json_decode($response, true);

        $suggestions = [];
        foreach ($classification['tags'] as $tagSuggestion) {
            $tag = $existingTags->firstWhere('slug', $tagSuggestion['slug']);

            if ($tag && $tagSuggestion['confidence'] >= $params['confidence_threshold']) {
                $suggestions[] = [
                    'tag_id' => $tag->id,
                    'tag_name' => $tag->name,
                    'confidence' => $tagSuggestion['confidence'],
                    'reason' => $tagSuggestion['reason'],
                    'evidence' => $tagSuggestion['evidence'] ?? [],
                ];

                // Auto-apply if above threshold and flag set
                if ($params['auto_apply'] && $tagSuggestion['confidence'] >= 0.90) {
                    ContentTagAssignment::firstOrCreate([
                        'taggable_type' => 'user_openai',
                        'taggable_id' => $content->id,
                        'tag_id' => $tag->id,
                    ], [
                        'assignment_source' => 'ai_auto',
                        'confidence_score' => $tagSuggestion['confidence'],
                    ]);
                }
            }
        }

        // Suggest new tags if needed
        $newTagSuggestions = $this->suggestNewTags($classification, $existingTags);

        return [
            'content_id' => $content->id,
            'suggestions' => $suggestions,
            'new_tag_suggestions' => $newTagSuggestions,
            'auto_applied' => $params['auto_apply'] ?
                collect($suggestions)->where('confidence', '>=', 0.90)->count() : 0,
        ];
    }

    protected function buildClassificationPrompt(UserOpenai $content, $existingTags): string
    {
        $tagList = $existingTags->map(fn($t) => [
            'slug' => $t->slug,
            'name' => $t->name,
            'category' => $t->category,
            'description' => $t->description,
        ])->toJson();

        return <<<PROMPT
        Analyze the following content and classify it using the available tags.

        CONTENT:
        Title: {$content->title}
        Type: {$content->metadata['content_type']}

        Body:
        {$content->output}

        AVAILABLE TAGS:
        {$tagList}

        INSTRUCTIONS:
        1. Identify which existing tags apply to this content
        2. Provide a confidence score (0.0-1.0) for each tag
        3. Explain why each tag applies
        4. If content clearly belongs to a category not covered by existing tags, suggest a new tag

        Respond in JSON format:
        {
          "tags": [
            {
              "slug": "tag-slug",
              "confidence": 0.95,
              "reason": "Content explicitly discusses...",
              "evidence": ["quote from content"]
            }
          ],
          "new_tag_suggestions": [
            {
              "name": "Suggested Tag Name",
              "category": "campaign|product|source|custom",
              "reason": "Why this tag should exist"
            }
          ]
        }
        PROMPT;
    }
}

6.2 Backfill Job Handler

// app/Extensions/ContentTagging/System/Jobs/AiTagBackfillJob.php

class AiTagBackfillJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        public int $teamId,
        public array $filters,
        public array $options,
        public string $jobId,
    ) {}

    public function handle(AutoTagContentCapability $capability): void
    {
        $query = UserOpenai::where('team_id', $this->teamId);

        // Apply filters
        if ($this->filters['untagged_only'] ?? false) {
            $query->whereDoesntHave('tagAssignments');
        }

        if ($from = $this->filters['date_from'] ?? null) {
            $query->where('created_at', '>=', $from);
        }

        if ($to = $this->filters['date_to'] ?? null) {
            $query->where('created_at', '<=', $to);
        }

        if ($types = $this->filters['content_types'] ?? null) {
            $query->whereIn('metadata->content_type', $types);
        }

        $progress = TagBackfillProgress::find($this->jobId);
        $progress->update(['status' => 'processing', 'total' => $query->count()]);

        $query->chunk(50, function ($contents) use ($capability, $progress) {
            foreach ($contents as $content) {
                try {
                    $result = $capability->execute([
                        'content_id' => $content->id,
                        'tag_categories' => $this->options['tag_categories'],
                        'confidence_threshold' => $this->options['auto_apply_threshold'],
                        'auto_apply' => true,
                    ]);

                    $progress->increment('processed');
                    $progress->increment('auto_tagged', $result['auto_applied']);

                    if (count($result['suggestions']) > $result['auto_applied']) {
                        $progress->increment('pending_review');

                        // Store pending suggestions for review
                        TagSuggestionQueue::create([
                            'backfill_job_id' => $this->jobId,
                            'content_id' => $content->id,
                            'suggestions' => $result['suggestions'],
                        ]);
                    }
                } catch (\Exception $e) {
                    $progress->increment('failed');
                    Log::error('AI tagging failed', [
                        'content_id' => $content->id,
                        'error' => $e->getMessage(),
                    ]);
                }
            }
        });

        $progress->update(['status' => 'completed', 'completed_at' => now()]);
    }
}

7. UI/UX Requirements

7.1 Tag Management Interface

┌─────────────────────────────────────────────────────────────────────────────┐
│ Content Tags                                                    + New Tag   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  🔍 Search tags...                              Filter: [All Categories ▼]  │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ CAMPAIGNS (8)                                              [+ Add]   │   │
│  ├─────────────────────────────────────────────────────────────────────┤   │
│  │ ● Q1 2026 Launch          47 items    ━━━━━━━━━━░░  $125K pipeline  │   │
│  │ ● AWS re:Invent 2025      32 items    ━━━━━━━░░░░░  $89K pipeline   │   │
│  │ ● ISV Accelerate          28 items    ━━━━━░░░░░░░  $67K pipeline   │   │
│  │ └─ Co-Sell Sprint Q1      12 items    ━━░░░░░░░░░░  $23K pipeline   │   │
│  │ └─ Partner Summit         8 items     ━░░░░░░░░░░░  $12K pipeline   │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ PRODUCTS (6)                                               [+ Add]   │   │
│  ├─────────────────────────────────────────────────────────────────────┤   │
│  │ ● Data Analytics Suite    23 items    ━━━━━━━━░░░░  $200K pipeline  │   │
│  │ ● Security Module         18 items    ━━━━━━░░░░░░  $150K pipeline  │   │
│  │ ● Integration Platform    15 items    ━━━━━░░░░░░░  $95K pipeline   │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ SOURCES (3)                                                [+ Add]   │   │
│  ├─────────────────────────────────────────────────────────────────────┤   │
│  │ ● Product KB              89 items    ━━━━━━━━━━━░  Best performer  │   │
│  │ ● Marketing KB            45 items    ━━━━━━━░░░░░                   │   │
│  │ ● Partner KB              23 items    ━━━━░░░░░░░░                   │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.2 Content Tagging Interface

┌─────────────────────────────────────────────────────────────────────────────┐
│ Blog Post: "5 Ways to Optimize Your AWS Marketplace Listing"               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  CURRENT TAGS                                                               │
│  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐                  │
│  │ ● Q1 Launch  ✕ │ │ ● AWS MP     ✕ │ │ ● Product KB ✕ │                  │
│  └────────────────┘ └────────────────┘ └────────────────┘                  │
│                                                                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  AI SUGGESTIONS                                        [Apply All] [Ignore] │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ ⚡ ISV Accelerate                              92% confidence  [+]   │   │
│  │   "Content discusses ISV benefits and acceleration strategies"       │   │
│  ├─────────────────────────────────────────────────────────────────────┤   │
│  │ ⚡ Listing Optimization                        87% confidence  [+]   │   │
│  │   "Primary topic is marketplace listing optimization"                │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  ADD TAGS                                                                   │
│  🔍 Search or create tag...                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Recent: Co-Sell Sprint, Partner Summit, Security Module             │   │
│  │ ─────────────────────────────────────────────────────────────────── │   │
│  │ Campaigns: Q1 Launch ✓, AWS re:Invent, ISV Accelerate               │   │
│  │ Products: Data Analytics, Security Module, Integration              │   │
│  │ ─────────────────────────────────────────────────────────────────── │   │
│  │ + Create "New Tag Name"                                              │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.3 Attribution Dashboard

┌─────────────────────────────────────────────────────────────────────────────┐
│ Content Attribution Dashboard                    Jan 1 - Jan 19, 2026  [▼] │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐          │
│  │   156            │  │   $1.25M         │  │   $450K          │          │
│  │   Content Pieces │  │   Pipeline       │  │   Revenue        │          │
│  │   ↑ 23% vs prev  │  │   Influenced     │  │   Attributed     │          │
│  └──────────────────┘  └──────────────────┘  └──────────────────┘          │
│                                                                             │
│  ATTRIBUTION BY CAMPAIGN                                                    │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Campaign            Content   Views    Leads   Pipeline   Revenue   │   │
│  ├─────────────────────────────────────────────────────────────────────┤   │
│  │ Q1 2026 Launch         47    12.5K      45    $450K      $125K  ██ │   │
│  │ ISV Accelerate         28     8.2K      32    $320K       $89K  █░ │   │
│  │ AWS re:Invent          32     6.1K      28    $280K       $67K  █░ │   │
│  │ Co-Sell Sprint         12     3.4K      15    $150K       $45K  ░░ │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  TOP PERFORMING CONTENT                                                     │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 1. "AWS Marketplace Success Guide"      Blog    $89K attributed     │   │
│  │    Tags: Q1 Launch, ISV Accelerate, Product KB                      │   │
│  │    12 deals influenced | 2,340 views | 3.8% conversion              │   │
│  ├─────────────────────────────────────────────────────────────────────┤   │
│  │ 2. "Co-Sell Partner Benefits"           Social  $67K attributed     │   │
│  │    Tags: Co-Sell Sprint, Partner KB                                 │   │
│  │    8 deals influenced | 1,890 views | 2.9% conversion               │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  KNOWLEDGE BASE PERFORMANCE                                                 │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                                                                     │   │
│  │  Product KB     ████████████████████████████░░░░░  $200K  (44%)    │   │
│  │  Marketing KB   ██████████████████░░░░░░░░░░░░░░░  $150K  (33%)    │   │
│  │  Partner KB     ██████████░░░░░░░░░░░░░░░░░░░░░░░  $100K  (22%)    │   │
│  │                                                                     │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.4 Bulk Tagging Interface

┌─────────────────────────────────────────────────────────────────────────────┐
│ Bulk Tag Content                                                    [Close] │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  SELECTED: 23 items                                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ ☑ Blog: AWS Marketplace Optimization...                             │   │
│  │ ☑ Social: Partner benefits announcement...                          │   │
│  │ ☑ Email: Q1 Campaign Newsletter...                                  │   │
│  │ ☑ ... and 20 more                                    [View All]     │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  ACTION                                                                     │
│  ○ Add tags to selected content                                            │
│  ○ Remove tags from selected content                                       │
│  ● Replace all tags on selected content                                    │
│                                                                             │
│  TAGS TO APPLY                                                              │
│  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐                  │
│  │ ● Q1 Launch  ✕ │ │ ● Product KB ✕ │ │ + Add tag...   │                  │
│  └────────────────┘ └────────────────┘ └────────────────┘                  │
│                                                                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  ⚠️  This will modify 23 content items. This action cannot be undone.      │
│                                                                             │
│                                            [Cancel]  [Apply to 23 Items]   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

8. Implementation Phases

Phase 1: Foundation ✅ COMPLETED

Objective: Core data model and basic tagging

  • Create database migrations
  • content_tags table
  • content_tag_assignments table
  • content_attribution_events table
  • content_tag_rules table
  • content_tag_suggestions table
  • content_tag_backfill_jobs table
  • Added plan_content_tags JSON column to plans table
  • Create Eloquent models
  • ContentTag model with relationships and plan feature constants
  • ContentTagAssignment model with bulk operations
  • ContentAttributionEvent model with multi-touch attribution
  • Add HasContentTags trait to UserOpenai
  • Build basic API endpoints via ContentTagController
  • CRUD for tags (index, store, show, update, destroy)
  • Assign/remove tags from content (assignTags, removeTags)
  • List content with tag filters
  • Bulk operations for Professional+ plans
  • Implement plan-gated feature system
  • Added PlanHelper methods for content tag feature checking
  • Added MenuService.planContentTagsMenu() for feature definitions
  • Feature tiers: Starter (basic), Professional (campaigns/attribution), Enterprise (AI/rules/integrations)
  • Add tag management UI (pending frontend implementation)
  • Tag list/create/edit/delete views
  • Tag assignment on content detail view

Files Created: - app/Extensions/ContentManager/Database/Migrations/2026_01_19_000001_create_content_tagging_tables.php - app/Extensions/ContentManager/System/Models/ContentTag.php - app/Extensions/ContentManager/System/Models/ContentTagAssignment.php - app/Extensions/ContentManager/System/Models/ContentAttributionEvent.php - app/Extensions/ContentManager/System/Models/Concerns/HasContentTags.php - app/Extensions/ContentManager/System/Services/ContentTaggingService.php - app/Extensions/ContentManager/System/Http/Controllers/ContentTagController.php

Files Modified: - app/Models/Plan.php - Added plan_content_tags support - app/Services/Common/MenuService.php - Added content tags menu - app/Helpers/Classes/PlanHelper.php - Added feature check methods - app/Extensions/ContentManager/System/ContentManagerServiceProvider.php - Added routes

Phase 2: Attribution Tracking ✅ COMPLETED

Objective: Connect content to business outcomes

  • Create attribution tables (completed in Phase 1)
  • content_attribution_events table
  • tag_rules table (for automation)
  • Build attribution event recording
  • Click tracking via enhanced TrackedUrlCode with content linking
  • View/click/conversion/deal event recording via ContentAttributionController
  • Deal linkage with multi-touch attribution models
  • Integrate with HubSpot
  • ContentAttributionHubSpotService for deal webhook processing
  • Contact-to-content influence tracking
  • Attribution events created on deal close
  • Custom HubSpot properties for content attribution metadata
  • Add UTM generation from tags
  • UtmTagService implementation with tag-to-UTM mapping
  • Auto-generate TrackedUrlCode with tag-derived UTMs
  • Multi-channel share package generation (social, email, blog, partner)
  • Build attribution reporting
  • AttributionReportService for dashboard data
  • Attribution by campaign, content type, source, KB
  • Top performing content identification
  • Trend analysis and export capabilities

Files Created: - app/Extensions/ContentManager/Database/Migrations/2026_01_19_000002_add_metadata_to_tracked_url_codes.php - app/Extensions/ContentManager/System/Services/UtmTagService.php - app/Extensions/ContentManager/System/Services/ContentAttributionHubSpotService.php - app/Extensions/ContentManager/System/Services/AttributionReportService.php - app/Extensions/ContentManager/System/Http/Controllers/ContentAttributionController.php

Files Modified: - app/Models/TrackedUrlCode.php - Added content linking, click attribution, metadata support - app/Models/UserOpenai.php - Added HasContentTags trait - app/Extensions/ContentManager/System/ContentManagerServiceProvider.php - Added attribution routes

API Endpoints Added: - GET /api/content-attribution/dashboard - Attribution dashboard summary - GET /api/content-attribution/by-campaign - Attribution grouped by campaign - GET /api/content-attribution/by-content-type - Attribution grouped by content type - GET /api/content-attribution/by-source - Attribution grouped by UTM source - GET /api/content-attribution/by-kb - Attribution grouped by Knowledge Base - GET /api/content-attribution/top-content - Top performing content - GET /api/content-attribution/trends - Attribution trends over time - POST /api/content-attribution/record - Record attribution event - POST /api/content-attribution/create-tracked-url - Create tracked URL with tag UTMs - GET /api/content-attribution/export - Export attribution data - POST /api/webhooks/hubspot/deal - HubSpot deal webhook handler

Phase 3: AI Tagging & Backfill ✅ COMPLETED

Objective: Intelligent automation

  • Build AutoTagContentCapability
  • Classification prompt engineering with team tag library
  • Confidence scoring (0.0-1.0 scale)
  • Evidence extraction with reasoning
  • Auto-apply threshold (0.85) for high-confidence tags
  • Minimum suggestion threshold (0.5)
  • Create backfill job system
  • ContentTagBackfillJob model for tracking backfill operations
  • ProcessAiTagBackfillJob queue job for batch processing
  • Progress tracking with real-time updates
  • Filtering by date, content type, generator, user
  • Auto-apply option for high-confidence suggestions
  • Build suggestion review system
  • ContentTagSuggestion model for review queue
  • Pending/accepted/rejected/expired status workflow
  • Bulk accept/reject operations
  • Accept all high-confidence suggestions endpoint
  • Expiration after 30 days
  • Create AiTaggingService for orchestration
  • Real-time tag suggestions for individual content
  • Backfill job management (start, cancel, status)
  • Suggestion queue management
  • Feature access control (Enterprise plan)
  • Add AI suggestion UI (pending frontend implementation)
  • Inline suggestions on content
  • Bulk review interface

Files Created: - app/Extensions/ContentManager/System/Services/Capabilities/AutoTagContentCapability.php - app/Extensions/ContentManager/System/Models/ContentTagSuggestion.php - app/Extensions/ContentManager/System/Models/ContentTagBackfillJob.php - app/Extensions/ContentManager/System/Jobs/ProcessAiTagBackfillJob.php - app/Extensions/ContentManager/System/Services/AiTaggingService.php - app/Extensions/ContentManager/System/Http/Controllers/AiTaggingController.php

Files Modified: - app/Models/UserOpenai.php - Added tagSuggestions() relationship - app/Extensions/ContentManager/System/ContentManagerServiceProvider.php - Added AI tagging routes

API Endpoints Added: - GET /api/ai-tagging/info - Feature info and stats - POST /api/ai-tagging/suggest - Get AI tag suggestions for content - POST /api/ai-tagging/apply - Apply suggested tags - GET /api/ai-tagging/backfill/active - Get active backfill job - GET /api/ai-tagging/backfill/history - Backfill job history - POST /api/ai-tagging/backfill - Start new backfill job - GET /api/ai-tagging/backfill/{jobId} - Get backfill status - POST /api/ai-tagging/backfill/{jobId}/cancel - Cancel backfill - GET /api/ai-tagging/suggestions - List pending suggestions - GET /api/ai-tagging/suggestions/stats - Suggestion statistics - POST /api/ai-tagging/suggestions/{id}/accept - Accept suggestion - POST /api/ai-tagging/suggestions/{id}/reject - Reject suggestion - POST /api/ai-tagging/suggestions/bulk-accept - Bulk accept - POST /api/ai-tagging/suggestions/bulk-reject - Bulk reject - POST /api/ai-tagging/suggestions/accept-high-confidence - Accept all high-confidence

Phase 4: Reporting & Analytics ✅ COMPLETED

Objective: Deliver actionable insights

  • Build attribution dashboard (completed in Phase 2)
  • Campaign performance view
  • Content ROI calculations
  • KB source comparison
  • Create export capabilities
  • CSV export with proper UTF-8 BOM for Excel
  • Excel-compatible TSV export
  • AWS tag format export (JSON with CLI/Terraform examples)
  • HubSpot property sync format export
  • HubSpot deals attribution export
  • Add team/permission features
  • Role-based tag permissions (Viewer, Editor, Manager, Admin)
  • Tag-level permissions (view, assign, edit, delete, manage)
  • Team-scoped views
  • Comprehensive audit logging with change tracking

Files Created: - app/Extensions/ContentManager/Database/Migrations/2026_01_19_000003_create_content_tag_audit_logs_table.php - app/Extensions/ContentManager/System/Models/ContentTagAuditLog.php - app/Extensions/ContentManager/System/Models/ContentTagUserRole.php - app/Extensions/ContentManager/System/Models/ContentTagPermission.php - app/Extensions/ContentManager/System/Services/AttributionExportService.php - app/Extensions/ContentManager/System/Services/ContentTagAuditService.php - app/Extensions/ContentManager/System/Policies/ContentTagPolicy.php - app/Extensions/ContentManager/System/Http/Controllers/ContentTagPermissionController.php

Files Modified: - app/Extensions/ContentManager/System/ContentManagerServiceProvider.php - Added routes and policy

API Endpoints Added:

Audit Logs: - GET /api/content-tags/audit-logs - Get audit logs with filters - GET /api/content-tags/audit-logs/export - Export audit logs as CSV - GET /api/content-tags/audit-summary - Get audit summary for dashboard - GET /api/content-tags/activity - Get team activity report - GET /api/content-tags/{id}/history - Get audit history for specific tag

Role Management: - GET /api/content-tags/roles - Get all user roles for team - GET /api/content-tags/my-role - Get current user's role and permissions - PUT /api/content-tags/roles/{userId} - Set user role - DELETE /api/content-tags/roles/{userId} - Remove user role

Tag-Level Permissions: - GET /api/content-tags/{id}/permissions - Get permissions for a tag - PUT /api/content-tags/{id}/permissions/{userId} - Set tag permissions for user

Advanced Exports: - GET /api/content-attribution/export/options - Get available export formats - GET /api/content-attribution/export/csv - Export attribution data as CSV - GET /api/content-attribution/export/excel - Export as Excel-compatible TSV - GET /api/content-attribution/export/aws - Export tags in AWS format - GET /api/content-attribution/export/hubspot - Export in HubSpot format


9. Success Metrics

Adoption Metrics

Metric Target Measurement
% content tagged > 80% Tagged / Total content
Tags per content 2-4 avg Sum tags / Content count
Active tag users > 60% Users who tagged / Total users
Backfill completion > 90% AI-tagged historical / Total historical

Attribution Metrics

Metric Target Measurement
Content with attribution > 50% Attributed / Total content
Avg touches per deal Track Touch events / Closed deals
Attribution coverage > 70% Deals with content touch / Total deals
Time to first attribution < 24h Avg time from content create to first event

Business Value Metrics

Metric Target Measurement
Pipeline per content Track trend Total pipeline / Content count
Revenue per campaign Track trend Attributed revenue / Campaign
KB ROI comparison Identify winner Revenue per content by KB source
Top content identification Weekly Ranked list of highest-value content

10. Risk Mitigation

Risk Likelihood Impact Mitigation
Low adoption Medium High Default auto-tagging, minimal friction UI
Attribution gaps High Medium Multi-touch models, conservative estimates
AI accuracy Medium Medium Human review queue, confidence thresholds
Integration failures Low High Async processing, retry logic, fallbacks
Performance at scale Low Medium Denormalized counts, efficient queries, caching

11. Appendix: Sample Queries

Get top-performing content by campaign

SELECT
    ct.name AS campaign_name,
    COUNT(DISTINCT uo.id) AS content_count,
    SUM(COALESCE(uo.metadata->'$.performance.views', 0)) AS total_views,
    SUM(COALESCE(uo.metadata->'$.performance.pipeline_influenced', 0)) AS pipeline,
    SUM(COALESCE(uo.metadata->'$.performance.revenue_attributed', 0)) AS revenue
FROM content_tags ct
JOIN content_tag_assignments cta ON ct.id = cta.tag_id
JOIN user_openai uo ON cta.taggable_type = 'user_openai'
    AND cta.taggable_id = uo.id
WHERE ct.category = 'campaign'
    AND ct.team_id = ?
GROUP BY ct.id, ct.name
ORDER BY revenue DESC;

Get content attribution for a specific deal

SELECT
    uo.id,
    uo.title,
    uo.metadata->'$.content_type' AS content_type,
    GROUP_CONCAT(ct.name) AS tags,
    cae.event_type,
    cae.attribution_weight,
    cae.attributed_value,
    cae.occurred_at
FROM content_attribution_events cae
JOIN user_openai uo ON cae.taggable_type = 'user_openai'
    AND cae.taggable_id = uo.id
LEFT JOIN content_tag_assignments cta ON cta.taggable_type = 'user_openai'
    AND cta.taggable_id = uo.id
LEFT JOIN content_tags ct ON cta.tag_id = ct.id
WHERE cae.hubspot_deal_id = ?
GROUP BY uo.id, cae.id
ORDER BY cae.occurred_at ASC;

12. Glossary

Term Definition
Attribution Connecting content to business outcomes (leads, deals, revenue)
First-touch Attribution model giving 100% credit to first content interaction
Last-touch Attribution model giving 100% credit to final content before conversion
Linear Attribution model giving equal credit to all content touches
Multi-touch Any attribution model considering multiple content interactions
Tag Metadata label applied to content for organization and tracking
Taggable Any content entity that can receive tags (polymorphic)
UTM Urchin Tracking Module - URL parameters for campaign tracking
Backfill Retroactively applying tags to existing content

13. Plan-Gated Feature Tiers

Content tagging features are gated by subscription plan tier to provide clear upgrade paths and value differentiation.

Feature Matrix by Plan Tier

Feature Key Feature Name Starter Professional Enterprise
content_tags_basic Basic Tagging
content_tags_campaigns Campaign Tags
content_tags_attribution Attribution Tracking
content_tags_bulk_operations Bulk Tag Management
content_tags_ai_tagging AI Auto-Tagging
content_tags_rules Automated Tagging Rules
content_tags_integrations External Integrations

Feature Descriptions

Starter Tier

  • Basic Tagging: Create custom, product, team, and source tags
  • Manual Tag Assignment: Apply tags to individual content items
  • Tag Search & Filter: Find content by tags

Professional Tier (adds)

  • Campaign Tags: Track content by marketing campaigns with UTM correlation
  • Attribution Tracking: Link content to deals, track pipeline and revenue influence
  • Bulk Tag Operations: Apply/remove tags to multiple content items at once

Enterprise Tier (adds)

  • AI Auto-Tagging: Automatic content classification using LLM analysis
  • Automated Tagging Rules: Create rules to auto-apply tags based on content attributes
  • External Integrations: Sync tags with HubSpot, Salesforce, and AWS

Tag Category Restrictions

Category Starter Professional Enterprise
custom
product
team
source
campaign

Implementation Details

Features are stored in the plans.plan_content_tags JSON column:

{
  "content_tags_basic": true,
  "content_tags_campaigns": true,
  "content_tags_attribution": true,
  "content_tags_bulk_operations": true,
  "content_tags_ai_tagging": false,
  "content_tags_rules": false,
  "content_tags_integrations": false
}

API Feature Check

// Check feature access in service
$service = app(ContentTaggingService::class)->forUser($user);

if (!$service->canUseCampaignTags()) {
    throw new \RuntimeException('Campaign tags require Professional plan.');
}

// Check in helper
if (!PlanHelper::canUseAttribution()) {
    return response()->json(['upgrade_required' => true], 403);
}

Upgrade Prompts

When users attempt to access gated features, the API returns:

{
  "success": false,
  "message": "Campaign tags require a Professional or Enterprise plan.",
  "upgrade_required": true,
  "required_tier": "professional"
}

Default Plan Configuration

Use MenuService::getDefaultContentTagsForTier() to initialize new plans:

// For a Professional plan
$plan->plan_content_tags = MenuService::getDefaultContentTagsForTier('professional');
// Returns: basic=true, campaigns=true, attribution=true, bulk=true, ai=false, rules=false, integrations=false

Document Version: 1.3 Last Updated: January 19, 2026 All Phases Complete: Foundation, Attribution, AI Tagging, Reporting & Analytics