|
# retoor <retoor@molodetz.nl>
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from devplacepy.services.jobs.isslop.analysis.domsignals.base import DomPageContext, dom_check
|
|
from devplacepy.services.jobs.isslop.analysis.signals import (
|
|
AXIS_ORIGIN,
|
|
SEVERITY_MEDIUM,
|
|
SEVERITY_WEAK,
|
|
Signal,
|
|
)
|
|
|
|
CLICHE_PHRASES: tuple[str, ...] = (
|
|
r"unlock (the )?(power|potential) of",
|
|
r"unleash the power of",
|
|
r"take your .{1,40} to the next level",
|
|
r"elevate your",
|
|
r"seamlessly integrat(e|es|ion)",
|
|
r"revolutioniz(e|ing) the way",
|
|
r"whether you('re| are) a .{1,30} or (a )?.{1,30}",
|
|
r"delve into",
|
|
r"dive into the world of",
|
|
r"embark on a journey",
|
|
r"push(ing)? the boundaries of",
|
|
r"at the forefront of",
|
|
r"game[- ]chang(er|ing)",
|
|
r"pave the way for",
|
|
r"bridging the gap between",
|
|
r"navigate the complexities of",
|
|
r"foster a culture of",
|
|
r"harness the power of",
|
|
r"cutting[- ]edge",
|
|
r"future[- ]proof",
|
|
r"world[- ]class",
|
|
r"enterprise[- ]grade",
|
|
r"streamline(d)?",
|
|
r"empower(ing)?",
|
|
r"supercharg(e|ed|ing)",
|
|
)
|
|
CLICHE_PATTERN = re.compile("|".join(CLICHE_PHRASES), re.IGNORECASE)
|
|
|
|
BUZZWORDS: tuple[str, ...] = ("delve", "crucial", "intricate", "nuanced", "myriad", "realm", "tapestry", "landscape")
|
|
BUZZWORD_THRESHOLD: int = 3
|
|
BUZZWORD_PATTERNS: tuple[re.Pattern[str], ...] = tuple(
|
|
re.compile(rf"\b{word}\b", re.IGNORECASE) for word in BUZZWORDS
|
|
)
|
|
|
|
HEADING_EMOJI_PATTERN = re.compile(r"[\U0001F300-\U0001FAFF✅⭐✨\U0001F680]")
|
|
|
|
FAQ_PATTERNS: tuple[re.Pattern[str], ...] = (
|
|
re.compile(r"^what is\b", re.IGNORECASE),
|
|
re.compile(r"^how does .{1,30} work\??$", re.IGNORECASE),
|
|
re.compile(r"^is (it|.{1,20}) secure\??$", re.IGNORECASE),
|
|
re.compile(r"^can i cancel", re.IGNORECASE),
|
|
re.compile(r"^do you offer a? ?(free trial|refund)", re.IGNORECASE),
|
|
)
|
|
FAQ_PATTERN_THRESHOLD: int = 3
|
|
|
|
|
|
def _heading_texts(page: DomPageContext) -> list[str]:
|
|
return [
|
|
str(heading.get("text", ""))
|
|
for heading in (page.dom.get("headings") or [])
|
|
if isinstance(heading, dict) and heading.get("text")
|
|
]
|
|
|
|
|
|
def _text_blob(page: DomPageContext) -> str:
|
|
parts = list(_heading_texts(page))
|
|
for image in page.dom.get("images", []) or []:
|
|
if isinstance(image, dict) and image.get("alt"):
|
|
parts.append(str(image["alt"]))
|
|
meta = page.dom.get("meta", {}) or {}
|
|
description = meta.get("description") if isinstance(meta, dict) else ""
|
|
if description:
|
|
parts.append(str(description))
|
|
return "\n".join(parts)
|
|
|
|
|
|
def _detect_template_copy(blob: str) -> Signal | None:
|
|
match = CLICHE_PATTERN.search(blob)
|
|
if not match:
|
|
return None
|
|
return Signal(
|
|
"TEMPLATE_COPY_DOM",
|
|
"Stock AI landing-page cliche phrase in rendered copy",
|
|
SEVERITY_MEDIUM,
|
|
AXIS_ORIGIN,
|
|
1.5,
|
|
0,
|
|
match.group(0)[:150],
|
|
)
|
|
|
|
|
|
def _detect_buzzword_cluster(blob: str) -> Signal | None:
|
|
hits = [pattern.pattern for pattern in BUZZWORD_PATTERNS if pattern.search(blob)]
|
|
if len(hits) < BUZZWORD_THRESHOLD:
|
|
return None
|
|
return Signal(
|
|
"AI_BUZZWORD_CLUSTER",
|
|
f"Elevated buzzword cluster ({len(hits)} distinct terms)",
|
|
SEVERITY_WEAK,
|
|
AXIS_ORIGIN,
|
|
1.0,
|
|
0,
|
|
", ".join(hits),
|
|
)
|
|
|
|
|
|
def _detect_emoji_heading(page: DomPageContext) -> Signal | None:
|
|
for text in _heading_texts(page):
|
|
if HEADING_EMOJI_PATTERN.search(text):
|
|
return Signal(
|
|
"EMOJI_HEADING_DOM",
|
|
"Emoji embedded inside a rendered heading",
|
|
SEVERITY_MEDIUM,
|
|
AXIS_ORIGIN,
|
|
1.0,
|
|
0,
|
|
text[:100],
|
|
)
|
|
return None
|
|
|
|
|
|
def _detect_generic_faq_template(page: DomPageContext) -> Signal | None:
|
|
texts = _heading_texts(page)
|
|
matched = 0
|
|
for pattern in FAQ_PATTERNS:
|
|
if any(pattern.search(text.strip()) for text in texts):
|
|
matched += 1
|
|
if matched < FAQ_PATTERN_THRESHOLD:
|
|
return None
|
|
return Signal(
|
|
"GENERIC_FAQ_TEMPLATE",
|
|
f"Generic templated FAQ question set ({matched} canonical patterns matched)",
|
|
SEVERITY_MEDIUM,
|
|
AXIS_ORIGIN,
|
|
1.5,
|
|
0,
|
|
f"{matched} FAQ patterns matched",
|
|
)
|
|
|
|
|
|
@dom_check
|
|
def detect_copy_signals(page: DomPageContext) -> list[Signal]:
|
|
blob = _text_blob(page)
|
|
findings: list[Signal] = []
|
|
for signal in (
|
|
_detect_template_copy(blob),
|
|
_detect_buzzword_cluster(blob),
|
|
_detect_emoji_heading(page),
|
|
_detect_generic_faq_template(page),
|
|
):
|
|
if signal is not None:
|
|
findings.append(signal)
|
|
return findings
|