Separate PR Topic (angle) from Keyword (anchor text) in press release pipeline

PR Topic now only drives headline angle and awareness/news framing.
Anchor text is derived from Company Name + Keyword (ClickUp Keyword field).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix/customer-field-migration
PeninsulaInd 2026-03-17 10:51:10 -05:00
parent f60928f3fa
commit ad7094e8a5
3 changed files with 20 additions and 26 deletions

View File

@ -364,16 +364,14 @@ def _build_judge_prompt(headlines: str, headlines_ref: str, topic: str = "") ->
return prompt
def _derive_anchor_phrase(company_name: str, topic: str) -> str:
"""Derive a 'brand + keyword' anchor phrase from company name and topic.
def _derive_anchor_phrase(company_name: str, keyword: str) -> str:
"""Derive a 'brand + keyword' anchor phrase from company name and keyword.
Examples:
("Advanced Industrial", "PEEK machining") -> "Advanced Industrial PEEK machining"
("Metal Craft", "custom metal fabrication") -> "Metal Craft custom metal fabrication"
"""
# Clean up topic: strip leading articles, lowercase
keyword = topic.strip()
return f"{company_name} {keyword}"
return f"{company_name} {keyword.strip()}"
def _find_anchor_in_text(text: str, anchor: str) -> bool:
@ -546,6 +544,7 @@ def write_press_releases(
topic: str,
company_name: str,
url: str = "",
keyword: str = "",
lsi_terms: str = "",
required_phrase: str = "",
ctx: dict | None = None,
@ -668,7 +667,7 @@ def write_press_releases(
# ── Step 3: Write 2 press releases (execution brain x 2) ─────────────
log.info("[PR Pipeline] Step 3/4: Writing 2 press releases...")
anchor_phrase = _derive_anchor_phrase(company_name, topic)
anchor_phrase = _derive_anchor_phrase(company_name, keyword) if keyword else ""
pr_texts: list[str] = []
pr_files: list[str] = []
docx_files: list[str] = []
@ -708,11 +707,11 @@ def write_press_releases(
if wc < 575 or wc > 800:
log.warning("PR %d word count %d outside 575-800 range", i + 1, wc)
# Validate anchor phrase
if _find_anchor_in_text(clean_result, anchor_phrase):
# Validate anchor phrase (only when keyword provided)
if anchor_phrase and _find_anchor_in_text(clean_result, anchor_phrase):
log.info("PR %d contains anchor phrase '%s'", i + 1, anchor_phrase)
else:
fuzzy = _fuzzy_find_anchor(clean_result, company_name, topic)
elif anchor_phrase:
fuzzy = _fuzzy_find_anchor(clean_result, company_name, keyword)
if fuzzy:
log.info("PR %d: exact anchor not found, fuzzy match: '%s'", i + 1, fuzzy)
anchor_warnings.append(
@ -1078,7 +1077,7 @@ def _resolve_branded_url(branded_url: str, company_data: dict | None) -> str:
def _build_links(
pr_text: str,
company_name: str,
topic: str,
keyword: str,
target_url: str,
branded_url_resolved: str,
) -> tuple[list[dict], list[str]]:
@ -1091,13 +1090,13 @@ def _build_links(
warnings: list[str] = []
# Link 1: brand+keyword → target_url
if target_url:
anchor_phrase = _derive_anchor_phrase(company_name, topic)
if target_url and keyword:
anchor_phrase = _derive_anchor_phrase(company_name, keyword)
if _find_anchor_in_text(pr_text, anchor_phrase):
links.append({"url": target_url, "anchor": anchor_phrase})
else:
# Try fuzzy match
fuzzy = _fuzzy_find_anchor(pr_text, company_name, topic)
fuzzy = _fuzzy_find_anchor(pr_text, company_name, keyword)
if fuzzy:
links.append({"url": target_url, "anchor": fuzzy})
warnings.append(
@ -1141,6 +1140,7 @@ def submit_press_release(
company_name: str,
target_url: str = "",
branded_url: str = "",
keyword: str = "",
topic: str = "",
pr_text: str = "",
file_path: str = "",
@ -1178,13 +1178,6 @@ def submit_press_release(
f"Press Advantage requires at least 550 words. Please expand the content."
)
# --- Derive topic from headline if not provided ---
if not topic:
topic = headline
for part in [company_name, "Inc.", "LLC", "Corp.", "Ltd.", "Limited", "Inc"]:
topic = topic.replace(part, "").strip()
topic = re.sub(r"\s+", " ", topic).strip(" -\u2013\u2014,")
# --- Load company data ---
companies_text = _load_file_if_exists(_COMPANIES_FILE)
company_all = _parse_company_data(companies_text)
@ -1227,7 +1220,7 @@ def submit_press_release(
link_list, link_warnings = _build_links(
pr_text,
company_name,
topic,
keyword,
target_url,
branded_url_resolved,
)

View File

@ -58,6 +58,7 @@ clickup:
required_fields: [topic, company_name, target_url]
field_mapping:
topic: "PR Topic"
keyword: "Keyword"
company_name: "Client"
target_url: "IMSURL"
branded_url: "SocialURL"

View File

@ -552,7 +552,7 @@ class TestSubmitPressRelease:
headline="Advanced Industrial Expands PEEK Machining",
company_name="Advanced Industrial",
pr_text=REALISTIC_PR_TEXT,
topic="PEEK machining",
keyword="PEEK machining",
target_url="https://advancedindustrial.com/peek",
ctx=submit_ctx,
)
@ -575,7 +575,7 @@ class TestSubmitPressRelease:
headline="Advanced Industrial Expands PEEK Machining",
company_name="Advanced Industrial",
pr_text=REALISTIC_PR_TEXT,
topic="PEEK machining",
keyword="PEEK machining",
branded_url="https://linkedin.com/company/advanced-industrial",
ctx=submit_ctx,
)
@ -598,7 +598,7 @@ class TestSubmitPressRelease:
headline="Advanced Industrial Expands PEEK Machining",
company_name="Advanced Industrial",
pr_text=REALISTIC_PR_TEXT,
topic="PEEK machining",
keyword="PEEK machining",
branded_url="GBP",
ctx=submit_ctx,
)
@ -694,7 +694,7 @@ class TestSubmitPressRelease:
headline="Advanced Industrial Expands PEEK Machining",
company_name="Advanced Industrial",
pr_text=LONG_PR_TEXT,
topic="PEEK machining",
keyword="PEEK machining",
target_url="https://example.com/peek",
ctx=submit_ctx,
)