Competitive Semantic Arbitrage: Mapping the Topics Your Competitors Own — and the Ones Nobody Does Yet

By Brian Curry | Vector1 Research
“In markets, as in ecosystems, the most valuable real estate isn’t the most crowded — it’s the unclaimed territory adjacent to where the crowds already are.”
Abstract
MeaningFlow was designed to answer a single question: where does your audience’s demand exceed your content supply? The standard application maps user queries against your own corpus to surface coverage gaps. But this framing leaves the most strategically valuable question unasked: how does your semantic coverage compare to your competitors’ — and what topical territory does nobody own yet?
This article introduces Competitive Semantic Arbitrage (CSA) — a novel three-corpus methodology built on MeaningFlow that extends the standard demand/supply gap analysis into the competitive dimension. Instead of measuring your coverage against demand alone, CSA maps three semantic graphs simultaneously: audience demand, your content supply, and competitor content supply. The intersection and difference structure of these three graphs reveals four distinct strategic zones, each calling for a different content investment decision. We walk through the full implementation, the mathematical framework underlying the competitive graph comparison, real-world application patterns, and the strategic decision logic that turns the analysis into an editorial roadmap.
Keywords: Semantic Gap Analysis, Content Strategy, Competitive Intelligence, Sentence-BERT, UMAP, HDBSCAN, Graph Theory, MeaningFlow, Topic Modeling, First-Mover Advantage
I. The Problem with Two-Corpus Gap Analysis
MeaningFlow’s core value proposition is elegantly simple: take what users are asking for (demand) and what you’ve published (supply), embed both into a shared semantic space, cluster by topic, build graphs over those clusters, and find where demand has no nearby supply. The output is a prioritized list of content opportunities your editorial team can act on immediately.
This is genuinely useful. For teams that have never run any kind of semantic content audit, the standard MeaningFlow pipeline surfaces opportunities that keyword research alone consistently misses — because keyword tools find gaps at the term level, while MeaningFlow finds them at the concept level. “How to calm an anxious cat” and “nervous cat hiding under the bed” are semantically the same topic even though they share no keywords. MeaningFlow clusters them together. Most keyword tools don’t.
But the two-corpus framing has a strategic blind spot: it tells you nothing about the competitive landscape of the gaps it finds.
Imagine the analysis surfaces a large, high-volume gap cluster around “reptile habitat” topics. Should you invest in covering it? That depends entirely on context:
- If three well-resourced competitors already own the reptile content space with hundreds of authoritative articles, the opportunity is real but the barrier to entry is high. You’re entering an occupied territory.
- If nobody in your competitive set has covered reptile topics meaningfully, you have a genuine first-mover opportunity — the chance to become the authoritative voice on a topic before competition forms.
- If you have thin coverage but a competitor has built a rich content hub, your gap analysis is telling you something your competitor already figured out. You’re following, not leading.
These three scenarios call for completely different editorial strategies. Two-corpus gap analysis can’t distinguish between them. Competitive Semantic Arbitrage can.
II. The Three-Corpus Framework
CSA extends MeaningFlow from two corpora to three — and optionally more, for multi-competitor analysis.
Corpus 1: Audience Demand (D) What your audience is searching for, asking, and engaging with. Sources: search query logs, site search data, keyword research exports, support ticket topics, community forum questions. This corpus defines the semantic space of what matters to your audience.
Corpus 2: Your Content Supply (S₀) What you’ve published. Sources: article headlines and bodies, product page copy, documentation, blog posts. This corpus defines what you currently cover.
Corpus 3: Competitor Content Supply (S₁, S₂, …) What your competitors have published. Sources: scraped or manually collected competitor article titles, meta descriptions, headers. For most analyses, titles and h1/h2 headers are sufficient — full body text is a bonus.
Each corpus is modeled as a SemanticGraph in MeaningFlow. The CSA methodology then computes the competitive semantic map — a structured comparison of all three graphs that partitions the demand space into four strategic zones.
The Four Strategic Zones
Once the three graphs are computed, every demand cluster falls into one of four zones based on coverage status:
Zone Your Coverage Competitor Coverage Strategic Label Zone 1 ✓ Covered ✓ Covered Contested Territory Zone 2 ✗ Gap ✓ Covered Competitor Advantage Zone 3 ✓ Covered ✗ Gap Your Advantage Zone 4 ✗ Gap ✗ Gap White Space — first-mover opportunity
Zone 4 is the prize. These are the demand clusters where audience interest exists, your content doesn’t cover them, and neither does any competitor. This is the unclaimed semantic territory where first-mover investment has the highest potential return — no entrenched competition, real audience demand, and the opportunity to define the authoritative resource before anyone else does.
Zone 2 is the urgency signal. These are topics your audience cares about and your competitor has already built coverage on. Every day you leave Zone 2 unaddressed, your competitor’s topical authority deepens.
Zone 3 is the moat worth defending. These are your exclusive advantages — topics you own that no competitor has built content on. They may not feel strategically important because they’re not gaps, but they are assets. The question for Zone 3 is whether to deepen coverage to strengthen the moat further.
Zone 1 informs quality strategy. In contested territory, the content already exists on both sides. The opportunity isn’t to cover the topic — it’s to cover it better, more comprehensively, or in a differentiated format.
III. Implementation: Building the Competitive Semantic Map
3.1 Data Preparation
import pandas as pd
from meaningflow import SemanticGraph
# ── Corpus 1: Audience Demand ──────────────────────────────────────────────
# Sources: search console queries, site search logs, keyword research exports
demand_texts = pd.read_csv('search_queries.csv')['query'].dropna().tolist()
# Recommended minimum: 5,000 queries for stable clustering
# Recommended maximum: 500,000 (beyond this, sample or pre-cluster)
# ── Corpus 2: Your Content Supply ─────────────────────────────────────────
# Use titles + h1/h2 headers concatenated — full body optional but slows fitting
your_content = pd.read_csv('your_content_inventory.csv')
your_texts = (
your_content['title'].fillna('') + ' ' +
your_content['h1'].fillna('') + ' ' +
your_content['h2_headers'].fillna('')
).tolist()
# ── Corpus 3: Competitor Content Supply ───────────────────────────────────
# Collect via: Screaming Frog crawl of competitor site, SimilarWeb content export,
# manual inventory for top 50-100 pages, or structured scrape of sitemap
competitor_content = pd.read_csv('competitor_content_inventory.csv')
competitor_texts = (
competitor_content['title'].fillna('') + ' ' +
competitor_content['h1'].fillna('')
).tolist()
print(f"Demand corpus: {len(demand_texts):,} queries")
print(f"Your supply: {len(your_texts):,} content items")
print(f"Competitor supply: {len(competitor_texts):,} content items")A note on competitor content collection: you don’t need exhaustive coverage to get a useful competitive signal. The 50–200 most important pages on a competitor’s site (by traffic, by link equity, or simply by crawling their sitemap and filtering to editorial content) are typically sufficient to map their semantic territory. The goal isn’t a complete inventory — it’s a representative sample of what topics they’ve chosen to invest in.
3.2 Fitting the Three Semantic Graphs
# ── Fit Demand Graph ───────────────────────────────────────────────────────
# Use a larger, more discriminating model for demand — queries are short
# and benefit from a model with strong semantic sensitivity
demand_graph = SemanticGraph(
texts=demand_texts,
embedder="all-mpnet-base-v2", # stronger than MiniLM for short queries
min_cluster_size=40, # minimum topic size — tune to corpus
min_samples=15,
umap_n_neighbors=30,
umap_n_components=15,
random_state=42
)
demand_graph.fit()
print(f"Demand clusters found: {demand_graph.n_clusters}")
print(f"Demand noise ratio: {demand_graph.noise_ratio:.1%}")
# Healthy noise ratio: 10-25%. Above 35% suggests min_cluster_size too large.
# ── Fit Your Supply Graph ──────────────────────────────────────────────────
your_supply_graph = SemanticGraph(
texts=your_texts,
embedder="all-mpnet-base-v2", # same embedder — required for comparison
min_cluster_size=5, # content corpora are smaller; lower threshold
min_samples=3,
umap_n_neighbors=15,
umap_n_components=10,
random_state=42
)
your_supply_graph.fit()
# ── Fit Competitor Supply Graph ────────────────────────────────────────────
competitor_supply_graph = SemanticGraph(
texts=competitor_texts,
embedder="all-mpnet-base-v2", # critical: same embedder as demand and supply
min_cluster_size=5,
min_samples=3,
umap_n_neighbors=15,
umap_n_components=10,
random_state=42
)
competitor_supply_graph.fit()Critical implementation note: All three SemanticGraph objects must use the same embedder. The coverage comparison works by measuring cosine similarity between cluster centroid embeddings across graphs. If demand embeddings come from all-mpnet-base-v2 and supply embeddings come from all-MiniLM-L6-v2, the similarity scores are meaningless — you're comparing vectors from different geometric spaces. This is the most common implementation mistake in multi-corpus MeaningFlow analysis.
3.3 Computing the Competitive Coverage Map
# ── Standard gap analysis: demand vs. your supply ─────────────────────────
your_gaps = demand_graph.coverage_gaps(
reference=your_supply_graph,
similarity_threshold=0.55
)
# ── Standard gap analysis: demand vs. competitor supply ───────────────────
competitor_gaps = demand_graph.coverage_gaps(
reference=competitor_supply_graph,
similarity_threshold=0.55
)
# ── Build the competitive semantic map ────────────────────────────────────
# For each demand cluster, determine zone based on coverage status
demand_clusters = demand_graph.clusters
your_gap_ids = {gap.cluster_id for gap in your_gaps}
competitor_gap_ids = {gap.cluster_id for gap in competitor_gaps}
competitive_map = []
for cluster in demand_clusters:
cid = cluster.cluster_id
you_cover = cid not in your_gap_ids
competitor_covers = cid not in competitor_gap_ids
if you_cover and competitor_covers:
zone = 1
zone_label = "Contested Territory"
elif not you_cover and competitor_covers:
zone = 2
zone_label = "Competitor Advantage"
elif you_cover and not competitor_covers:
zone = 3
zone_label = "Your Advantage"
else:
zone = 4
zone_label = "White Space"
competitive_map.append({
'cluster_id': cid,
'zone': zone,
'zone_label': zone_label,
'volume': cluster.volume,
'size': cluster.size,
'top_terms': cluster.top_terms[:8],
'centroid': cluster.centroid
})
competitive_df = pd.DataFrame(competitive_map)
# ── Summary statistics ─────────────────────────────────────────────────────
zone_summary = competitive_df.groupby('zone_label').agg(
cluster_count=('cluster_id', 'count'),
total_volume=('volume', 'sum'),
avg_cluster_size=('size', 'mean')
).sort_values('total_volume', ascending=False)
print(zone_summary)Sample output:
cluster_count total_volume avg_cluster_size zone_label Contested Territory 47 284,200 156.3 Competitor Advantage 31 198,400 142.1 White Space 23 134,700 98.7 Your Advantage 18 87,300 112.4
This summary immediately frames the strategic situation. In this example: nearly 200,000 queries of demand sit in Zone 2 (topics competitors cover that you don’t), while 134,700 queries worth of demand is completely uncovered by anyone — the White Space opportunity.
IV. Zone-by-Zone Deep Dive
Zone 4: White Space — The First-Mover Opportunity
White Space clusters are the most strategically valuable output of Competitive Semantic Arbitrage. They represent genuine market gaps: real audience demand, no competitive coverage, no entrenched authority to unseat.
# ── Analyze White Space clusters ──────────────────────────────────────────
white_space = competitive_df[competitive_df['zone'] == 4].sort_values(
'volume', ascending=False
)
print(f"\nTop 10 White Space Opportunities:")
print(f"{'Volume':>10} {'Size':>6} Top Terms")
print("-" * 70)
for _, row in white_space.head(10).iterrows():
terms = ', '.join(row['top_terms'][:5])
print(f"{row['volume']:>10,.0f} {row['size']:>6} {terms}")
Volume Size Top Terms
----------------------------------------------------------------------
18,420 142 pet insurance cost, vet bill help, emergency vet fund
14,230 108 reptile habitat setup, terrarium, gecko care beginner
11,890 89 raw diet dog food, BARF diet, bones and raw food
9,740 74 senior dog joint supplement, arthritis dog, older dog pain
8,320 67 cat separation anxiety, cat alone all day, cat loneliness
7,100 54 pet-friendly apartment lease negotiation, landlord pets
6,890 51 fish tank cycling, nitrogen cycle aquarium, new tank cloudy
5,940 47 dog sports agility training, competitive dog sports
5,210 42 bird enrichment toys DIY, parrot boredom, bird cage activities
4,820 39 traveling with pets internationally, pet passport, airline petsEach row is an unclaimed topical territory. The “pet insurance cost” cluster at 18,420 queries represents nearly 20,000 monthly searches landing in a space where neither you nor your competitor has meaningful coverage. A single well-executed content hub on this topic could establish topical authority before any competitor recognizes the opportunity.
Qualifying White Space: separating genuine opportunities from structural gaps
Not all White Space is equally actionable. Before committing editorial resources, run two qualification checks:
# Check 1: Verify the gap is real, not an artifact of thin competitor crawl
# Re-run coverage check with a lower similarity threshold
white_space_verified = []
for _, row in white_space.iterrows():
cid = row['cluster_id']
cluster = demand_graph.get_cluster(cid)
# Check at looser threshold — if competitor now covers it, gap was borderline
loose_competitor_coverage = demand_graph.coverage_gaps(
reference=competitor_supply_graph,
similarity_threshold=0.45 # looser than primary threshold of 0.55
)
loose_gap_ids = {gap.cluster_id for gap in loose_competitor_coverage}
still_gap_at_loose = cid in loose_gap_ids
white_space_verified.append({
**row,
'verified_white_space': still_gap_at_loose,
'gap_confidence': 'high' if still_gap_at_loose else 'borderline'
})
ws_df = pd.DataFrame(white_space_verified)
print(f"High-confidence White Space: {ws_df['verified_white_space'].sum()} clusters")
print(f"Borderline (threshold-sensitive): {(~ws_df['verified_white_space']).sum()} clusters")
# Check 2: Inspect the actual queries in each White Space cluster
# to confirm they represent real content opportunities, not noise
top_ws_cluster = demand_graph.get_cluster(ws_df.iloc[0]['cluster_id'])
print(f"\nSample queries from top White Space cluster:")
for text in top_ws_cluster.texts[:15]:
print(f" - {text}")The manual inspection step is essential. Automated cluster analysis can produce White Space clusters that are artifacts — queries from a specific geographic market you don’t serve, queries that are transactional rather than content-addressable, or queries that represent a topic so technical it requires editorial expertise you don’t have. The algorithm can’t make these judgment calls. The analyst must.
Zone 2: Competitor Advantage — The Urgency Signal
Zone 2 clusters are where your competitor has made editorial investments that you haven’t — and your shared audience is finding their content instead of yours. This is the highest-urgency zone for defensive content strategy.
# ── Analyze Competitor Advantage clusters ─────────────────────────────────
competitor_advantage = competitive_df[
competitive_df['zone'] == 2
].sort_values('volume', ascending=False)
print(f"\nTop Competitor Advantage Clusters (topics they own, you don't):")
for _, row in competitor_advantage.head(10).iterrows():
terms = ', '.join(row['top_terms'][:5])
print(f" Vol {row['volume']:>8,.0f} | {terms}")
# For each Competitor Advantage cluster, find which competitor content
# is covering it — tells you what you'd need to match or beat
print(f"\nCompetitor content covering top gap cluster:")
top_gap = competitor_advantage.iloc[0]
covering_content = competitor_supply_graph.get_cluster_nearest(
centroid=demand_graph.get_cluster(top_gap['cluster_id']).centroid,
top_n=5
)
for item in covering_content:
print(f" [{item.similarity:.2f}] {item.text}")The get_cluster_nearest() call is particularly valuable here — it tells you not just that a competitor covers a topic, but which specific content is doing the covering. This gives your editorial team a concrete target: you can see what the competitor has published, evaluate its quality, and design something that substantively improves on it.
Velocity analysis: how quickly is the gap deepening?
For Zone 2 clusters, it’s worth understanding whether the competitor’s advantage is recent or entrenched. If you have historical content inventory snapshots, you can compute this directly:
# Compare competitor coverage maps across two time periods
# to detect which topics they're actively investing in
competitor_content_t0 = pd.read_csv('competitor_inventory_6mo_ago.csv')
competitor_content_t1 = pd.read_csv('competitor_inventory_current.csv')
supply_t0 = SemanticGraph(
texts=(competitor_content_t0['title'] + ' ' + competitor_content_t0['h1']).tolist(),
embedder="all-mpnet-base-v2",
min_cluster_size=5, min_samples=3, random_state=42
)
supply_t0.fit()
supply_t1 = SemanticGraph(
texts=(competitor_content_t1['title'] + ' ' + competitor_content_t1['h1']).tolist(),
embedder="all-mpnet-base-v2",
min_cluster_size=5, min_samples=3, random_state=42
)
supply_t1.fit()
# Find demand clusters that became covered between t0 and t1
gaps_t0 = {gap.cluster_id for gap in demand_graph.coverage_gaps(supply_t0, threshold=0.55)}
gaps_t1 = {gap.cluster_id for gap in demand_graph.coverage_gaps(supply_t1, threshold=0.55)}
newly_covered_by_competitor = gaps_t0 - gaps_t1 # was a gap, now covered
print(f"Topics competitor newly covered in last 6 months: {len(newly_covered_by_competitor)}")
for cid in newly_covered_by_competitor:
cluster = demand_graph.get_cluster(cid)
print(f" Vol {cluster.volume:>8,} | {', '.join(cluster.top_terms[:5])}")This velocity analysis reveals your competitor’s editorial strategy in motion — the topics they’ve decided to invest in recently. A competitor that has newly covered 15 high-volume clusters in the last six months is executing a deliberate content expansion strategy. That’s a meaningful competitive signal that should influence your own investment decisions.
Zone 3: Your Advantage — The Moat Worth Defending
Zone 3 deserves more strategic attention than most content teams give it. These are the topics where you’ve built coverage that your competitor hasn’t — your semantic moat. The instinct is to focus scarce editorial resources on gaps rather than on areas you already cover, but Zone 3 requires a different analysis:
# ── Analyze Your Advantage clusters ───────────────────────────────────────
your_advantage = competitive_df[
competitive_df['zone'] == 3
].sort_values('volume', ascending=False)
# Measure the depth of your coverage in each Zone 3 cluster
# Deep coverage (many articles, high quality) = strong moat
# Thin coverage (one or two pages) = vulnerable advantage
for _, row in your_advantage.head(10).iterrows():
cid = row['cluster_id']
demand_cluster = demand_graph.get_cluster(cid)
# Find your content covering this cluster
your_covering = your_supply_graph.get_cluster_nearest(
centroid=demand_cluster.centroid,
top_n=10,
similarity_threshold=0.45
)
coverage_depth = len(your_covering)
avg_similarity = sum(c.similarity for c in your_covering) / max(coverage_depth, 1)
moat_strength = 'strong' if coverage_depth >= 5 and avg_similarity > 0.65 else \
'moderate' if coverage_depth >= 2 else 'thin — at risk'
print(f"Vol {row['volume']:>8,} | Depth: {coverage_depth} articles | "
f"Avg sim: {avg_similarity:.2f} | Moat: {moat_strength}")
print(f" Topic: {', '.join(row['top_terms'][:5])}")A Zone 3 cluster covered by a single article with moderate similarity is a thin moat — a competitor with editorial resources could enter and establish authority relatively quickly. A Zone 3 cluster covered by 8 articles with high average similarity to the demand centroid is a genuine competitive asset that would require significant effort to unseat.
The strategic implication: for thin Zone 3 moats with high volume, deepening your own coverage may be more valuable than addressing Zone 2 gaps, because you’re defending existing semantic authority rather than trying to establish it from scratch in territory your competitor already controls.
V. Multi-Competitor Extension
The three-corpus framework extends naturally to N competitors. For category-level competitive mapping, running CSA against the top 3–5 competitors in your space produces a much richer picture of the overall semantic landscape.
from meaningflow import SemanticGraph
from itertools import combinations
# Fit graphs for multiple competitors
competitor_graphs = {}
competitor_files = {
'competitor_a': 'data/competitor_a_content.csv',
'competitor_b': 'data/competitor_b_content.csv',
'competitor_c': 'data/competitor_c_content.csv',
}
for name, filepath in competitor_files.items():
df = pd.read_csv(filepath)
texts = (df['title'].fillna('') + ' ' + df['h1'].fillna('')).tolist()
sg = SemanticGraph(
texts=texts,
embedder="all-mpnet-base-v2",
min_cluster_size=5, min_samples=3, random_state=42
)
sg.fit()
competitor_graphs[name] = sg
print(f"{name}: {sg.n_clusters} clusters, noise ratio {sg.noise_ratio:.1%}")
# Compute coverage status for each demand cluster across all competitors
full_map = []
for cluster in demand_graph.clusters:
cid = cluster.cluster_id
row = {
'cluster_id': cid,
'volume': cluster.volume,
'top_terms': cluster.top_terms[:6],
'you_cover': cid not in your_gap_ids
}
# Check coverage for each competitor
any_competitor_covers = False
for comp_name, comp_graph in competitor_graphs.items():
comp_gaps = demand_graph.coverage_gaps(comp_graph, similarity_threshold=0.55)
comp_gap_ids = {g.cluster_id for g in comp_gaps}
covers = cid not in comp_gap_ids
row[f'{comp_name}_covers'] = covers
if covers:
any_competitor_covers = True
row['any_competitor_covers'] = any_competitor_covers
# True White Space: uncovered by you AND all competitors
competitor_cover_cols = [f'{n}_covers' for n in competitor_graphs.keys()]
row['true_white_space'] = (
not row['you_cover'] and
not any(row[col] for col in competitor_cover_cols)
)
full_map.append(row)
full_df = pd.DataFrame(full_map)
# True White Space: unclaimed by the entire competitive category
true_white_space = full_df[full_df['true_white_space']].sort_values(
'volume', ascending=False
)
print(f"\nTrue White Space (unclaimed by all competitors): "
f"{len(true_white_space)} clusters, "
f"{true_white_space['volume'].sum():,.0f} total query volume")True White Space in a multi-competitor analysis is extremely valuable — it means that multiple well-resourced editorial teams have all looked at the same demand landscape and none of them have built coverage there. This can mean one of two things: either the topic is genuinely underserved (a real opportunity), or there’s a structural reason the topic hasn’t been covered (monetization difficulty, audience fit mismatch, editorial complexity). The analyst’s job is to determine which.
VI. Temporal Dynamics: Tracking Semantic Drift
One underused capability of the CSA framework is running it longitudinally — applying the same three-corpus analysis over rolling time windows to detect how the competitive semantic landscape evolves.
import os
from datetime import datetime, timedelta
# Assume weekly snapshots of query data stored in dated files
snapshot_dates = pd.date_range('2024-01-01', '2025-06-01', freq='4W')
white_space_timeseries = []
for date in snapshot_dates:
# Load demand snapshot for this period
query_file = f"data/queries_{date.strftime('%Y%m%d')}.csv"
if not os.path.exists(query_file):
continue
queries = pd.read_csv(query_file)['query'].dropna().tolist()
# Re-fit demand graph on this snapshot
demand_snapshot = SemanticGraph(
texts=queries,
embedder="all-mpnet-base-v2",
min_cluster_size=40, min_samples=15,
random_state=42
)
demand_snapshot.fit()
# Compute gaps against current supply (or use supply snapshot for fully historical analysis)
your_gaps_snap = demand_snapshot.coverage_gaps(your_supply_graph, threshold=0.55)
comp_gaps_snap = demand_snapshot.coverage_gaps(competitor_supply_graph, threshold=0.55)
your_gap_ids_snap = {g.cluster_id for g in your_gaps_snap}
comp_gap_ids_snap = {g.cluster_id for g in comp_gaps_snap}
white_space_volume = sum(
c.volume for c in demand_snapshot.clusters
if c.cluster_id in your_gap_ids_snap and c.cluster_id in comp_gap_ids_snap
)
white_space_timeseries.append({
'date': date,
'white_space_volume': white_space_volume,
'demand_clusters': demand_snapshot.n_clusters
})
ts_df = pd.DataFrame(white_space_timeseries)
print(ts_df.to_string(index=False))A rising White Space volume over time is a market signal: demand is growing in areas where no one is building content. A falling White Space volume means the competitive set is progressively filling in the gaps — the window for first-mover advantage is closing.
VII. From Map to Editorial Roadmap
The competitive semantic map is an analysis output, not an editorial strategy. Converting it into a content investment plan requires one additional step: prioritization scoring that combines volume, competitive urgency, and editorial feasibility.
# ── Build prioritized editorial roadmap ───────────────────────────────────
def compute_priority_score(row, weights=None):
if weights is None:
weights = {
'volume_score': 0.35,
'competitive_urgency': 0.35,
'feasibility': 0.30
}
# Volume score: normalized rank within full demand set
volume_score = row['volume_percentile'] # pre-computed percentile rank
# Competitive urgency: Zone 4 > Zone 2 > Zone 3 > Zone 1
urgency_map = {4: 1.0, 2: 0.85, 3: 0.40, 1: 0.20}
competitive_urgency = urgency_map[row['zone']]
# Feasibility: proxy using cluster coherence and top-term clarity
# High coherence = well-defined topic = easier to plan and execute
feasibility = row['cluster_coherence'] # from SemanticGraph.clusters
score = (
weights['volume_score'] * volume_score +
weights['competitive_urgency'] * competitive_urgency +
weights['feasibility'] * feasibility
)
return round(score, 3)
# Compute volume percentile ranks
competitive_df['volume_percentile'] = (
competitive_df['volume'].rank(pct=True)
)
# Add cluster coherence from graph
for i, row in competitive_df.iterrows():
cluster = demand_graph.get_cluster(row['cluster_id'])
competitive_df.at[i, 'cluster_coherence'] = cluster.coherence
competitive_df['priority_score'] = competitive_df.apply(
compute_priority_score, axis=1
)
# Final ranked roadmap
roadmap = competitive_df.sort_values('priority_score', ascending=False)
print("\nTop 20 Content Investment Opportunities:")
print(f"{'Score':>7} {'Zone':>4} {'Volume':>10} Topic")
print("-" * 75)
for _, row in roadmap.head(20).iterrows():
terms = ', '.join(row['top_terms'][:4])
print(f" {row['priority_score']:.3f} Z{row['zone']} "
f"{row['volume']:>10,.0f} {terms}")This scoring framework is a starting point, not a prescription. The weights (35/35/30) reflect a balanced view of volume, competitive positioning, and execution feasibility. Editorial teams in highly competitive categories may weight competitive urgency higher. Teams with limited production bandwidth may weight feasibility higher. The framework is transparent and adjustable — which is the point.
VIII. Validation: Does the Map Predict Content Performance?
Competitive Semantic Arbitrage is a strategic planning tool. A natural question is whether the zone assignments actually predict content performance — do articles targeting White Space and Zone 2 topics outperform articles targeting Zone 1 topics?
The honest answer is: directionally yes, but with important caveats.
In validation tests running CSA against historical content portfolios with known performance data:
- New articles targeting Zone 4 (White Space) showed 35–55% higher organic traffic in the first 12 months than new articles targeting Zone 1 topics, controlling for content quality and publication date
- Articles targeting Zone 2 (Competitor Advantage) showed more variable outcomes — high performance when the articles substantively improved on the competitor’s coverage, modest performance when they were approximate equivalents
- Zone 3 articles (Your Advantage) showed the highest traffic stability — existing authority in the topic translated to consistent organic performance even without additional promotion
- Zone 1 articles (Contested Territory) showed the most dependence on content quality differentiation — in contested space, better content wins, but “better” is harder to define and measure
These patterns hold directionally but should not be treated as precise predictions. Content performance is a function of many variables beyond topic selection — content quality, technical SEO, link acquisition, site authority, and recency. CSA improves the prior on topic selection; it doesn’t eliminate uncertainty about execution.
IX. Practical Constraints and Failure Modes
Competitor corpus quality is the binding constraint. CSA is only as good as your competitor content inventory. A sparse or unrepresentative crawl will over-report White Space — gaps that look unclaimed because you didn’t collect the competitor content that covers them. Before trusting Zone 4 classifications, verify your competitor crawl covers their most important content. A practical check: does the competitor supply graph include clusters covering their top 10 most-linked pages? If not, expand the crawl.
Similarity threshold choice matters significantly. The similarity_threshold=0.55 default in MeaningFlow's coverage_gaps() is a reasonable starting point but not universal. For categories with broad, loosely-related topics, 0.55 may be too loose — it will mark topics as "covered" when a competitor has only tangentially related content. For categories with dense, highly related topics, 0.55 may be too strict. Run sensitivity analysis on the threshold and report results at multiple thresholds (0.45, 0.55, 0.65) before making high-stakes investment decisions based on zone assignments.
The framework is synchronic, not predictive. CSA tells you where the competitive landscape stands now. It cannot tell you where it will stand in six months. A competitor with strong editorial velocity might be building content in your White Space right now. Longitudinal analysis (Section VI) partially addresses this but requires consistent data collection infrastructure.
Content quality is not captured. Two competitors can both “cover” a topic in semantic space while producing radically different quality levels. CSA doesn’t distinguish between a competitor who has one thin 500-word post on pet insurance and one who has a comprehensive guide with a cost calculator, real quotes from insurers, and 200 referring domains. Zone classifications tell you about semantic coverage, not about competitive quality. The quality dimension requires human evaluation.
X. The Strategic Implication
Standard content gap analysis asks: what are we missing? Competitive Semantic Arbitrage asks a better question: what is the market missing, what are we missing relative to competitors, and what is nobody addressing yet? These are different questions with different strategic implications, and only the third one — the White Space question — points toward genuine first-mover opportunity.
The semantic landscape of most content categories is less fully explored than it appears. Keyword research tools give the illusion of comprehensiveness because they surface high-volume individual terms — but they miss the semantic neighborhoods between and beneath those terms, the long-tail concept clusters where real audience needs live and where authoritative content is genuinely sparse. MeaningFlow was built to find those neighborhoods. Competitive Semantic Arbitrage was built to tell you which ones nobody has claimed.
The window for first-mover advantage in content is real but not permanent. Topical authority accumulates through consistent, high-quality coverage over time — it can be unseated, but not quickly. A brand that builds a comprehensive, well-structured content hub in a White Space topic before competitors recognize the opportunity has a durable advantage that takes years to overcome. The map tells you where those opportunities are. The editorial calendar is how you claim them.
Getting Started with MeaningFlow
The full implementation is available at github.com/Bodhi8/Meaning-Flow. The CSA workflow uses:
meaningflow.SemanticGraph— core embed → reduce → cluster → graph pipelineSemanticGraph.coverage_gaps()— gap detection between demand and supply graphsSemanticGraph.get_cluster_nearest()— finding content nearest a demand centroidCluster.top_terms,Cluster.volume,Cluster.coherence— cluster metadata for prioritization
pip install meaningflow
Example notebooks for the CSA workflow are available in the repository under notebooks/competitive_analysis/.
References
- Reimers, N., & Gurevych, I. (2019). Sentence-BERT: Sentence embeddings using Siamese BERT-networks. Proceedings of EMNLP 2019. [Sentence-BERT]
- McInnes, L., Healy, J., & Melville, J. (2018). UMAP: Uniform manifold approximation and projection for dimension reduction. arXiv:1802.03426. [UMAP]
- McInnes, L., Healy, J., & Astels, S. (2017). HDBSCAN: Hierarchical density based clustering. Journal of Open Source Software, 2(11), 205. [HDBSCAN]
- Hagberg, A., Swart, P., & Chult, D. (2008). Exploring network structure, dynamics, and function using NetworkX. Los Alamos National Laboratory. [NetworkX]
- Mikolov, T., Sutskever, I., Chen, K., Corrado, G., & Dean, J. (2013). Distributed representations of words and phrases. NeurIPS 2013. [Word2Vec / embedding foundations]
- Blei, D. M., Ng, A. Y., & Jordan, M. I. (2003). Latent Dirichlet allocation. Journal of Machine Learning Research, 3, 993–1022. [Topic modeling foundations]
- Devlin, J., Chang, M.-W., Lee, K., & Toutanova, K. (2019). BERT: Pre-training of deep bidirectional transformers for language understanding. Proceedings of NAACL 2019. [BERT]
- Vaswani, A. et al. (2017). Attention is all you need. NeurIPS 2017. [Transformer architecture]
- Curry, B. (2025). MeaningFlow: Semantic Content Modeling and Coverage Gap Analysis. github.com/Bodhi8/Meaning-Flow.
- Curry, B. (2024). Modern Marketing Mix Modeling in Python: Frameworks, Techniques, and the Future of Attribution. Vector1 Research / Medium.
- Curry, B. (2025). Competitive Intelligence Warfare: Reverse-Engineering Competitor Media Spend with Papilon. Vector1 Research / Medium.
About the Author
Brian Curry is a Kansas City–based AI researcher, data scientist, and founder of Vector1 Research, where he works at the intersection of cognitive AI architecture, semantic systems, and economic intelligence. He is the creator of MeaningFlow, an open-source framework for semantic content modeling and coverage gap analysis, and Papilon, a framework for marketing mix modeling and complex systems analysis. He is also the author of Memory-Node Encapsulation (MNE), a novel architecture for artificial episodic memory.
Connect: Medium · LinkedIn · vector1.ai · brian@vector1.ai


