Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to Backlink Intelligence are documented here.
- Added conservative singular/plural anchor adaptation when the natural grammatical form already exists in source copy.
- Added destination-intent scoring so context specific to the destination topic receives more weight than generic anchor repetition.
- Added destination-fit and actual placed-anchor details to placement CLI output.
- Replaced mechanical target-title fallback sentences with concise destination-intent-aware editorial copy.

## 1.0.0 - 2026-08-30

Expand Down
78 changes: 71 additions & 7 deletions backlink_intelligence/placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,73 @@ def _simple_anchor_variants(anchor: str) -> list[str]:
return variants


def _fallback_anchor_case(anchor: str) -> str:
"""Use conservative editorial casing for generated fallback copy.

When a requested anchor begins with a short acronym followed by title-cased words,
preserve the acronym but lowercase the descriptive words. This turns ``AI Agent``
into ``AI agent`` without changing arbitrary brand or mixed-case anchors.
"""
words = anchor.strip().split()
if len(words) < 2:
return anchor.strip()
if not (words[0].isupper() and 1 < len(words[0]) <= 5):
return anchor.strip()

changed = False
output = [words[0]]
for word in words[1:]:
if word[:1].isupper() and word[1:].islower():
output.append(word.lower())
changed = True
else:
output.append(word)
return " ".join(output) if changed else anchor.strip()


def _contextual_fallback_sentence(
paragraph: str,
anchor: str,
target_url: str,
target_title: str,
) -> tuple[str, str, list[str]]:
"""Create concise deterministic fallback copy without dumping the target title."""
placed_anchor = _fallback_anchor_case(anchor)
linked = f"[{placed_anchor}]({target_url})"
target_lower = target_title.lower()
paragraph_lower = paragraph.lower()

cost_intent = any(term in target_lower for term in ("cost", "pricing", "price", "tco", "roi"))
cost_context = any(term in paragraph_lower for term in ("cost", "price", "pricing", "budget", "expense", "roi", "investment", "expensive"))

notes: list[str] = ["target_title_not_injected_into_source_copy"]
if placed_anchor != anchor:
notes.append("anchor_casing_adapted_for_generated_sentence")

if cost_intent and cost_context:
sentence = (
f"These factors are useful when estimating {linked} implementation costs, "
"ongoing operating expenses, and expected ROI."
)
notes.append("destination_intent_used_for_contextual_sentence")
return sentence, placed_anchor, notes

if cost_intent:
sentence = (
f"Businesses evaluating this type of automation should also account for {linked} costs, "
"including implementation, integrations, ongoing operation, and expected ROI."
)
notes.append("destination_intent_used_for_contextual_sentence")
return sentence, placed_anchor, notes

if any(term in target_lower for term in ("roadmap", "learning", "course", "guide")):
sentence = f"Readers who want a structured next step can explore this {linked} for more detail."
return sentence, placed_anchor, notes

sentence = f"Readers who want additional context can review this {linked} resource."
return sentence, placed_anchor, notes


def _compose_after(
paragraph: str,
anchor: str,
Expand Down Expand Up @@ -102,13 +169,10 @@ def _compose_after(
["anchor_adapted_to_source_grammar", "requested_anchor_not_used_verbatim"],
)

linked = f"[{anchor}]({target_url})"
topic = target_title.strip()
if topic and topic.lower() != anchor.lower():
sentence = f"For a more detailed resource on {topic}, see {linked}."
else:
sentence = f"For a more detailed resource on this topic, see {linked}."
return "contextual_sentence", paragraph.rstrip() + " " + sentence, anchor, []
sentence, placed_anchor, notes = _contextual_fallback_sentence(
paragraph, anchor, target_url, target_title
)
return "contextual_sentence", paragraph.rstrip() + " " + sentence, placed_anchor, notes


def _stem(term: str) -> str:
Expand Down
39 changes: 39 additions & 0 deletions tests/test_placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,45 @@ def test_destination_intent_prioritizes_cost_context(self, fetch):
self.assertEqual(item.destination_fit, "low")


@patch("backlink_intelligence.placement.fetch_page")
def test_contextual_sentence_avoids_target_title_dump(self, fetch):
source = parse_page(
"<main><p>AI automation does not have to be expensive, but the cost depends on what you want to build and the systems that need to be connected.</p></main>",
requested_url="https://s.com", final_url="https://s.com", status_code=200,
)
target = parse_page(
"<title>AI Agent Cost in 2026: Pricing, TCO and ROI Guide</title><h1>AI Agent Cost</h1><p>Pricing depends on implementation, integrations, operations, and expected ROI.</p>",
requested_url="https://t.com", final_url="https://t.com", status_code=200,
)
fetch.side_effect = [source, target]
item = suggest_placements("https://s.com", "https://t.com", "AI Agent", top_n=1)[0]
self.assertEqual(item.strategy, "contextual_sentence")
self.assertNotIn("AI Agent Cost in 2026: Pricing, TCO and ROI Guide", item.after)
self.assertNotIn("see [", item.after)
self.assertIn("[AI agent](https://t.com)", item.after)
self.assertIn("implementation costs", item.after)
self.assertEqual(item.suggested_anchor, "AI agent")
self.assertIn("target_title_not_injected_into_source_copy", item.reasons)
self.assertIn("destination_intent_used_for_contextual_sentence", item.reasons)

@patch("backlink_intelligence.placement.fetch_page")
def test_general_contextual_sentence_does_not_echo_target_title(self, fetch):
source = parse_page(
"<main><p>Teams often introduce these capabilities progressively as systems become more autonomous and reliable across increasingly complex production workflows.</p></main>",
requested_url="https://s.com", final_url="https://s.com", status_code=200,
)
target = parse_page(
"<title>Agentic AI Learning Roadmap</title><h1>Learn Agentic AI</h1><p>A structured roadmap for tool calling, retrieval, memory, evaluation, and production reliability.</p>",
requested_url="https://t.com", final_url="https://t.com", status_code=200,
)
fetch.side_effect = [source, target]
item = suggest_placements("https://s.com", "https://t.com", "Agentic AI learning roadmap", top_n=1)[0]
self.assertEqual(item.strategy, "contextual_sentence")
self.assertNotIn("For a more detailed resource on", item.after)
self.assertNotIn("see [", item.after)
self.assertIn("[Agentic AI learning roadmap](https://t.com)", item.after)



if __name__ == "__main__":
unittest.main()
Loading