This guide uses the API client from the Guides index. Copy it to your project first.
What You’ll Build
A weekly report showing:- Current vs previous week metrics comparison
- Trend indicators (up/down/stable)
- Percentage change for each KPI
Generate Weekly Report
Copy
async function generateWeeklyReport(client, brandId) {
const now = new Date();
// Calculate date ranges
const thisWeekEnd = now.toISOString().split('T')[0];
const thisWeekStart = new Date(now - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const lastWeekEnd = thisWeekStart;
const lastWeekStart = new Date(now - 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
// Fetch both periods in parallel
const [thisWeek, lastWeek] = await Promise.all([
client.getPerformance(brandId, { startDate: thisWeekStart, endDate: thisWeekEnd }),
client.getPerformance(brandId, { startDate: lastWeekStart, endDate: lastWeekEnd }),
]);
const calculateChange = (current, previous) => {
if (!previous || previous === 0) return null;
return ((current - previous) / previous * 100).toFixed(1);
};
const getTrend = (current, previous) => {
if (current > previous) return 'up';
if (current < previous) return 'down';
return 'stable';
};
const metrics = [
{ name: 'GEO Score', key: 'global' },
{ name: 'Mention Rate', key: 'mentionRate' },
{ name: 'Source Rate', key: 'sourceRate' },
{ name: 'Share of Voice', key: 'shareOfVoice' },
{ name: 'Sentiment', key: 'sentiment' },
];
return {
generatedAt: new Date().toISOString(),
period: {
thisWeek: { start: thisWeekStart, end: thisWeekEnd },
lastWeek: { start: lastWeekStart, end: lastWeekEnd },
},
metrics: metrics.map(m => ({
name: m.name,
current: thisWeek.scores[m.key],
previous: lastWeek.scores[m.key],
change: calculateChange(thisWeek.scores[m.key], lastWeek.scores[m.key]),
trend: getTrend(thisWeek.scores[m.key], lastWeek.scores[m.key]),
})),
methodology: {
thisWeek: thisWeek.methodology.responsesTotal,
lastWeek: lastWeek.methodology.responsesTotal,
},
};
}
Usage
Copy
const client = new QwairyClient(process.env.QWAIRY_API_TOKEN);
const report = await generateWeeklyReport(client, 'your-brand-id');
// Console output
console.log(`\n Weekly GEO Report: ${report.period.thisWeek.start} to ${report.period.thisWeek.end}\n`);
console.log('Metric Current Previous Change');
console.log('─'.repeat(55));
for (const m of report.metrics) {
const trend = m.trend === 'up' ? '↑' : m.trend === 'down' ? '↓' : '→';
const changeStr = m.change !== null ? `${m.change > 0 ? '+' : ''}${m.change}%` : 'N/A';
console.log(`${m.name.padEnd(20)} ${String(m.current).padEnd(10)} ${String(m.previous).padEnd(11)} ${trend} ${changeStr}`);
}
Example Output
Console:Copy
Weekly GEO Report: 2024-12-12 to 2024-12-19
Metric Current Previous Change
───────────────────────────────────────────────────────
GEO Score 62 58 ↑ +6.9%
Mention Rate 45.2 42.1 ↑ +7.4%
Source Rate 23.9 25.0 ↓ -4.4%
Share of Voice 8.13 7.5 ↑ +8.4%
Sentiment 78.1 76.2 ↑ +2.5%
Copy
{
"generatedAt": "2024-12-19T10:30:00.000Z",
"period": {
"thisWeek": { "start": "2024-12-12", "end": "2024-12-19" },
"lastWeek": { "start": "2024-12-05", "end": "2024-12-12" }
},
"metrics": [
{ "name": "GEO Score", "current": 62, "previous": 58, "change": "6.9", "trend": "up" },
{ "name": "Mention Rate", "current": 45.2, "previous": 42.1, "change": "7.4", "trend": "up" },
{ "name": "Source Rate", "current": 23.9, "previous": 25.0, "change": "-4.4", "trend": "down" },
{ "name": "Share of Voice", "current": 8.13, "previous": 7.5, "change": "8.4", "trend": "up" },
{ "name": "Sentiment", "current": 78.1, "previous": 76.2, "change": "2.5", "trend": "up" }
]
}
Add Diagnostic Drivers (Optional)
Enrich your report with the “why” behind metric changes: which providers moved, which prompts gained or lost citations.Copy
async function getReportDrivers(client, brandId, startDate, endDate) {
const [answers, searchQueries] = await Promise.all([
client.getAnswers(brandId, {
startDate,
endDate,
hasSelfMention: true,
hasSelfSource: false,
limit: 5,
sort: 'createdAt',
order: 'desc',
}),
client.getSearch(brandId, {
startDate,
endDate,
limit: 5,
sort: 'createdAt',
order: 'desc',
}),
]);
return {
missedCitations: {
count: answers.pagination.total,
top: answers.answers.map(a => ({
prompt: a.promptText,
provider: a.provider,
position: a.selfMentionPosition,
})),
},
searchQueries: {
count: searchQueries.pagination.total,
top: searchQueries.searches.map(s => ({
query: s.query,
prompt: s.prompt,
})),
},
};
}
// Usage: add to your report
const drivers = await getReportDrivers(client, brandId, thisWeekStart, thisWeekEnd);
console.log(`\nMissed citations this week: ${drivers.missedCitations.count}`);
console.log('Top prompts where you are mentioned but not cited:');
drivers.missedCitations.top.forEach(d => console.log(` - [${d.provider}] "${d.prompt}"`));
Send Report
Deliver the report via Slack or email.Copy
async function sendToSlack(report, webhookUrl) {
const formatMetric = (m) => {
const icon = m.trend === 'up' ? ':chart_with_upwards_trend:' : m.trend === 'down' ? ':chart_with_downwards_trend:' : ':arrow_right:';
const sign = m.change > 0 ? '+' : '';
return `${icon} *${m.name}*: ${m.current} (${sign}${m.change}%)`;
};
const blocks = [
{
type: 'header',
text: { type: 'plain_text', text: `Weekly GEO Report` },
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Period:* ${report.period.thisWeek.start} to ${report.period.thisWeek.end}`,
},
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: report.metrics.map(formatMetric).join('\n'),
},
},
];
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ blocks }),
});
}
// Usage
await sendToSlack(report, process.env.SLACK_WEBHOOK_URL);
Scheduling
Run this report automatically:| Platform | Method |
|---|---|
| Cron | 0 9 * * MON (every Monday at 9am) |
| GitHub Actions | schedule: cron: '0 9 * * 1' |
| AWS Lambda | EventBridge rule with cron expression |
| Google Cloud | Cloud Scheduler + Cloud Functions |
Next Steps
- Build a custom dashboard for real-time monitoring
- Add competitive analysis to your reports
- Export data to your BI tools

