735 lines
27 KiB
Markdown
735 lines
27 KiB
Markdown
# IGNY8 Phase 2: External Linker & Backlinks (02E)
|
||
## SAG-Based External Backlink Campaign Engine
|
||
|
||
**Document Version:** 1.0
|
||
**Date:** 2026-03-23
|
||
**Phase:** IGNY8 Phase 2 — Feature Expansion
|
||
**Status:** Build Ready
|
||
**Source of Truth:** Codebase at `/data/app/igny8/`
|
||
**Audience:** Claude Code, Backend Developers, Architects
|
||
|
||
---
|
||
|
||
## 1. CURRENT STATE
|
||
|
||
### External Linking Today
|
||
There is **no** backlink management in IGNY8. No external API integrations exist for link building platforms. No campaign generation, tracking, or KPI monitoring. Backlink building is entirely manual and external to the platform.
|
||
|
||
### What Exists
|
||
- `SAGBlueprint` and `SAGCluster` models (01A) — provide the hierarchy and cluster assignments for target page identification
|
||
- `Keywords` model (planner app) — provides search volume data for tier assignment
|
||
- `Content` model with `content_type` and `content_structure` — classifies pages for tiering
|
||
- `GSCMetricsCache` (02C) — provides organic traffic and impression data for KPI tracking
|
||
- `SAGLink` and `LinkMap` models (02D) — internal link data complements external strategy
|
||
- The `linker` app (02D) — provides app namespace for related models
|
||
|
||
### What Does Not Exist
|
||
- No SAGCampaign model, SAGBacklink model, or CampaignKPISnapshot model
|
||
- No page tier assignment system
|
||
- No country-specific strategy profiles
|
||
- No marketplace API integrations (FatGrid, PRNews.io, etc.)
|
||
- No campaign generation algorithm
|
||
- No anchor text planning
|
||
- No quality scoring for backlink opportunities
|
||
- No tipping point detection
|
||
- No dead link monitoring for placed backlinks
|
||
|
||
---
|
||
|
||
## 2. WHAT TO BUILD
|
||
|
||
### Overview
|
||
Build a SAG-based backlink campaign engine that generates country-specific link-building campaigns targeting hub pages. The system leverages the SAG hierarchy to focus backlinks on high-value pages (T1-T3) and lets internal linking (02D) distribute authority to supporting content.
|
||
|
||
### 2.1 Hub-Only Strategy (Core Principle)
|
||
|
||
Backlinks target ONLY T1-T3 pages (homepage + cluster hubs + key service/product pages). SAG internal linking (02D) distributes authority from hubs downstream to 70+ supporting pages per cluster.
|
||
|
||
**Budget Allocation:**
|
||
- 70-85% to T1-T3 (homepage, top hubs, products/services)
|
||
- 15-30% to T4-T5 (authority magnets: guides, tools, supporting articles)
|
||
- 0% to term/taxonomy pages (get authority via internal links)
|
||
|
||
Typically 20-30 target pages per site.
|
||
|
||
### 2.2 Page Tier Assignment
|
||
|
||
| Tier | Pages | Links/Page Target | Description |
|
||
|------|-------|-------------------|-------------|
|
||
| **T1** | Homepage (1 page) | 10-30 | Brand authority anchor |
|
||
| **T2** | Top 40% hubs by search volume | 5-15 | Primary money pages |
|
||
| **T3** | Remaining hubs + products/services | 3-10 | Supporting money pages |
|
||
| **T4** | Supporting blog articles | 1-4 | Content authority |
|
||
| **T5** | Authority magnets (guides, tools) | 2-6 | Link bait pages |
|
||
|
||
**Tier Assignment Algorithm:**
|
||
1. Load SAGBlueprint → identify all published hub pages
|
||
2. Sort hubs by total cluster keyword search volume (from Keywords model)
|
||
3. Top 40% of hubs = T2, remaining hubs = T3
|
||
4. Products/services pages = T3
|
||
5. Supporting blog articles = T4
|
||
6. Content with `content_structure` in (`guide`, `comparison`, `listicle`) and high word count = T5
|
||
|
||
### 2.3 Country-Specific Strategy Profiles
|
||
|
||
Four pre-built profiles with different timelines, budgets, and quality thresholds:
|
||
|
||
| Parameter | Pakistan (PK) | Canada (CA) | UK | USA |
|
||
|-----------|--------------|-------------|-----|------|
|
||
| Timeline | 8 months | 12 months | 14 months | 18 months |
|
||
| Budget range | $2-5K | $3-7K | $3-9K | $5-13K |
|
||
| Target DR | 25-30 | 35-40 | 35-40 | 40-45 |
|
||
| Quality threshold | ≥5/11 | ≥6/11 | ≥6/11 | ≥7/11 |
|
||
| Exact match anchor | 5-10% | 3-7% | 3-7% | 2-5% |
|
||
| Velocity phases | ramp→peak→cruise→maintenance | Same 4 phases | Same | Same |
|
||
|
||
**Anchor Text Mix by Country:**
|
||
|
||
| Anchor Type | PK | CA | UK | USA |
|
||
|-------------|-----|-----|-----|------|
|
||
| Branded | 30-35% | 35-40% | 35-40% | 35-45% |
|
||
| Naked URL | 15-20% | 15-20% | 15-20% | 15-20% |
|
||
| Generic | 15-20% | 15-20% | 15-20% | 15-20% |
|
||
| Partial Match | 15-20% | 12-18% | 12-18% | 10-15% |
|
||
| Exact Match | 5-10% | 3-7% | 3-7% | 2-5% |
|
||
| LSI/Topical | 5-10% | 5-10% | 5-10% | 5-8% |
|
||
| Brand+Keyword | — | 3-5% | 3-5% | 3-5% |
|
||
|
||
### 2.4 Campaign Generation Algorithm
|
||
|
||
1. Load SAGBlueprint → identify all published hub pages
|
||
2. Assign tiers based on search volume data (from Keywords model)
|
||
3. Select country profile → calculate `referring_domains_needed`
|
||
4. `links_per_tier = referring_domains_needed × tier_allocation_%`
|
||
5. `budget_estimate = links × cost_per_link × link_mix_%`
|
||
6. Distribute across monthly velocity curve (ramp → peak → cruise → maintenance)
|
||
7. Assign pages to months by priority (keyword difficulty, search volume, commercial value)
|
||
8. Pre-generate 3 anchor text variants per page per anchor type
|
||
9. Set quality requirements per country threshold
|
||
|
||
### 2.5 Marketplace Integrations
|
||
|
||
#### FatGrid API
|
||
- **Base URL:** `https://api.fatgrid.com/api/public`
|
||
- **Auth:** API key in request header
|
||
- **Endpoints:**
|
||
- Domain Lookup — DR, DA, traffic, niche for a domain
|
||
- Marketplace Browse — filterable by DR, traffic, price, niche
|
||
- Bulk Domain Lookup — up to 1,000 domains per request
|
||
- **15+ Aggregated Marketplaces:** Collaborator.pro, PRNews.io, Adsy.com, WhitePress.com, Bazoom.com, MeUp.com, etc.
|
||
- **Usage:** IGNY8 proxies FatGrid API calls to find and filter link placement opportunities
|
||
|
||
#### PR Distribution (3 Tiers)
|
||
| Tier | Provider | Price Range | Reach |
|
||
|------|----------|-------------|-------|
|
||
| PR Basic | EIN Presswire | $99-499/release | AP News, Bloomberg, 115+ US TV |
|
||
| PR Premium | PRNews.io | $500-5K/placement | Yahoo Finance, Forbes-tier publications |
|
||
| PR Enterprise | Linking News (white-label) | $500-2K/distribution | ABC, NBC, FOX, Yahoo, Bloomberg |
|
||
|
||
### 2.6 Quality Scoring (Per Backlink Opportunity)
|
||
|
||
**Auto-Checkable Factors (7 points):**
|
||
1. Organic traffic >500/month
|
||
2. Domain Rating / Domain Authority > country threshold
|
||
3. Indexed in Google
|
||
4. Not on known PBN/spam farm blocklist
|
||
5. Traffic trend stable or growing
|
||
6. Niche relevance to content topic
|
||
7. Dofollow link confirmed
|
||
|
||
**Manual Review Factors (4 points):**
|
||
8. Outbound links <100 on linking page
|
||
9. Niche relevance (editorial check)
|
||
10. Editorial quality of surrounding content
|
||
11. Dofollow confirmed (manual verification)
|
||
|
||
**Total: 0-11 points. Country thresholds:** PK ≥5, CA ≥6, UK ≥6, USA ≥7
|
||
|
||
### 2.7 Authority Tipping Point Detection
|
||
|
||
Monitor for 3+ simultaneous indicators:
|
||
- Domain Rating reached country target
|
||
- Pages with GSC impressions >100 but 0 SAGBacklinks start getting organic clicks
|
||
- Un-linked pages rank on page 2-3 (positions 11-30)
|
||
- New content ranks passively without dedicated backlinks
|
||
- Keywords in top 10 exceed threshold: PK 10+, UK/CA 15+, USA 20+
|
||
|
||
**When triggered:** Recommend: reduce link-building velocity, shift budget to content creation, enter maintenance mode.
|
||
|
||
### 2.8 Dead Link Monitoring
|
||
|
||
- Periodic HTTP checks on all placed backlinks (status=`live`)
|
||
- Status tracking: `live` → `dead` (404/403/removed) → `replaced`
|
||
- Impact scoring: estimate authority loss based on source DR and link type
|
||
- Auto-generate replacement recommendations
|
||
- Reserve 10-15% monthly budget for replacements
|
||
|
||
---
|
||
|
||
## 3. DATA MODELS & APIS
|
||
|
||
### 3.1 New Models
|
||
|
||
All models in the `linker` app (same app as 02D).
|
||
|
||
#### SAGCampaign (linker app)
|
||
|
||
```python
|
||
class SAGCampaign(SiteSectorBaseModel):
|
||
"""
|
||
Backlink campaign generated from SAG data + country profile.
|
||
"""
|
||
blueprint = models.ForeignKey(
|
||
'planner.SAGBlueprint',
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='backlink_campaigns'
|
||
)
|
||
country_code = models.CharField(
|
||
max_length=3,
|
||
help_text='PK, CA, UK, or USA'
|
||
)
|
||
status = models.CharField(
|
||
max_length=15,
|
||
choices=[
|
||
('draft', 'Draft'),
|
||
('active', 'Active'),
|
||
('paused', 'Paused'),
|
||
('completed', 'Completed'),
|
||
],
|
||
default='draft'
|
||
)
|
||
tier_assignments = models.JSONField(
|
||
default=dict,
|
||
help_text='{content_id: tier_level (T1/T2/T3/T4/T5)}'
|
||
)
|
||
total_links_target = models.IntegerField(default=0)
|
||
budget_estimate_min = models.DecimalField(
|
||
max_digits=10, decimal_places=2, default=0
|
||
)
|
||
budget_estimate_max = models.DecimalField(
|
||
max_digits=10, decimal_places=2, default=0
|
||
)
|
||
timeline_months = models.IntegerField(default=12)
|
||
monthly_plan = models.JSONField(
|
||
default=list,
|
||
help_text='[{month, links_target, pages[], budget}]'
|
||
)
|
||
anchor_text_plan = models.JSONField(
|
||
default=dict,
|
||
help_text='{content_id: [{text, type, allocated}]}'
|
||
)
|
||
country_profile = models.JSONField(
|
||
default=dict,
|
||
help_text='Full profile snapshot at campaign creation'
|
||
)
|
||
kpi_data = models.JSONField(
|
||
default=dict,
|
||
help_text='Monthly KPI snapshots summary'
|
||
)
|
||
started_at = models.DateTimeField(null=True, blank=True)
|
||
|
||
class Meta:
|
||
app_label = 'linker'
|
||
db_table = 'igny8_sag_campaigns'
|
||
```
|
||
|
||
**PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel
|
||
|
||
#### SAGBacklink (linker app)
|
||
|
||
```python
|
||
class SAGBacklink(SiteSectorBaseModel):
|
||
"""
|
||
Individual backlink record within a campaign.
|
||
Tracks from planning through placement to ongoing monitoring.
|
||
"""
|
||
campaign = models.ForeignKey(
|
||
'linker.SAGCampaign',
|
||
on_delete=models.CASCADE,
|
||
related_name='backlinks'
|
||
)
|
||
blueprint = models.ForeignKey(
|
||
'planner.SAGBlueprint',
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True
|
||
)
|
||
target_content = models.ForeignKey(
|
||
'writer.Content',
|
||
on_delete=models.CASCADE,
|
||
related_name='backlinks'
|
||
)
|
||
target_url = models.URLField()
|
||
target_tier = models.CharField(
|
||
max_length=3,
|
||
choices=[
|
||
('T1', 'Tier 1 — Homepage'),
|
||
('T2', 'Tier 2 — Top Hubs'),
|
||
('T3', 'Tier 3 — Other Hubs/Products'),
|
||
('T4', 'Tier 4 — Supporting Articles'),
|
||
('T5', 'Tier 5 — Authority Magnets'),
|
||
]
|
||
)
|
||
source_url = models.URLField(
|
||
blank=True,
|
||
default='',
|
||
help_text='May not be known at planning stage'
|
||
)
|
||
source_domain = models.CharField(max_length=255, blank=True, default='')
|
||
source_dr = models.IntegerField(null=True, blank=True)
|
||
source_traffic = models.IntegerField(null=True, blank=True)
|
||
anchor_text = models.CharField(max_length=200)
|
||
anchor_type = models.CharField(
|
||
max_length=20,
|
||
choices=[
|
||
('branded', 'Branded'),
|
||
('naked_url', 'Naked URL'),
|
||
('generic', 'Generic'),
|
||
('partial_match', 'Partial Match'),
|
||
('exact_match', 'Exact Match'),
|
||
('lsi', 'LSI/Topical'),
|
||
('brand_keyword', 'Brand + Keyword'),
|
||
]
|
||
)
|
||
link_type = models.CharField(
|
||
max_length=20,
|
||
choices=[
|
||
('guest_post', 'Guest Post'),
|
||
('niche_edit', 'Niche Edit'),
|
||
('pr_distribution', 'PR Distribution'),
|
||
('directory', 'Directory'),
|
||
('resource_page', 'Resource Page'),
|
||
('broken_link', 'Broken Link Building'),
|
||
('haro', 'HARO/Journalist Query'),
|
||
]
|
||
)
|
||
marketplace = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
default='',
|
||
help_text='fatgrid, prnews, ein, linking_news, manual'
|
||
)
|
||
cost = models.DecimalField(
|
||
max_digits=10, decimal_places=2, null=True, blank=True
|
||
)
|
||
quality_score = models.FloatField(
|
||
null=True,
|
||
blank=True,
|
||
help_text='0-11 quality score'
|
||
)
|
||
country_relevant = models.BooleanField(default=True)
|
||
date_ordered = models.DateField(null=True, blank=True)
|
||
date_live = models.DateField(null=True, blank=True)
|
||
date_last_checked = models.DateField(null=True, blank=True)
|
||
status = models.CharField(
|
||
max_length=15,
|
||
choices=[
|
||
('planned', 'Planned'),
|
||
('ordered', 'Ordered'),
|
||
('live', 'Live'),
|
||
('dead', 'Dead'),
|
||
('replaced', 'Replaced'),
|
||
('rejected', 'Rejected'),
|
||
],
|
||
default='planned'
|
||
)
|
||
notes = models.TextField(blank=True, default='')
|
||
|
||
class Meta:
|
||
app_label = 'linker'
|
||
db_table = 'igny8_sag_backlinks'
|
||
```
|
||
|
||
**PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel
|
||
|
||
#### CampaignKPISnapshot (linker app)
|
||
|
||
```python
|
||
class CampaignKPISnapshot(SiteSectorBaseModel):
|
||
"""
|
||
Monthly KPI snapshot for a backlink campaign.
|
||
Tracks domain metrics, link counts, keyword rankings, and tipping point indicators.
|
||
"""
|
||
campaign = models.ForeignKey(
|
||
'linker.SAGCampaign',
|
||
on_delete=models.CASCADE,
|
||
related_name='kpi_snapshots'
|
||
)
|
||
snapshot_date = models.DateField()
|
||
dr = models.FloatField(null=True, blank=True, help_text='Domain Rating')
|
||
da = models.FloatField(null=True, blank=True, help_text='Domain Authority')
|
||
referring_domains = models.IntegerField(default=0)
|
||
new_links_this_month = models.IntegerField(default=0)
|
||
links_by_tier = models.JSONField(default=dict, help_text='{T1: count, T2: count, ...}')
|
||
cost_this_month = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||
cost_per_link_avg = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||
keywords_top_10 = models.IntegerField(default=0)
|
||
keywords_top_20 = models.IntegerField(default=0)
|
||
keywords_top_50 = models.IntegerField(default=0)
|
||
organic_traffic = models.IntegerField(
|
||
null=True, blank=True,
|
||
help_text='From GSC via 02C GSCMetricsCache'
|
||
)
|
||
impressions = models.IntegerField(
|
||
null=True, blank=True,
|
||
help_text='From GSC via 02C GSCMetricsCache'
|
||
)
|
||
pages_ranking_without_backlinks = models.IntegerField(default=0)
|
||
tipping_point_indicators = models.JSONField(
|
||
default=dict,
|
||
help_text='{indicator_name: True/False}'
|
||
)
|
||
tipping_point_triggered = models.BooleanField(default=False)
|
||
|
||
class Meta:
|
||
app_label = 'linker'
|
||
db_table = 'igny8_campaign_kpi_snapshots'
|
||
```
|
||
|
||
**PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel
|
||
|
||
### 3.2 Migration
|
||
|
||
```
|
||
igny8_core/migrations/XXXX_add_backlink_models.py
|
||
```
|
||
|
||
**Operations:**
|
||
1. `CreateModel('SAGCampaign', ...)` — with index on country_code, status
|
||
2. `CreateModel('SAGBacklink', ...)` — with indexes on campaign, status, target_content
|
||
3. `CreateModel('CampaignKPISnapshot', ...)` — with index on campaign, snapshot_date
|
||
|
||
### 3.3 API Endpoints
|
||
|
||
All endpoints under `/api/v1/linker/` (extending the linker URL namespace from 02D):
|
||
|
||
#### Campaign Management
|
||
| Method | Path | Description |
|
||
|--------|------|-------------|
|
||
| GET | `/api/v1/linker/campaigns/?site_id=X` | List backlink campaigns |
|
||
| POST | `/api/v1/linker/campaigns/generate/` | AI-generate campaign from blueprint + country. Body: `{site_id, blueprint_id, country_code}`. |
|
||
| GET | `/api/v1/linker/campaigns/{id}/` | Campaign detail with monthly plan |
|
||
| PUT | `/api/v1/linker/campaigns/{id}/` | Update campaign (adjust plan, budget) |
|
||
| POST | `/api/v1/linker/campaigns/{id}/activate/` | Start campaign (set status=active, started_at) |
|
||
| POST | `/api/v1/linker/campaigns/{id}/pause/` | Pause active campaign |
|
||
|
||
#### KPI Tracking
|
||
| Method | Path | Description |
|
||
|--------|------|-------------|
|
||
| GET | `/api/v1/linker/campaigns/{id}/kpi/` | KPI snapshot timeline for campaign |
|
||
| POST | `/api/v1/linker/campaigns/{id}/kpi/snapshot/` | Record monthly KPI. Body: `{dr, da, referring_domains, ...}`. |
|
||
| GET | `/api/v1/linker/tipping-point/?campaign_id=X` | Tipping point analysis — current indicator state |
|
||
|
||
#### Backlink Records
|
||
| Method | Path | Description |
|
||
|--------|------|-------------|
|
||
| GET | `/api/v1/linker/backlinks/?campaign_id=X` | List backlinks with filters (status, tier, anchor_type) |
|
||
| POST | `/api/v1/linker/backlinks/` | Add backlink record. Body: `{campaign_id, target_content_id, ...}`. |
|
||
| PUT | `/api/v1/linker/backlinks/{id}/` | Update backlink status/details |
|
||
| POST | `/api/v1/linker/backlinks/check/` | Trigger dead link check for campaign. Body: `{campaign_id}`. |
|
||
|
||
#### Marketplace Proxy
|
||
| Method | Path | Description |
|
||
|--------|------|-------------|
|
||
| GET | `/api/v1/linker/marketplace/search/` | FatGrid marketplace search. Query params: `dr_min, traffic_min, price_max, niche`. |
|
||
| GET | `/api/v1/linker/marketplace/domain/{domain}/` | FatGrid domain lookup — DR, DA, traffic, niche. |
|
||
|
||
**Permissions:** All endpoints use `SiteSectorModelViewSet` permission patterns.
|
||
|
||
### 3.4 Campaign Generation Service
|
||
|
||
**Location:** `igny8_core/business/campaign_generation.py`
|
||
|
||
```python
|
||
class CampaignGenerationService:
|
||
"""
|
||
Generates a backlink campaign from SAG data + country profile.
|
||
"""
|
||
|
||
COUNTRY_PROFILES = {
|
||
'PK': {
|
||
'timeline_months': 8,
|
||
'budget_min': 2000, 'budget_max': 5000,
|
||
'target_dr': 25, 'quality_threshold': 5,
|
||
'exact_match_max': 0.10,
|
||
'tier_allocation': {'T1': 0.25, 'T2': 0.35, 'T3': 0.25, 'T4': 0.10, 'T5': 0.05},
|
||
# ... full profile
|
||
},
|
||
'CA': { ... },
|
||
'UK': { ... },
|
||
'USA': { ... },
|
||
}
|
||
|
||
def generate(self, site_id, blueprint_id, country_code):
|
||
"""
|
||
1. Load blueprint → published hub pages
|
||
2. Assign tiers by search volume
|
||
3. Select country profile
|
||
4. Calculate total links needed + budget
|
||
5. Build monthly plan with velocity curve
|
||
6. Pre-generate anchor text variants
|
||
7. Create SAGCampaign record
|
||
Returns SAGCampaign instance.
|
||
"""
|
||
pass
|
||
|
||
def _assign_tiers(self, blueprint, published_content):
|
||
"""Sort hubs by search volume, assign T1-T5."""
|
||
pass
|
||
|
||
def _build_monthly_plan(self, tier_assignments, country_profile):
|
||
"""Distribute links across months using velocity curve."""
|
||
pass
|
||
|
||
def _generate_anchor_plans(self, tier_assignments, country_profile):
|
||
"""Pre-generate 3 anchor text variants per page per type."""
|
||
pass
|
||
```
|
||
|
||
### 3.5 Quality Scoring Service
|
||
|
||
**Location:** `igny8_core/business/backlink_quality.py`
|
||
|
||
```python
|
||
class BacklinkQualityService:
|
||
"""
|
||
Scores backlink opportunities on 0-11 scale.
|
||
"""
|
||
|
||
def score(self, domain_data, country_code):
|
||
"""
|
||
Auto-check 7 factors:
|
||
1. Organic traffic >500/mo
|
||
2. DR/DA > country threshold
|
||
3. Indexed in Google
|
||
4. Not on blocklist
|
||
5. Traffic trend stable/growing
|
||
6. Niche relevance
|
||
7. Dofollow confirmed
|
||
Returns (score, breakdown).
|
||
"""
|
||
pass
|
||
|
||
def meets_threshold(self, score, country_code):
|
||
"""Check if score meets country minimum."""
|
||
pass
|
||
```
|
||
|
||
### 3.6 Tipping Point Detector
|
||
|
||
**Location:** `igny8_core/business/tipping_point.py`
|
||
|
||
```python
|
||
class TippingPointDetector:
|
||
"""
|
||
Monitors campaign KPI snapshots for authority tipping point indicators.
|
||
"""
|
||
|
||
def evaluate(self, campaign_id):
|
||
"""
|
||
Check 5 indicators against latest KPI data + GSC data.
|
||
If 3+ triggered, set tipping_point_triggered=True.
|
||
Returns {indicators: {name: bool}, triggered: bool, recommendation: str}
|
||
"""
|
||
pass
|
||
```
|
||
|
||
### 3.7 FatGrid API Client
|
||
|
||
**Location:** `igny8_core/integration/fatgrid_client.py`
|
||
|
||
```python
|
||
class FatGridClient:
|
||
"""
|
||
Client for FatGrid marketplace API.
|
||
API key stored in SiteIntegration or account-level settings.
|
||
"""
|
||
|
||
BASE_URL = 'https://api.fatgrid.com/api/public'
|
||
|
||
def __init__(self, api_key):
|
||
self.api_key = api_key
|
||
|
||
def search_marketplace(self, dr_min=None, traffic_min=None,
|
||
price_max=None, niche=None, limit=50):
|
||
"""Browse marketplace with filters."""
|
||
pass
|
||
|
||
def lookup_domain(self, domain):
|
||
"""Get DR, DA, traffic, niche for a domain."""
|
||
pass
|
||
|
||
def bulk_lookup(self, domains):
|
||
"""Lookup up to 1,000 domains."""
|
||
pass
|
||
```
|
||
|
||
### 3.8 Celery Tasks
|
||
|
||
**Location:** `igny8_core/tasks/backlink_tasks.py`
|
||
|
||
```python
|
||
@shared_task(name='check_backlink_status')
|
||
def check_backlink_status(campaign_id):
|
||
"""Weekly HTTP check on all 'live' backlinks. Updates status to 'dead' if 4xx/5xx."""
|
||
pass
|
||
|
||
@shared_task(name='record_kpi_snapshot')
|
||
def record_kpi_snapshot(campaign_id):
|
||
"""Monthly KPI recording: pull GSC data, calculate keyword rankings."""
|
||
pass
|
||
|
||
@shared_task(name='evaluate_tipping_point')
|
||
def evaluate_tipping_point(campaign_id):
|
||
"""Monthly, after KPI snapshot. Check tipping point indicators."""
|
||
pass
|
||
|
||
@shared_task(name='generate_replacement_recommendations')
|
||
def generate_replacement_recommendations(campaign_id):
|
||
"""Triggered when dead links detected. Generate replacement suggestions."""
|
||
pass
|
||
```
|
||
|
||
**Beat Schedule Additions:**
|
||
|
||
| Task | Schedule | Notes |
|
||
|------|----------|-------|
|
||
| `check_backlink_status` | Weekly (Thursday 3:00 AM) | HTTP check all live backlinks |
|
||
| `record_kpi_snapshot` | Monthly (1st of month, 5:00 AM) | Record KPI for all active campaigns |
|
||
| `evaluate_tipping_point` | Monthly (1st of month, 6:00 AM) | After KPI snapshot |
|
||
|
||
---
|
||
|
||
## 4. IMPLEMENTATION STEPS
|
||
|
||
### Step 1: Models
|
||
1. Create SAGCampaign, SAGBacklink, CampaignKPISnapshot in linker app (extends 02D)
|
||
2. Run migration
|
||
|
||
### Step 2: Country Profiles
|
||
1. Define 4 country profiles (PK, CA, UK, USA) as configuration data
|
||
2. Store as constants in `CampaignGenerationService` or as a seed data migration
|
||
|
||
### Step 3: Services
|
||
1. Implement `CampaignGenerationService` in `igny8_core/business/campaign_generation.py`
|
||
2. Implement `BacklinkQualityService` in `igny8_core/business/backlink_quality.py`
|
||
3. Implement `TippingPointDetector` in `igny8_core/business/tipping_point.py`
|
||
4. Implement `FatGridClient` in `igny8_core/integration/fatgrid_client.py`
|
||
|
||
### Step 4: API Endpoints
|
||
1. Add campaign, backlink, KPI, and marketplace endpoints to `igny8_core/urls/linker.py`
|
||
2. Create views: `CampaignViewSet`, `BacklinkViewSet`, `KPISnapshotView`, `TippingPointView`
|
||
3. Create `MarketplaceSearchView`, `MarketplaceDomainLookupView` (FatGrid proxy)
|
||
|
||
### Step 5: Celery Tasks
|
||
1. Implement 4 tasks in `igny8_core/tasks/backlink_tasks.py`
|
||
2. Add to Celery beat schedule (weekly backlink check, monthly KPI + tipping point)
|
||
|
||
### Step 6: Serializers & Admin
|
||
1. Create DRF serializers for SAGCampaign, SAGBacklink, CampaignKPISnapshot
|
||
2. Register models in Django admin
|
||
|
||
### Step 7: Credit Cost Configuration
|
||
Add to `CreditCostConfig` (billing app):
|
||
|
||
| operation_type | default_cost | description |
|
||
|---------------|-------------|-------------|
|
||
| `campaign_generation` | 2 | AI-generate campaign from blueprint + country |
|
||
| `backlink_audit` | 1 | Dead link check for campaign |
|
||
| `kpi_snapshot` | 0.5 | Monthly KPI recording |
|
||
| `dead_link_detection` | 1 | Dead link detection + replacement recommendations |
|
||
|
||
**Note:** FatGrid API calls consume FatGrid credits (separate billing — not IGNY8 credits). Store FatGrid API key in account-level settings or SiteIntegration.
|
||
|
||
---
|
||
|
||
## 5. ACCEPTANCE CRITERIA
|
||
|
||
### Campaign Generation
|
||
- [ ] Campaign generated from SAGBlueprint + country code
|
||
- [ ] Page tiers assigned based on search volume from Keywords model
|
||
- [ ] Budget estimates calculated from country profile + tier allocation
|
||
- [ ] Monthly plan distributed across velocity curve (ramp → peak → cruise → maintenance)
|
||
- [ ] Anchor text plan pre-generated with 3 variants per page per anchor type
|
||
- [ ] Country profile snapshot stored in campaign record
|
||
|
||
### Country Profiles
|
||
- [ ] All 4 profiles (PK, CA, UK, USA) defined with correct parameters
|
||
- [ ] Anchor text mix enforced per country (branded %, exact match %, etc.)
|
||
- [ ] Quality thresholds enforced per country (PK ≥5, CA ≥6, UK ≥6, USA ≥7)
|
||
- [ ] Timeline and budget ranges match specification
|
||
|
||
### Quality Scoring
|
||
- [ ] 7 auto-checkable factors scored per backlink opportunity
|
||
- [ ] Country-specific threshold enforcement
|
||
- [ ] Quality score stored on SAGBacklink record
|
||
|
||
### Tipping Point
|
||
- [ ] 5 indicators monitored from KPI data + GSC data
|
||
- [ ] Triggered when 3+ indicators simultaneous
|
||
- [ ] Recommendation generated when triggered (reduce velocity, shift to content)
|
||
- [ ] tipping_point_triggered flag set on KPI snapshot
|
||
|
||
### Dead Link Monitoring
|
||
- [ ] Weekly HTTP checks on all live backlinks
|
||
- [ ] Status transitions: live → dead, with date tracking
|
||
- [ ] Replacement recommendations generated for dead links
|
||
- [ ] 10-15% budget reserve recommended in campaign plan
|
||
|
||
### Marketplace Integration
|
||
- [ ] FatGrid marketplace search proxied through IGNY8 API
|
||
- [ ] FatGrid domain lookup returns DR, DA, traffic, niche
|
||
- [ ] API key stored securely (not exposed to frontend)
|
||
|
||
---
|
||
|
||
## 6. CLAUDE CODE INSTRUCTIONS
|
||
|
||
### File Locations
|
||
```
|
||
igny8_core/
|
||
├── modules/
|
||
│ └── linker/
|
||
│ └── models.py # Add SAGCampaign, SAGBacklink, CampaignKPISnapshot
|
||
├── business/
|
||
│ ├── campaign_generation.py # CampaignGenerationService
|
||
│ ├── backlink_quality.py # BacklinkQualityService
|
||
│ └── tipping_point.py # TippingPointDetector
|
||
├── integration/
|
||
│ └── fatgrid_client.py # FatGridClient
|
||
├── tasks/
|
||
│ └── backlink_tasks.py # Celery tasks
|
||
├── urls/
|
||
│ └── linker.py # Extend with campaign/backlink endpoints
|
||
└── migrations/
|
||
└── XXXX_add_backlink_models.py
|
||
```
|
||
|
||
### Conventions
|
||
- **PKs:** BigAutoField (integer) — do NOT use UUIDs
|
||
- **Table prefix:** `igny8_` on all new tables
|
||
- **App label:** `linker` (same app as 02D)
|
||
- **Celery app name:** `igny8_core`
|
||
- **URL pattern:** `/api/v1/linker/campaigns/...`, `/api/v1/linker/backlinks/...`, `/api/v1/linker/marketplace/...`
|
||
- **Permissions:** Use `SiteSectorModelViewSet` permission pattern
|
||
- **Model inheritance:** All new models extend `SiteSectorBaseModel`
|
||
- **Frontend:** `.tsx` files with Zustand stores
|
||
|
||
### Cross-References
|
||
| Doc | Relationship |
|
||
|-----|-------------|
|
||
| **02D** | Internal linking distributes authority from hub pages (backlink targets) to supporting content |
|
||
| **02C** | GSC data feeds KPIs (organic traffic, impressions, keyword positions) |
|
||
| **01A** | SAGBlueprint provides cluster hierarchy for tier assignment |
|
||
| **04A** | Managed services package includes backlink campaign management for clients |
|
||
| **01G** | SAG health monitoring can incorporate backlink campaign progress |
|
||
|
||
### Key Decisions
|
||
1. **Same `linker` app as 02D** — Internal and external linking share the same app since they're conceptually related; campaigns reference the same Content and Blueprint models
|
||
2. **Country profiles as code constants** — Stored in CampaignGenerationService class, not database, to prevent accidental modification; versioned with code
|
||
3. **FatGrid proxy** — Never expose FatGrid API key to frontend; all marketplace calls routed through IGNY8 backend
|
||
4. **KPI snapshots are manual + automatic** — Monthly auto-recording via Celery, but manual recording also supported for ad-hoc updates
|
||
5. **Separate billing for marketplace** — FatGrid credits are external; IGNY8 credits cover campaign generation, audits, and AI-powered anchor text generation
|