Dynamic SEO Scoring Architecture¶
Overview¶
The Dynamic SEO Scoring system ensures that marketplace listing scores remain meaningful and accurate regardless of which external data sources are available. When DataForSEO APIs are unavailable (subscription lapsed, API outage, rate limits), the system dynamically adjusts scoring weights based on the confidence level of each data source rather than treating all sources as equally reliable.
Problem Statement¶
The Marketplace SEO Score (MSS) combines three components:
| Component | Ideal Weight | Data Source |
|---|---|---|
| Listing Quality (LQ) | 40% | Internal analysis (always available) |
| Backlink Authority (BA) | 25% | DataForSEO Backlinks API ($100/mo) |
| AI Visibility (AIV) | 35% | DataForSEO LLM Mentions API ($100/mo) |
When DataForSEO is unavailable, Bedrock/Claude can produce proxy scores by analyzing listing content. However, these estimates are inherently less reliable than real API measurements. The previous system treated Bedrock proxies with the same weight as measured data, which overstated confidence in the final score.
Solution: Confidence-Based Weight Multipliers¶
Architecture¶
+----------------------------+
| MarketplaceSeoScoringService|
+----------------------------+
|
resolveComponentSource() per component
|
v
+----------------------------+
| DynamicWeightCalculator |
+----------------------------+
| Inputs: |
| componentSources: { |
| lq: measured |
| ba: estimated |
| aiv: unavailable |
| } |
| Multipliers: |
| measured: 1.00 |
| estimated: 0.70 |
| unavailable: 0.00 |
+----------------------------+
|
v
+----------------------------+
| DynamicWeightResult |
+----------------------------+
| weights: {lq: 0.70, ba: 0.30}|
| adjustments: {...} |
| overallConfidence: "low" |
+----------------------------+
Data Flow¶
- Score calculation begins in
MarketplaceSeoScoringService::calculateScoreData() - Each component's score is calculated (DataForSEO API or Bedrock fallback)
resolveComponentSource()classifies each component:- API available + score > 0 =
measured - Bedrock used + score > 0 =
estimated - Otherwise =
unavailable DynamicWeightCalculator::calculate()applies confidence multipliers and normalizes- Final score uses the adjusted weights
DynamicWeightResultmetadata is persisted inraw_data.weight_adjustments
Confidence Tiers¶
| Tier | Multiplier | Meaning |
|---|---|---|
measured |
1.00 | Real data from DataForSEO subscription APIs |
estimated |
0.70 | Proxy score from Bedrock/Claude content analysis |
unavailable |
0.00 | No data source can provide a score |
The 0.70 multiplier for estimated was chosen because:
- Bedrock proxy scores correlate with real scores but have ~30% variance
- It's aggressive enough to shift weight toward measured components
- It's generous enough that estimated components still contribute meaningfully
- It's configurable via seo_weight_multiplier_estimated for tuning
Weight Math Example¶
Scenario: DataForSEO down, Bedrock active
Step 1: Raw weights (ideal weight x confidence multiplier)
LQ: 0.40 x 1.00 = 0.400 (measured - always internal)
BA: 0.25 x 0.70 = 0.175 (estimated via Bedrock)
AIV: 0.35 x 0.70 = 0.245 (estimated via Bedrock)
Total: 0.820
Step 2: Normalize to sum to 1.0
LQ: 0.400 / 0.820 = 0.4878 (48.8%)
BA: 0.175 / 0.820 = 0.2134 (21.3%)
AIV: 0.245 / 0.820 = 0.2988 (29.9%)
Step 3: Calculate final score
MSS = (0.4878 x LQ) + (0.2134 x BA) + (0.2988 x AIV)
Files¶
| File | Responsibility |
|---|---|
app/Services/DataForSEO/DynamicWeightCalculator.php |
Core weight adjustment logic |
app/Services/DataForSEO/DynamicWeightResult.php |
Immutable result with transparency metadata |
app/Services/DataForSEO/MarketplaceSeoScoringService.php |
Orchestrates scoring, calls calculator |
app/Services/DataForSEO/BedrockListingAnalyzer.php |
Produces proxy scores when APIs unavailable |
app/Services/DataForSEO/MarketplaceSeoScoreDTO.php |
Exposes scoring_confidence and weight_adjustments in API |
tests/Unit/Services/DynamicWeightCalculatorTest.php |
27 test cases covering all source permutations |
Configuration¶
| Setting | Default | Description |
|---|---|---|
seo_weight_multiplier_measured |
1.00 | Multiplier for DataForSEO (real) data |
seo_weight_multiplier_estimated |
0.70 | Multiplier for Bedrock (proxy) data |
seo_use_bedrock |
true | Whether to attempt Bedrock fallback at all |
API Response Fields¶
The scoring API response includes:
{
"scoring_confidence": "medium",
"weight_adjustments": {
"weights": { ... },
"adjustments": {
"<component>": {
"ideal_weight": 0.25,
"data_source": "estimated",
"confidence_multiplier": 0.70,
"raw_weight": 0.175,
"normalized_weight": 0.2134
}
},
"overall_confidence": "medium",
"component_sources": { ... },
"confidence_multipliers": { ... }
}
}
Consumers That Need Awareness¶
When modifying scoring weights or confidence logic, these files may need updates:
| File | What It Does |
|---|---|
resources/views/.../marketplace-listing-optimizer/index.blade.php |
Displays weight percentages and confidence badges in UI |
resources/views/.../dashboard/widgets/marketplace-seo.blade.php |
Dashboard widget uses weights for discoverability calculation |
app/Extensions/.../MarketplaceSeoInsightCapability.php |
AgentCore capability exposes confidence to AI agents |
app/Extensions/.../MarketplaceListingOptimizerCapability.php |
Defines capability-level weight constants (documentation only) |
app/Http/Controllers/.../MarketplaceSeoScoreController.php |
API controller returns DTO with weight data |
app/Http/Controllers/.../MarketplaceListingOptimizerController.php |
Unified optimizer controller |
app/Jobs/ProcessMarketplaceSeoScoreJob.php |
Async job calls scoring service |
docs/marketing/MARKETPLACE_SEO_SCORE.md |
Technical documentation for scoring methodology |
Design Decisions¶
-
Why not just drop estimated components entirely? Partners need scores even without DataForSEO. A Bedrock estimate at 70% weight is far more useful than no score at all.
-
Why 0.70 as the default multiplier? It's a pragmatic middle ground. Setting it too high (0.95) would make estimated scores misleadingly confident. Setting it too low (0.30) would make the score almost entirely listing-quality-driven when Bedrock is the fallback.
-
Why normalize after multiplying? Without normalization, the total weight would be less than 1.0, meaning scores would be deflated (e.g., max possible score of 82 instead of 100). Normalization preserves the 0-100 scale.
-
Why expose full metadata in the API? Transparency builds trust. Partners can see exactly why their weights shifted and what data sources contributed. This also enables UI to show contextual indicators.