1
This commit is contained in:
734
v2/V2-Execution-Docs/02E-linker-external-backlinks.md
Normal file
734
v2/V2-Execution-Docs/02E-linker-external-backlinks.md
Normal file
@@ -0,0 +1,734 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user