Scenario
Some of your content resonates and some doesn’t — but you don’t always know why. You pull posts with comment counts and modification dates as engagement proxies, segment them into high and low performers, create Mavera personas for each segment’s reader profile, then run a focus group to understand what makes certain content connect.Architecture
Code
import os, re, requests
WP, MV = os.environ["WORDPRESS_URL"], os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
strip_html = lambda h: re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", h or "")).strip()
posts, page = [], 1
while True:
resp = requests.get(f"{WP}/wp-json/wp/v2/posts", params={
"status": "publish", "per_page": 100, "page": page,
"_fields": "id,title,content,comment_count,date,modified,link"})
if resp.status_code == 400: break
resp.raise_for_status()
batch = resp.json()
if not batch: break
posts.extend(batch)
if page >= int(resp.headers.get("X-WP-TotalPages", 1)): break
page += 1
print(f"Fetched {len(posts)} posts")
posts.sort(key=lambda p: p.get("comment_count", 0), reverse=True)
cutoff = max(len(posts) // 4, 3)
high, low = posts[:cutoff], [p for p in posts[-cutoff:] if p.get("comment_count", 0) == 0] or posts[-cutoff:]
print(f"High: {len(high)} | Low: {len(low)}")
persona_ids = []
for label, group, desc in [
("Engaged Reader", high, "Readers who comment on top-performing content"),
("Silent Visitor", low, "Visitors who read low-engagement content but never comment"),
]:
titles = ", ".join(p["title"]["rendered"] for p in group[:5])
r = requests.post(f"{MB}/personas", json={"name": f"WordPress {label}", "description": f"{desc}. Posts: {titles}."}, headers=MH)
r.raise_for_status(); persona_ids.append(r.json()["id"])
print(f" Persona: {r.json()['name']} ({r.json()['id']})")
high_s = "\n".join(f"- \"{p['title']['rendered']}\" ({p.get('comment_count',0)} comments)" for p in high[:5])
low_s = "\n".join(f"- \"{p['title']['rendered']}\" ({p.get('comment_count',0)} comments)" for p in low[:5])
fg = requests.post(f"{MB}/focus-groups", json={
"name": "WordPress Content Performance Analysis", "persona_ids": persona_ids,
"questions": ["What about high-performing content makes you engage?",
"Why might low-performing posts fail to hold attention?",
"What change would make you comment on a post?",
"How important is the opening paragraph?"],
"context": f"HIGH:\n{high_s}\n\nExcerpt: {strip_html(high[0]['content']['rendered'])[:400]}\n\n"
f"LOW:\n{low_s}\n\nExcerpt: {strip_html(low[0]['content']['rendered'])[:400]}",
}, headers=MH)
fg.raise_for_status(); result = fg.json()
print(f"\nFocus group: {result['id']}")
for r in result.get("responses", []):
print(f"\n[{r['persona_name']}] {r['question']}\n → {r['answer']}")
const WP = process.env.WORDPRESS_URL, MV = process.env.MAVERA_API_KEY;
const MB = "https://app.mavera.io/api/v1";
const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const stripHtml = h => (h || "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
(async () => {
const posts = []; let page = 1;
while (true) {
const resp = await fetch(`${WP}/wp-json/wp/v2/posts?status=publish&per_page=100&page=${page}&_fields=id,title,content,comment_count,date,modified,link`);
if (resp.status === 400) break;
const batch = await resp.json();
if (!batch.length) break;
posts.push(...batch);
if (page >= parseInt(resp.headers.get("X-WP-TotalPages") || "1")) break;
page++;
}
console.log(`Fetched ${posts.length} posts`);
posts.sort((a, b) => (b.comment_count || 0) - (a.comment_count || 0));
const cut = Math.max(Math.floor(posts.length / 4), 3);
const high = posts.slice(0, cut);
const low = posts.slice(-cut).filter(p => !(p.comment_count || 0));
const lowGroup = low.length ? low : posts.slice(-cut);
const personaIds = [];
for (const [label, group, desc] of [
["Engaged Reader", high, "Readers who comment on top content"],
["Silent Visitor", lowGroup, "Visitors who read but never comment"],
]) {
const r = await fetch(`${MB}/personas`, { method: "POST", headers: MH,
body: JSON.stringify({ name: `WordPress ${label}`, description: `${desc}. Posts: ${group.slice(0,5).map(p=>p.title.rendered).join(", ")}.` }) });
personaIds.push((await r.json()).id);
}
const fmt = (arr) => arr.slice(0,5).map(p => `- "${p.title.rendered}" (${p.comment_count||0} comments)`).join("\n");
const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: MH,
body: JSON.stringify({ name: "WordPress Content Performance Analysis", persona_ids: personaIds,
questions: ["What about high-performing content makes you engage?", "Why might low-performing posts fail?",
"What change would make you comment?", "How important is the opening paragraph?"],
context: `HIGH:\n${fmt(high)}\n\nExcerpt: ${stripHtml(high[0]?.content?.rendered).slice(0,400)}\n\nLOW:\n${fmt(lowGroup)}\n\nExcerpt: ${stripHtml(lowGroup[0]?.content?.rendered).slice(0,400)}` }) });
const result = await fg.json();
console.log(`\nFocus group: ${result.id}`);
for (const r of result.responses || []) console.log(`\n[${r.persona_name}] ${r.question}\n → ${r.answer}`);
})();
Example Output
{
"id": "fg_3c7d2e9b",
"name": "WordPress Content Performance Analysis",
"responses": [
{
"persona_name": "WordPress Engaged Reader",
"question": "What about the high-performing content makes you want to engage?",
"answer": "The high-comment posts take a clear stance — they don't just explain, they argue a position. That gives me something to agree or disagree with. The listicle-style posts with no opinion get a skim and a close."
},
{
"persona_name": "WordPress Silent Visitor",
"question": "Why might the low-performing posts fail to hold your attention?",
"answer": "They read like documentation — accurate but flat. There's no hook in the first paragraph, no reason to keep scrolling. I found the information I needed in the first 200 words and left."
}
]
}
Error Handling
comment_count not available or always zero
comment_count not available or always zero
Content field too large for focus group context
Content field too large for focus group context
Full post HTML can be very large. The code truncates excerpts to 500 characters. For richer context without token overflow, use a summarization step: send each post’s content through Mavera responses for a 2-sentence summary, then pass those summaries into the focus group context.
comment_countis 0 for all posts, consider integrating with GA4 for pageview data (/integrations/ga4) or use themodifieddate relative todateas an engagement proxy — frequently updated posts indicate editorial investment.