Skip to content

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

  1. Score calculation begins in MarketplaceSeoScoringService::calculateScoreData()
  2. Each component's score is calculated (DataForSEO API or Bedrock fallback)
  3. resolveComponentSource() classifies each component:
  4. API available + score > 0 = measured
  5. Bedrock used + score > 0 = estimated
  6. Otherwise = unavailable
  7. DynamicWeightCalculator::calculate() applies confidence multipliers and normalizes
  8. Final score uses the adjusted weights
  9. DynamicWeightResult metadata is persisted in raw_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

  1. 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.

  2. 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.

  3. 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.

  4. 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.