diff --git a/data/economics.json b/data/economics.json new file mode 100644 index 00000000..8b9130bb --- /dev/null +++ b/data/economics.json @@ -0,0 +1,74 @@ +[ + { + "id": "pricing-usage-based-models", + "title": "When usage-based pricing beats seat-based pricing", + "content": "Usage-based pricing usually outperforms seat pricing when value scales with automation volume rather than headcount. Agent products tied to runs, tasks, or tokens can map revenue directly to customer outcomes and reduce friction for small teams adopting early.", + "category": "pricing", + "tags": ["pricing", "usage-based", "packaging"], + "updatedAt": "2026-02-01T09:00:00.000Z" + }, + { + "id": "pricing-tier-design", + "title": "Designing pricing tiers for AI agent products", + "content": "A strong tier model combines one clear value metric with operational guardrails. Keep starter tiers generous enough to activate adoption, then gate premium features like private data connectors, advanced workflows, and team controls at higher plans.", + "category": "pricing", + "tags": ["pricing", "tiers", "saas"], + "updatedAt": "2026-02-02T10:30:00.000Z" + }, + { + "id": "costs-token-governance", + "title": "Token cost governance for production agents", + "content": "Token governance starts with observability per workflow and model. Teams that enforce prompt budgets, caching strategies, and fallback routing can often reduce monthly inference spend by double-digit percentages without harming quality.", + "category": "costs", + "tags": ["costs", "tokens", "governance"], + "updatedAt": "2026-02-03T08:15:00.000Z" + }, + { + "id": "costs-infra-overhead", + "title": "Estimating infrastructure overhead beyond model spend", + "content": "Model inference is only one part of unit economics. Storage, vector indexing, retries, queue workers, monitoring, and support overhead should be included in cost per task calculations before pricing decisions are finalized.", + "category": "costs", + "tags": ["costs", "infrastructure", "unit-economics"], + "updatedAt": "2026-02-03T17:45:00.000Z" + }, + { + "id": "revenue-expansion-playbooks", + "title": "Revenue expansion with agent capability ladders", + "content": "Expansion revenue improves when customers can graduate from simple automations to higher-leverage orchestrations. Packaging advanced reliability, integrations, and analytics as clear upgrade paths increases net revenue retention.", + "category": "revenue", + "tags": ["revenue", "expansion", "nrr"], + "updatedAt": "2026-02-04T11:10:00.000Z" + }, + { + "id": "revenue-onboarding-activation", + "title": "Onboarding activation as a revenue multiplier", + "content": "Fast activation shortens time-to-value and raises paid conversion. Product onboarding should guide teams to one measurable win in week one, then surface monetizable next steps through templates and integrations.", + "category": "revenue", + "tags": ["revenue", "onboarding", "activation"], + "updatedAt": "2026-02-05T12:00:00.000Z" + }, + { + "id": "roi-baseline-method", + "title": "How to set an ROI baseline before deploying agents", + "content": "Reliable ROI measurement starts with a pre-launch baseline for cycle time, manual hours, defect rate, and throughput. Without baseline metrics, post-launch wins can be hard to attribute and defend with finance teams.", + "category": "roi", + "tags": ["roi", "measurement", "baseline"], + "updatedAt": "2026-02-06T14:20:00.000Z" + }, + { + "id": "roi-payback-window", + "title": "Calculating payback windows for agent initiatives", + "content": "Payback window analysis should include build time, implementation support, and ongoing model/infrastructure costs. Teams with disciplined rollout sequencing can often recover costs in one to two quarters.", + "category": "roi", + "tags": ["roi", "payback", "finance"], + "updatedAt": "2026-02-07T16:35:00.000Z" + }, + { + "id": "roi-risk-adjusted-forecasting", + "title": "Risk-adjusted forecasting for AI agent rollouts", + "content": "Forecast ranges should account for adoption risk, quality regressions, and integration delays. Scenario planning with conservative, expected, and upside cases improves stakeholder confidence and reduces planning surprises.", + "category": "roi", + "tags": ["roi", "forecasting", "risk"], + "updatedAt": "2026-02-08T09:40:00.000Z" + } +] diff --git a/src/app/api/economics/route.ts b/src/app/api/economics/route.ts index b3273a67..481500bf 100644 --- a/src/app/api/economics/route.ts +++ b/src/app/api/economics/route.ts @@ -1,9 +1,88 @@ -import { modelEconomics } from "@/lib/economics"; - -export function GET() { - return Response.json({ - generatedAt: new Date().toISOString(), - count: modelEconomics.length, - models: modelEconomics, - }); +import { NextRequest, NextResponse } from "next/server"; +import { + createEconomicsEntryId, + filterEconomicsEntries, + normalizeEconomicsCategory, + readEconomicsEntries, + type EconomicsCategory, + type EconomicsEntry, + writeEconomicsEntries, +} from "@/lib/economics"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET(request: NextRequest) { + try { + const category = request.nextUrl.searchParams.get("category"); + const search = request.nextUrl.searchParams.get("search"); + + const entries = await readEconomicsEntries(); + const filtered = filterEconomicsEntries(entries, { category, search }); + + const categories = Array.from(new Set(entries.map((entry) => entry.category))); + + return NextResponse.json( + { + entries: filtered, + total: filtered.length, + categories, + }, + { headers: { "Cache-Control": "no-store" } } + ); + } catch (error) { + console.error("Failed to load economics entries", error); + return NextResponse.json({ error: "Failed to load economics entries" }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + try { + const body = (await request.json()) as Record; + + const title = typeof body.title === "string" ? body.title.trim() : ""; + const content = typeof body.content === "string" ? body.content.trim() : ""; + const categoryInput = typeof body.category === "string" ? body.category : ""; + const category = normalizeEconomicsCategory(categoryInput); + + const tags = Array.isArray(body.tags) + ? body.tags + .filter((tag): tag is string => typeof tag === "string") + .map((tag) => tag.trim()) + .filter(Boolean) + : typeof body.tags === "string" + ? body.tags + .split(",") + .map((tag) => tag.trim()) + .filter(Boolean) + : []; + + if (!title || !content || !category || tags.length === 0) { + return NextResponse.json( + { + error: "title, content, category (pricing|costs|revenue|roi), and at least one tag are required", + }, + { status: 400 } + ); + } + + const entries = await readEconomicsEntries(); + + const entry: EconomicsEntry = { + id: createEconomicsEntryId(title), + title, + content, + category: category as EconomicsCategory, + tags, + updatedAt: new Date().toISOString(), + }; + + const updated = [entry, ...entries]; + await writeEconomicsEntries(updated); + + return NextResponse.json({ entry }, { status: 201 }); + } catch (error) { + console.error("Failed to create economics entry", error); + return NextResponse.json({ error: "Failed to create economics entry" }, { status: 500 }); + } } diff --git a/src/app/economics/compare/page.tsx b/src/app/economics/compare/page.tsx index ac46ea20..c06339ca 100644 --- a/src/app/economics/compare/page.tsx +++ b/src/app/economics/compare/page.tsx @@ -1,130 +1,115 @@ /* eslint-disable react/no-unescaped-entities */ +import Link from "next/link"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; -import { costEfficiencyScore, modelEconomics, type ModelEconomics } from "@/lib/economics"; +import { readEconomicsEntries, type EconomicsCategory } from "@/lib/economics"; -const formatCurrency = (value: number) => - new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - minimumFractionDigits: value < 1 ? 3 : 2, - maximumFractionDigits: value < 1 ? 3 : 2, - }).format(value); +const categoryOrder: EconomicsCategory[] = ["pricing", "costs", "revenue", "roi"]; -const scoreByUseCase = (model: ModelEconomics, useCase: string) => { - const qualityWeight = useCase === "feature-build" ? 0.6 : 0.45; - const speedWeight = useCase === "testing" ? 0.35 : 0.2; - const efficiencyWeight = 0.2; - const affinityWeight = 0.15; - - const affinity = model.recommendedFor.includes(useCase) ? 10 : 5; - - return ( - model.qualityRating * qualityWeight + - model.speedRating * speedWeight + - Math.min(costEfficiencyScore(model), 10) * efficiencyWeight + - affinity * affinityWeight - ); +const categoryNarrative: Record = { + pricing: "How to package, tier, and position value metrics.", + costs: "How to control spend and improve operational efficiency.", + revenue: "How to increase conversion, retention, and expansion.", + roi: "How to prove measurable business outcomes from deployment.", }; -const useCases = [ - { id: "feature-build", label: "Feature builds" }, - { id: "bug-fix", label: "Bug fixing" }, - { id: "code-review", label: "Code review" }, - { id: "testing", label: "Testing pipelines" }, - { id: "docs", label: "Documentation" }, -]; +export default async function EconomicsComparePage() { + const entries = await readEconomicsEntries(); + + const grouped = categoryOrder.map((category) => { + const items = entries.filter((entry) => entry.category === category); + const latest = items[0]; + const tags = Array.from(new Set(items.flatMap((entry) => entry.tags))).slice(0, 6); -export default function EconomicsComparePage() { - const ranked = [...modelEconomics].sort((a, b) => costEfficiencyScore(b) - costEfficiencyScore(a)); + return { + category, + count: items.length, + latest, + tags, + }; + }); return ( -
-
- - Model Cost Comparison +
+
+ + Economics Category Comparison -

Compare model economics side-by-side

-

- Compare price, context window, speed, and quality. Cost-efficiency score highlights how much quality you get per dollar. +

Compare economics categories side-by-side

+

+ This page reads persistent entries from data/economics.json and summarizes depth, latest updates, and common themes by category.

+
+ + ← Back to economics hub + +
-
-
- {ranked.map((model, index) => { - const score = costEfficiencyScore(model); - - return ( - - -
-
- {model.name} -

{model.provider}

-
- {index === 0 && Best value} -
-
- -
- Input / 1M - {formatCurrency(model.inputPricePer1M)} -
-
- Output / 1M - {formatCurrency(model.outputPricePer1M)} -
-
- Context window - {model.contextWindow.toLocaleString()} -
-
- Speed rating - {model.speedRating.toFixed(1)} / 10 -
-
- Quality rating - {model.qualityRating.toFixed(1)} / 10 -
-
- Cost-efficiency score - {score.toFixed(2)} -
-
-
- ); - })} +
+
+

Total entries: {entries.length}

+ + API endpoint → +
-
- +
+ {grouped.map((group) => ( + + +
+ + {group.category} + + {group.count} entries +
+ {group.category} +

{categoryNarrative[group.category]}

+
+ + {group.latest ? ( +
+

Latest update

+

{group.latest.title}

+

+ {new Date(group.latest.updatedAt).toLocaleDateString()} +

+
+ ) : ( +

No entries in this category yet.

+ )} -
-

Best model recommendations by use case

-
- {useCases.map((useCase) => { - const best = [...modelEconomics].sort( - (a, b) => scoreByUseCase(b, useCase.id) - scoreByUseCase(a, useCase.id) - )[0]; +
+

Top tags

+
+ {group.tags.length > 0 ? ( + group.tags.map((tag) => ( + + {tag} + + )) + ) : ( + No tags yet. + )} +
+
- return ( - - - {useCase.label} - - -

{best.name}

-

- Quality {best.qualityRating.toFixed(1)} · Speed {best.speedRating.toFixed(1)} · Efficiency {costEfficiencyScore(best).toFixed(2)} -

-
-
- ); - })} + + View {group.category} entries → + + + + ))}
diff --git a/src/app/economics/page.tsx b/src/app/economics/page.tsx index 3c55943d..48fef782 100644 --- a/src/app/economics/page.tsx +++ b/src/app/economics/page.tsx @@ -1,349 +1,125 @@ /* eslint-disable react/no-unescaped-entities */ -"use client"; - import Link from "next/link"; -import { useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { - calculateTokenCostUSD, - complexityMultipliers, - modelEconomics, - taskProfiles, - type ComplexityKey, - type TaskType, + filterEconomicsEntries, + readEconomicsEntries, + type EconomicsCategory, } from "@/lib/economics"; -const featuredModelIds = [ - "gpt-5.3", - "gpt-5.3-codex", - "claude-opus-4.1", - "claude-sonnet-4.5", - "gemini-2.5-pro", - "llama-4-maverick", -]; - -const complexityLabels: Record = { - low: "Low", - medium: "Medium", - high: "High", - extreme: "Extreme", +type EconomicsPageProps = { + searchParams?: Promise<{ + category?: string; + search?: string; + }>; }; -const formatCurrency = (value: number) => - new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - minimumFractionDigits: value < 1 ? 4 : 2, - maximumFractionDigits: value < 1 ? 4 : 2, - }).format(value); - -export default function EconomicsHubPage() { - const [taskType, setTaskType] = useState("bug-fix"); - const [complexity, setComplexity] = useState("medium"); - const [selectedModelId, setSelectedModelId] = useState("gpt-5.3-codex"); - - const [tasksPerDay, setTasksPerDay] = useState(120); - const [avgInputTokens, setAvgInputTokens] = useState(3200); - const [avgOutputTokens, setAvgOutputTokens] = useState(1400); - const [mixPrimary, setMixPrimary] = useState("gpt-5.3-codex"); - const [mixSecondary, setMixSecondary] = useState("claude-sonnet-4.5"); - const [mixTertiary, setMixTertiary] = useState("gemini-2.5-flash"); - const [mixPrimaryShare, setMixPrimaryShare] = useState(40); - const [mixSecondaryShare, setMixSecondaryShare] = useState(35); - - const tertiaryShare = Math.max(0, 100 - mixPrimaryShare - mixSecondaryShare); - - const featuredModels = useMemo( - () => modelEconomics.filter((model) => featuredModelIds.includes(model.id)), - [] - ); - - const selectedModel = - modelEconomics.find((model) => model.id === selectedModelId) ?? modelEconomics[0]; - - const taskEstimate = useMemo(() => { - const profile = taskProfiles[taskType]; - const multiplier = complexityMultipliers[complexity]; - const input = Math.round(profile.inputTokens * multiplier); - const output = Math.round(profile.outputTokens * multiplier); - const cost = calculateTokenCostUSD(selectedModel, input, output); +const categoryOrder: EconomicsCategory[] = ["pricing", "costs", "revenue", "roi"]; - return { - input, - output, - cost, - }; - }, [complexity, selectedModel, taskType]); - - const monthlyProjection = useMemo(() => { - const primaryModel = modelEconomics.find((model) => model.id === mixPrimary) ?? modelEconomics[0]; - const secondaryModel = - modelEconomics.find((model) => model.id === mixSecondary) ?? modelEconomics[1] ?? modelEconomics[0]; - const tertiaryModel = - modelEconomics.find((model) => model.id === mixTertiary) ?? modelEconomics[2] ?? modelEconomics[0]; - - const costFor = (modelId: string) => { - const model = modelEconomics.find((candidate) => candidate.id === modelId) ?? modelEconomics[0]; - return calculateTokenCostUSD(model, avgInputTokens, avgOutputTokens); - }; - - const weightedTaskCost = - costFor(primaryModel.id) * (mixPrimaryShare / 100) + - costFor(secondaryModel.id) * (mixSecondaryShare / 100) + - costFor(tertiaryModel.id) * (tertiaryShare / 100); +const categoryDescription: Record = { + pricing: "Packaging, tiering, and value-metric strategy", + costs: "Operating expenses, token spend, and infrastructure", + revenue: "Conversion, expansion, and retention outcomes", + roi: "Payback windows, baseline metrics, and impact tracking", +}; - const monthlyTasks = tasksPerDay * 30; +export default async function EconomicsHubPage({ searchParams }: EconomicsPageProps) { + const sp = (await searchParams) || {}; + const entries = await readEconomicsEntries(); + const filtered = filterEconomicsEntries(entries, { + category: sp.category, + search: sp.search, + }); - return { - weightedTaskCost, - monthlyTasks, - monthlyCost: weightedTaskCost * monthlyTasks, - annualCost: weightedTaskCost * monthlyTasks * 12, - models: [primaryModel, secondaryModel, tertiaryModel], - }; - }, [ - avgInputTokens, - avgOutputTokens, - mixPrimary, - mixPrimaryShare, - mixSecondary, - mixSecondaryShare, - mixTertiary, - tasksPerDay, - tertiaryShare, - ]); + const counts = categoryOrder.reduce>((acc, category) => { + acc[category] = entries.filter((entry) => entry.category === category).length; + return acc; + }, { pricing: 0, costs: 0, revenue: 0, roi: 0 }); return ( -
-
- - Agent Economics Hub +
+
+ + Economics Knowledge Base -

Agent Economics & Cost Calculator

-

- Understand true operating cost per task, compare model pricing, and project monthly agent spend before your workload scales. +

Economics playbooks for AI products

+

+ Persistent economics entries covering pricing, costs, revenue, and ROI. Filter content directly on this page or query the JSON API.

-
- - -
-
-

Model cost comparison

- - Open deep comparison → +
+ + All ({entries.length}) -
- -
- - - - - - - - - - - {featuredModels.map((model, index) => ( - - - - - - - ))} - -
ModelProviderInput / 1MOutput / 1M
{model.name}{model.provider}{formatCurrency(model.inputPricePer1M)}{formatCurrency(model.outputPricePer1M)}
+ {categoryOrder.map((category) => ( + + {category} ({counts[category]}) + + ))}
-
- - - Cost per task estimator - - -
- - -
- -
- - -
- -
- - -
- -
-

Projected cost for this task

-

{formatCurrency(taskEstimate.cost)}

-

- {taskEstimate.input.toLocaleString()} input + {taskEstimate.output.toLocaleString()} output tokens -

-
-
-
- - - - Monthly cost projection - - -
- - setTasksPerDay(Number(event.target.value))} - className="w-full mt-1" - /> -
- -
- - setAvgInputTokens(Number(event.target.value))} - className="w-full mt-1" - /> -
- -
- - setAvgOutputTokens(Number(event.target.value))} - className="w-full mt-1" - /> -
- -
-
- -
- - setMixPrimaryShare(Number(event.target.value))} className="w-full" /> -
-
- -
- -
- - setMixSecondaryShare(Number(event.target.value))} className="w-full" /> -
-
- -
- -

Tertiary mix auto-balances: {tertiaryShare}%

-
-
- -
-

Weighted cost per task

-

{formatCurrency(monthlyProjection.weightedTaskCost)}

-

Monthly: {formatCurrency(monthlyProjection.monthlyCost)}

-

Annual run-rate: {formatCurrency(monthlyProjection.annualCost)}

-
-
-
-
- - - -
-

How to reduce agent costs

-
- - - Caching strategy - - - Cache stable system prompts, docs retrieval blocks, and repeated context so agents spend fewer input tokens each run. - - - - - - Model routing - - - Route simple tests and docs work to lower-cost models. Escalate only complex feature and architecture tasks to premium models. - - - - - - Prompt optimization - - - Keep prompts short, structured, and explicit. Better instructions reduce retries, output bloat, and wasted token cycles. - - +
+
+

Showing {filtered.length} entries

+
+ + Compare categories → + + + API endpoint → + +
+ + {filtered.length === 0 ? ( +
+ No economics entries matched this filter. +
+ ) : ( +
+ {filtered.map((entry) => ( + + +
+ + {entry.category} + + + Updated {new Date(entry.updatedAt).toLocaleDateString()} + +
+ {entry.title} +

{categoryDescription[entry.category]}

+
+ +

{entry.content}

+
+ {entry.tags.map((tag) => ( + + {tag} + + ))} +
+
+
+ ))} +
+ )}
); diff --git a/src/lib/economics.ts b/src/lib/economics.ts index b3c9b566..aa215176 100644 --- a/src/lib/economics.ts +++ b/src/lib/economics.ts @@ -1,55 +1,103 @@ -import modelEconomicsData from "@/data/model-economics.json"; +import { promises as fs } from "fs"; +import path from "path"; -export interface ModelEconomics { +export type EconomicsCategory = "pricing" | "costs" | "revenue" | "roi"; + +export interface EconomicsEntry { id: string; - name: string; - provider: string; - family: string; - inputPricePer1M: number; - outputPricePer1M: number; - contextWindow: number; - speedRating: number; - qualityRating: number; - recommendedFor: string[]; -} - -export type TaskType = - | "code-review" - | "bug-fix" - | "feature-build" - | "docs" - | "testing"; - -export const modelEconomics = modelEconomicsData as ModelEconomics[]; - -export const taskProfiles: Record = { - "code-review": { label: "Code review", inputTokens: 4200, outputTokens: 1100 }, - "bug-fix": { label: "Bug fix", inputTokens: 6200, outputTokens: 1800 }, - "feature-build": { label: "Feature build", inputTokens: 12000, outputTokens: 4200 }, - docs: { label: "Documentation", inputTokens: 3500, outputTokens: 1700 }, - testing: { label: "Testing", inputTokens: 7000, outputTokens: 2300 }, -}; - -export const complexityMultipliers = { - low: 0.7, - medium: 1, - high: 1.5, - extreme: 2.2, -} as const; - -export type ComplexityKey = keyof typeof complexityMultipliers; - -export function calculateTokenCostUSD(model: ModelEconomics, inputTokens: number, outputTokens: number) { - const inputCost = (inputTokens / 1_000_000) * model.inputPricePer1M; - const outputCost = (outputTokens / 1_000_000) * model.outputPricePer1M; - return inputCost + outputCost; -} - -export function blendedPricePer1M(model: ModelEconomics) { - return (model.inputPricePer1M + model.outputPricePer1M) / 2; -} - -export function costEfficiencyScore(model: ModelEconomics) { - const efficiencyBase = model.qualityRating / Math.max(blendedPricePer1M(model), 0.1); - return efficiencyBase * 10; + title: string; + content: string; + category: EconomicsCategory; + tags: string[]; + updatedAt: string; +} + +interface EconomicsFilters { + category?: string | null; + search?: string | null; +} + +const ECONOMICS_PATH = path.join(process.cwd(), "data", "economics.json"); + +const economicsCategories: EconomicsCategory[] = ["pricing", "costs", "revenue", "roi"]; + +function isEconomicsCategory(value: string): value is EconomicsCategory { + return economicsCategories.includes(value as EconomicsCategory); +} + +function isEconomicsEntry(item: unknown): item is EconomicsEntry { + if (!item || typeof item !== "object") return false; + + const candidate = item as Record; + + return ( + typeof candidate.id === "string" && + typeof candidate.title === "string" && + typeof candidate.content === "string" && + typeof candidate.category === "string" && + isEconomicsCategory(candidate.category) && + Array.isArray(candidate.tags) && + candidate.tags.every((tag) => typeof tag === "string") && + typeof candidate.updatedAt === "string" + ); +} + +export async function readEconomicsEntries(): Promise { + try { + const raw = await fs.readFile(ECONOMICS_PATH, "utf-8"); + const parsed = JSON.parse(raw) as unknown; + + if (!Array.isArray(parsed)) return []; + + return parsed + .filter(isEconomicsEntry) + .sort( + (a, b) => + new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() + ); + } catch { + return []; + } +} + +export async function writeEconomicsEntries(entries: EconomicsEntry[]): Promise { + const dir = path.dirname(ECONOMICS_PATH); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(ECONOMICS_PATH, `${JSON.stringify(entries, null, 2)}\n`, "utf-8"); +} + +export function filterEconomicsEntries( + entries: EconomicsEntry[], + filters: EconomicsFilters +): EconomicsEntry[] { + const category = filters.category?.trim().toLowerCase(); + const search = filters.search?.trim().toLowerCase(); + + return entries.filter((entry) => { + if (category && isEconomicsCategory(category) && entry.category !== category) { + return false; + } + + if (!search) return true; + + return [entry.title, entry.content, entry.category, ...entry.tags] + .join(" ") + .toLowerCase() + .includes(search); + }); +} + +export function normalizeEconomicsCategory(value: string): EconomicsCategory | null { + const normalized = value.trim().toLowerCase(); + return isEconomicsCategory(normalized) ? normalized : null; +} + +export function createEconomicsEntryId(title: string): string { + const slug = title + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)/g, "") + .slice(0, 48); + + return `${slug || "economics-entry"}-${Date.now().toString(36)}`; }