Keyword tracking, ranking, and opportunity scoring for SEO/SEM research
| Tier | domain |
| Role | unclassified (baselined) |
| Path | crates/domain/keywords |
| Edition | 2021 |
| Targets | domain_keywords |
| Public items | 13 across 1 module |
| Tests | 39 |
What it is for
domain-keywords: Keyword tracking, ranking, and opportunity scoring
Overview
This crate provides a comprehensive keyword research and tracking system for SEO/SEM analysis. It handles keyword management, ranking tracking across competitors, SERP feature detection, and opportunity scoring.
Layer
Domain - depends on: foundation-basemodels
Key Types
Keyword- A keyword being trackedKeywordRanking- A ranking record for a keyword/competitorKeywordOpportunity- Computed opportunity scoreKeywordMarketMetrics- Per-keyword, per-location market data (seasonality)KeywordVolumeHistory- Time-series search-volume observations for a keywordKeywordIntent- Search intent classificationKeywordStatus- Tracking status
Scoring System
Opportunity scores are calculated using:
- Volume score (30%): Based on monthly search volume
- Difficulty score (25%): Inverted ranking difficulty
- Competition score (25%): Based on competitor rankings
- Intent score (20%): Based on search intent type
Example
use domain_keywords::{
Keyword, KeywordRanking, KeywordIntent, KeywordStatus,
scoring::{calculate_opportunity, volume_score}
};
// Create a keyword
let keyword = Keyword::new(tenant_id, "plumber phoenix")
.with_search_volume(12_000)
.with_difficulty(45)
.with_intent(KeywordIntent::Commercial);
// Create a ranking
let ranking = KeywordRanking::new(keyword.id, competitor_id)
.with_rank(5)
.with_featured_snippet();
// Calculate opportunity
let opportunity = calculate_opportunity(&keyword, &ranking, None);
println!("Opportunity: {} ({})", opportunity.opportunity_score, opportunity.priority_tier);
Capabilities
scoring (other)
Opportunity scoring system for keywords.
| Item |
|---|
fn volume_score(search_volume : Option <i32>) -> i16 |
fn difficulty_score(difficulty : Option <i16>) -> i16 |
fn competition_score(rankings : & KeywordRanking) -> i16 |
fn competition_score_from_avg(avg_rank : Option <f64>, count : i32) -> i16 |
fn intent_score(intent : Option <KeywordIntent>) -> i16 |
fn calculate_opportunity(keyword : & Keyword, rankings : & KeywordRanking, client_rank : Option <i16>,) -> KeywordOpportunity |
fn generate_recommendation(opportunity_score : i16, client_rank : Option <i16>, best_competitor_rank : Option <i16>,) -> String |
ScoringWeights
Opportunity scoring system for keywords.
| Item |
|---|
pub struct ScoringWeights |
ScoringWeights :: fn default() -> Self |
ScoringWeights :: fn custom(volume : f64, difficulty : f64, competition : f64, intent : f64) -> Self |
ScoringWeights :: fn is_valid(& self) -> bool |
fn opportunity_score(volume : i16, difficulty : i16, competition : i16, intent : i16, weights : & ScoringWeights,) -> i16 |
fn opportunity_score_with_priority(volume : i16, difficulty : i16, competition : i16, intent : i16, priority : i16, weights : & ScoringWeights,) -> i16 |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
domain_keywords
scoring
Public surface
`scoring`
| Item | What it is |
|---|---|
pub struct ScoringWeights | Scoring weights for opportunity calculation. |
ScoringWeights :: fn default() -> Self | — |
ScoringWeights :: fn custom(volume : f64, difficulty : f64, competition : f64, intent : f64) -> Self | Create custom weights |
ScoringWeights :: fn is_valid(& self) -> bool | Check if weights sum to approximately 1.0. |
fn volume_score(search_volume : Option <i32>) -> i16 | Calculate volume score (0-100) from monthly search volume |
fn difficulty_score(difficulty : Option <i16>) -> i16 | Calculate difficulty score (0-100) from keyword difficulty |
fn competition_score(rankings : & KeywordRanking) -> i16 | Calculate competition score (0-100) based on competitor rankings |
fn competition_score_from_avg(avg_rank : Option <f64>, count : i32) -> i16 | Calculate competition score from pre-computed average rank. |
fn intent_score(intent : Option <KeywordIntent>) -> i16 | Calculate intent score (0-100) from keyword intent. |
fn opportunity_score(volume : i16, difficulty : i16, competition : i16, intent : i16, weights : & ScoringWeights,) -> i16 | Calculate the overall opportunity score. |
fn opportunity_score_with_priority(volume : i16, difficulty : i16, competition : i16, intent : i16, priority : i16, weights : & ScoringWeights,) -> i16 | Calculate opportunity score with priority multiplier |
fn calculate_opportunity(keyword : & Keyword, rankings : & KeywordRanking, client_rank : Option <i16>,) -> KeywordOpportunity | Calculate full opportunity record for a keyword. |
fn generate_recommendation(opportunity_score : i16, client_rank : Option <i16>, best_competitor_rank : Option <i16>,) -> String | Generate a recommendation based on the opportunity analysis. |
No pub use re-exports: every item above is declared in this crate.
Boundary
Reaches into foundation.
Shares tier domain with 41 other crates: domain-agreements, domain-ai-report, domain-billing, domain-catalog, domain-classify, domain-comments, domain-competitive-intel, domain-contact, … (41 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | domain |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/domain/keywords |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_domain["domain"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-basemodels` | foundation | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | no | always |
thiserror | ^2 | — | no | always |
uuid | ^1 | v4, serde | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full, test-util | no | always |
Build. None.
Depended on by. 1 workspace crate.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_application_keywords["application-keywords"] -->|uses| SELF SELF["domain-keywords"] SELF -->|runtime| n_foundation_basemodels["foundation-basemodels"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | domain_keywords | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | yes |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
1 workspace crate depends on this one: application-keywords.
Verification
| Kind | Count |
|---|---|
| Unit tests | 39 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
scoring | 10 | 0 | 0 |
What the tests establish, by name:
test_device—src/enums.rstest_keyword_intent—src/enums.rstest_keyword_source—src/enums.rstest_keyword_status—src/enums.rstest_priority_tier—src/enums.rstest_search_engine—src/enums.rstest_trend—src/enums.rstest_error_messages—src/errors.rstest_enums_roundtrip—src/lib.rstest_error_types—src/lib.rstest_filter_defaults—src/lib.rstest_keyword_lifecycle—src/lib.rstest_keyword_normalize—src/lib.rstest_opportunity_scoring—src/lib.rstest_priority_tier_from_score—src/lib.rstest_ranking_tracking—src/lib.rstest_scoring_calculations—src/lib.rstest_sort_options—src/lib.rstest_keyword_builder—src/models.rstest_keyword_market_metrics_builder—src/models.rstest_keyword_market_metrics_invalid_seasonality—src/models.rstest_keyword_market_metrics_new—src/models.rstest_keyword_new—src/models.rstest_keyword_opportunity—src/models.rstest_keyword_ranking—src/models.rstest_keyword_ranking_decline—src/models.rstest_keyword_status_changes—src/models.rstest_keyword_volume_history_builder—src/models.rstest_keyword_volume_history_new—src/models.rstest_seasonality_validation—src/models.rs- _… 9 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 12 | 13 |
Public modules with a //! block | 1 | 1 |
pie showData
title Public items with rustdoc
"Documented" : 12
"No rustdoc detected" : 1
Metrics
| Metric | Value |
|---|---|
| Rust source files | 6 |
| Source lines | 3254 |
| Code lines | 2411 |
| Public API items | 13 |
| Public modules | 1 |
| Tests | 39 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 8 |
| Workspace reverse dependencies | 1 |
pie showData
title Public API by kind
"function" : 9
"method" : 3
"struct" : 1
pie showData
title Rust source composition
"Code" : 2411
"Blank or comment" : 843
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.