116 lines
4.8 KiB
TypeScript
116 lines
4.8 KiB
TypeScript
import { PrismaClient } from "@prisma/client";
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
async function main() {
|
||
console.log("Seeding AI visibility + competitors...");
|
||
|
||
const brands = await prisma.brand.findMany();
|
||
if (brands.length === 0) { console.log("No brands. Run main seed first."); return; }
|
||
|
||
for (const brand of brands) {
|
||
// Clear existing
|
||
await prisma.aiVisibilitySnapshot.deleteMany({ where: { brandId: brand.id } });
|
||
await prisma.competitorSnapshot.deleteMany({ where: { brandId: brand.id } });
|
||
await prisma.competitor.deleteMany({ where: { brandId: brand.id } });
|
||
|
||
// ── AI Visibility: 30 days of snapshots ──
|
||
const platforms = ["chatgpt", "claude", "perplexity", "gemini", "google_aio"];
|
||
const topics = [
|
||
"best project management tools",
|
||
"SEO audit checklist",
|
||
"technical SEO guide",
|
||
"AI visibility optimization",
|
||
"website speed optimization",
|
||
];
|
||
const pages = ["/features", "/blog/seo-guide", "/technical-audit", "/pricing", "/about"];
|
||
|
||
for (let d = 30; d >= 0; d--) {
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - d);
|
||
date.setHours(12, 0, 0, 0);
|
||
|
||
for (let p = 0; p < platforms.length; p++) {
|
||
const baseMentions = [5, 4, 3, 2, 1][p];
|
||
const growth = 1 + ((30 - d) / 30) * 0.2;
|
||
const noise = () => 0.7 + Math.random() * 0.6;
|
||
|
||
for (let t = 0; t < 2; t++) {
|
||
const topicIdx = (p + t) % topics.length;
|
||
const mentions = Math.round(baseMentions * growth * noise());
|
||
const citations = Math.round(mentions * (0.3 + Math.random() * 0.4));
|
||
|
||
await prisma.aiVisibilitySnapshot.create({
|
||
data: {
|
||
brandId: brand.id,
|
||
date,
|
||
platform: platforms[p],
|
||
topic: topics[topicIdx],
|
||
mentions,
|
||
citations,
|
||
visibilityScore: Math.min(100, Math.round(mentions * 8 + citations * 12)),
|
||
sourceUrl: pages[topicIdx],
|
||
change: d < 28 ? Math.round((Math.random() - 0.3) * 3) : 0,
|
||
},
|
||
});
|
||
}
|
||
}
|
||
}
|
||
console.log(` AI visibility: 30 days × 5 platforms × 2 topics for ${brand.name}`);
|
||
|
||
// ── Competitors ──
|
||
const comps = [
|
||
{ name: "RankBoost.io", domain: "rankboost.io", type: "national", strongestPage: "/features/seo-audit", whyWinning: "Published 4 new long-form guides targeting core keywords. Strong internal linking structure.", watchlist: true },
|
||
{ name: "ContentKing", domain: "contentking.com", type: "national", strongestPage: "/technical-seo-guide", whyWinning: "12,000-word technical SEO guide displacing competitors in AI citations.", watchlist: true },
|
||
{ name: "LocalSEO Pro", domain: "localseepro.com", type: "local", strongestPage: "/local-seo-services", whyWinning: "Dominates local pack for city-specific queries. Strong GBP optimization.", watchlist: false },
|
||
{ name: "AgencyFirst", domain: "agencyfirst.io", type: "emerging", strongestPage: "/blog/ai-seo", whyWinning: "New player publishing AI-focused SEO content rapidly.", watchlist: false },
|
||
];
|
||
|
||
for (const c of comps) {
|
||
const competitor = await prisma.competitor.create({
|
||
data: {
|
||
brandId: brand.id,
|
||
name: c.name,
|
||
domain: c.domain,
|
||
type: c.type,
|
||
strongestPage: c.strongestPage,
|
||
whyWinning: c.whyWinning,
|
||
watchlist: c.watchlist,
|
||
},
|
||
});
|
||
|
||
// Add weekly snapshots for 12 weeks
|
||
for (let w = 12; w >= 0; w--) {
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - w * 7);
|
||
date.setHours(12, 0, 0, 0);
|
||
|
||
const threat = c.watchlist ? (w < 4 ? "high" : "medium") : "low";
|
||
const baseAi = c.name === "RankBoost.io" ? 8 : c.name === "ContentKing" ? 6 : 3;
|
||
|
||
await prisma.competitorSnapshot.create({
|
||
data: {
|
||
competitorId: competitor.id,
|
||
brandId: brand.id,
|
||
date,
|
||
movement: w < 3 ? `#${5 - w} → #${3 - w > 0 ? 3 - w : 1}` : "Stable",
|
||
rankChange: w < 4 ? Math.round(Math.random() * 3) : 0,
|
||
aiMentions: Math.round(baseAi * (1 + (12 - w) / 12 * 0.3) + (Math.random() - 0.5) * 2),
|
||
aiMentionChange: w < 6 ? Math.round((Math.random() - 0.3) * 3) : 0,
|
||
trafficEstimate: Math.round((5000 + Math.random() * 10000) * (1 + (12 - w) / 12 * 0.15)),
|
||
topKeyword: c.name === "RankBoost.io" ? "project management SEO tool" : c.name === "ContentKing" ? "technical SEO audit" : "local SEO services",
|
||
threat,
|
||
},
|
||
});
|
||
}
|
||
}
|
||
console.log(` Competitors: ${comps.length} with 13 weekly snapshots each for ${brand.name}`);
|
||
}
|
||
|
||
console.log("AI visibility + competitors seeding complete!");
|
||
}
|
||
|
||
main()
|
||
.catch((e) => { console.error(e); process.exit(1); })
|
||
.finally(() => prisma.$disconnect());
|