# OpportunityType Enum Categorization Criteria in SEO-Machine: Position-Based Classification

> Understand OpportunityType enum categorization criteria in SEO-Machine. Discover how keywords like quick_win and improvement are classified based on search position ranges.

- Repository: [Craig/seomachine](https://github.com/TheCraigHewitt/seomachine)
- Tags: internals
- Published: 2026-03-12

---

**The `OpportunityType` enum in SEO-Machine categorizes keywords into six strategic buckets—**quick_win**, **improvement**, **medium_term**, **new_content**, **declining**, and **under_performer**—based strictly on current search position ranges, with each category triggering specific scoring algorithms that assign 10-100 point position scores in the `OpportunityScorer` class.**

The `OpportunityType` enum is defined in [`data_sources/modules/opportunity_scorer.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/opportunity_scorer.py) (lines 12-19) and serves as the classification backbone for the SEO-Machine pipeline. This categorization system maps current search rankings to strategic SEO actions, enabling the automated scoring engine to calculate final priority levels ranging from `CRITICAL` to `SKIP` based on how easily each keyword can be improved. The enum is actively consumed by analysis scripts such as [`research_quick_wins.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/research_quick_wins.py) and [`research_competitor_gaps.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/research_competitor_gaps.py) to automate opportunity identification.

## The Six OpportunityType Categories and Position Criteria

The enum defines six distinct SEO opportunity buckets. Each category's criteria are determined by the keyword's current position in search results and the strategic goal for that ranking.

### QUICK_WIN: Position 11-20 (Second Page)

**Quick wins** target keywords ranking between **positions 11 and 20**—content sitting on the second page of search results. According to lines 5-16 of [`data_sources/modules/opportunity_scorer.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/opportunity_scorer.py), these represent high-impact, low-effort opportunities because a small optimization push can land them on page one.

In the `_calculate_position_score` method, the scoring system applies graduated point values:
- **Positions ≤12**: 100 points (perfect quick win)
- **Positions 13-15**: 85 points
- **Positions 16-18**: 70 points
- **Positions 19-20**: 55 points
- **All others**: 30 points

### IMPROVEMENT: First Page Positions 1-10 (Non-Top-3)

**Improvement** opportunities capture keywords already ranking on the first page (**positions 1-10**) but outside the top three. The strategic goal is moving these into positions 1-3 to maximize CTR and traffic share.

Lines 18-27 of the scorer implement the following position-based logic:
- **Positions 4-5**: 100 points
- **Positions ≤7**: 85 points
- **Positions ≤10**: 70 points
- **All others**: 40 points

### MEDIUM_TERM: Position 21-50 (Outside First Two Pages)

**Medium-term** opportunities require significant SEO work. These keywords sit **between positions 21 and 50**, well outside the first two pages. The strategic goal is reaching page one, which demands substantial content enhancement and link building.

Lines 29-38 of `_calculate_position_score` break down the tiered scoring:
- **Positions ≤30**: 70 points
- **Positions ≤40**: 50 points
- **Positions ≤50**: 30 points
- **All others**: 10 points

### NEW_CONTENT: Non-Ranking Competitor Gaps

**New content** opportunities identify keywords where your site does not currently rank (position **>100** or missing data) but competitors do. This represents a content gap requiring new page creation rather than existing page optimization.

Unlike graduated scoring, lines 40-43 assign new content a **flat 60-point baseline**, acknowledging the uniform effort required to enter the SERPs from scratch.

### DECLINING: Previously Strong Rankings

**Declining** opportunities flag keywords that historically performed well but are now dropping in rankings. While the enum definition exists for trend analysis downstream, lines 44-46 show the scoring engine treats these conservatively with a **default 50-point position score** when specific position data doesn't match other categories.

### UNDERPERFORMER: High Rank with Low CTR

**Underperformer** identifies keywords ranking on the first page (high position) but generating **click-through rates significantly below expected benchmarks** for that position. Like declining keywords, lines 44-46 indicate these receive a **default 50-point position score**, though they trigger different strategic workflows for title tag and meta description optimization.

## How `_calculate_position_score` Maps Categories to Priority

The `OpportunityScorer` class uses the opportunity type to determine the **position score**, which drives the final priority calculation (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, or `SKIP`). 

Each enum value routes through specific conditional logic in `_calculate_position_score`:
- **QUICK_WIN** checks `if position <= 12` first, cascading through 13-15, 16-18, and 19-20 thresholds
- **IMPROVEMENT** prioritizes the 4-5 range with 100 points, then relaxes for 6-7 and 8-10
- **MEDIUM_TERM** applies tiered deductions every 10 positions from 21-50
- **NEW_CONTENT**, **DECLINING**, and **UNDERPERFORMER** use flat scoring baselines

This position score then combines with search volume, keyword difficulty, SERP features, and trend data in the `calculate_score` method to produce the final prioritization.

## Practical Implementation: Scoring Each Opportunity Type

### Scoring a Quick Win Keyword

```python
from data_sources.modules.opportunity_scorer import OpportunityScorer, OpportunityType

scorer = OpportunityScorer()
keyword = {
    "position": 13,                # falls in 11-20 range

    "impressions": 1200,
    "clicks": 20,
    "ctr": 0.016,
    "commercial_intent": 2.0
}
result = scorer.calculate_score(
    keyword_data=keyword,
    opportunity_type=OpportunityType.QUICK_WIN,
    search_volume=2500,
    difficulty=30,
    serp_features=["featured_snippet"],
    cluster_value=70,
    trend_direction="rising",
    trend_percent=30,
)
print(result["priority"], result["final_score"])

```

### Scoring an Improvement Opportunity

For keywords already on page one but not in the top three:

```python
result = scorer.calculate_score(
    keyword_data={"position": 6, "impressions": 8000, "clicks": 120,
                  "ctr": 0.015, "commercial_intent": 1.8},
    opportunity_type=OpportunityType.IMPROVEMENT,
    search_volume=5000,
    difficulty=20,
    serp_features=["news_results"],
    cluster_value=80,
    trend_direction="stable",
)

```

### Scoring a Medium-Term Opportunity

For keywords outside the first two pages requiring substantial work:

```python
result = scorer.calculate_score(
    keyword_data={"position": 35, "impressions": 400, "clicks": 5,
                  "ctr": 0.012, "commercial_intent": 1.0},
    opportunity_type=OpportunityType.MEDIUM_TERM,
    search_volume=1500,
    difficulty=55,
    serp_features=None,
    cluster_value=50,
    trend_direction="declining",
    trend_percent=-25,
)

```

### Scoring a New Content Gap

For competitor gaps where no current ranking exists:

```python
result = scorer.calculate_score(
    keyword_data={"position": 101, "impressions": 0, "clicks": 0,
                  "ctr": 0, "commercial_intent": 0.5},
    opportunity_type=OpportunityType.NEW_CONTENT,
    search_volume=3000,
    difficulty=40,
    serp_features=["top_stories"],
    cluster_value=60,
    trend_direction="rising",
    trend_percent=80,
)

```

## Summary

- The `OpportunityType` enum in [`data_sources/modules/opportunity_scorer.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/opportunity_scorer.py) defines six SEO opportunity categories based on current search position ranges.
- **QUICK_WIN** targets positions 11-20 with graduated scoring from 55-100 points, emphasizing easy page-one potential.
- **IMPROVEMENT** focuses on first-page rankings (1-10) with 70-100 point scores to push content into top-3 positions.
- **MEDIUM_TERM** addresses positions 21-50 with 10-70 point scores, acknowledging the significant effort required.
- **NEW_CONTENT** assigns a flat 60-point baseline for non-ranking keywords identified through competitor gap analysis.
- **DECLINING** and **UNDERPERFORMER** use 50-point default scores while triggering specialized workflows for trend recovery or CTR optimization.
- The `_calculate_position_score` method implements these criteria at specific line ranges (5-46), feeding into the final priority calculation that drives SEO-Machine's recommendation engine.

## Frequently Asked Questions

### What file contains the OpportunityType enum definition?

The `OpportunityType` enum is defined in [`data_sources/modules/opportunity_scorer.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/opportunity_scorer.py) at lines 12-19. This file also contains the `OpportunityScorer` class and the `_calculate_position_score` method (lines 5-46) that implements the position-based categorization logic.

### How does SEO-Machine determine if a keyword is a quick win versus a medium-term opportunity?

The classification depends entirely on current ranking position. Keywords in **positions 11-20** are classified as `QUICK_WIN` because they require minimal effort to reach page one. Keywords in **positions 21-50** are classified as `MEDIUM_TERM` because they need significant content work and link building to become competitive. The `_calculate_position_score` method checks these ranges sequentially to assign the appropriate category and corresponding point value (100 points for position ≤12 quick wins versus 70 points for position ≤30 medium-term).

### What is the default position score for declining and underperformer keywords?

Both `DECLINING` and `UNDERPERFORMER` opportunity types receive a **default position score of 50 points** according to lines 44-46 of the scorer. While these categories exist primarily for trend analysis and CTR optimization workflows rather than position-based scoring, the 50-point baseline ensures they remain visible in the priority queue without artificially inflating or deflating their importance relative to position-based opportunities.

### Can the position score thresholds be customized in the OpportunityScorer class?

The current implementation in [`data_sources/modules/opportunity_scorer.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/opportunity_scorer.py) uses hardcoded thresholds in the `_calculate_position_score` method (e.g., position ≤12 = 100 points for quick wins). To modify these criteria, you would need to edit the source code directly in the method's conditional logic at lines 5-46, as the class does not expose these ranges as configuration parameters in the current version of the repository.