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:
- Tags all content with flexible, hierarchical metadata
- Links content to campaigns, deals, and revenue events
- Integrates with HubSpot, Salesforce, and AWS Marketplace data
- Provides multi-touch attribution modeling
- 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:
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:
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_tagstablecontent_tag_assignmentstablecontent_attribution_eventstablecontent_tag_rulestablecontent_tag_suggestionstablecontent_tag_backfill_jobstable- Added
plan_content_tagsJSON column toplanstable - Create Eloquent models
ContentTagmodel with relationships and plan feature constantsContentTagAssignmentmodel with bulk operationsContentAttributionEventmodel with multi-touch attribution- Add
HasContentTagstrait toUserOpenai - 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
PlanHelpermethods 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_eventstabletag_rulestable (for automation)- Build attribution event recording
- Click tracking via enhanced
TrackedUrlCodewith content linking - View/click/conversion/deal event recording via
ContentAttributionController - Deal linkage with multi-touch attribution models
- Integrate with HubSpot
ContentAttributionHubSpotServicefor 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
UtmTagServiceimplementation with tag-to-UTM mapping- Auto-generate
TrackedUrlCodewith tag-derived UTMs - Multi-channel share package generation (social, email, blog, partner)
- Build attribution reporting
AttributionReportServicefor 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
ContentTagBackfillJobmodel for tracking backfill operationsProcessAiTagBackfillJobqueue 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
ContentTagSuggestionmodel for review queue- Pending/accepted/rejected/expired status workflow
- Bulk accept/reject operations
- Accept all high-confidence suggestions endpoint
- Expiration after 30 days
- Create
AiTaggingServicefor 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