@prenta/core 5.3.1 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/ai/content-writing-generate.d.ts.map +1 -1
  3. package/dist/ai/content-writing-generate.js +3 -0
  4. package/dist/ai/content-writing-generate.js.map +1 -1
  5. package/dist/ai/feature-defaults.d.ts +1 -1
  6. package/dist/ai/feature-defaults.d.ts.map +1 -1
  7. package/dist/ai/feature-defaults.js +3 -13
  8. package/dist/ai/feature-defaults.js.map +1 -1
  9. package/dist/ai/governance.d.ts +31 -0
  10. package/dist/ai/governance.d.ts.map +1 -0
  11. package/dist/ai/governance.js +56 -0
  12. package/dist/ai/governance.js.map +1 -0
  13. package/dist/ai/index.d.ts +2 -1
  14. package/dist/ai/index.d.ts.map +1 -1
  15. package/dist/ai/index.js +2 -1
  16. package/dist/ai/index.js.map +1 -1
  17. package/dist/ai/providers/custom.d.ts +2 -1
  18. package/dist/ai/providers/custom.d.ts.map +1 -1
  19. package/dist/ai/providers/custom.js +7 -1
  20. package/dist/ai/providers/custom.js.map +1 -1
  21. package/dist/ai/providers/http.d.ts +13 -0
  22. package/dist/ai/providers/http.d.ts.map +1 -1
  23. package/dist/ai/providers/http.js +33 -0
  24. package/dist/ai/providers/http.js.map +1 -1
  25. package/dist/ai/providers/index.d.ts +5 -2
  26. package/dist/ai/providers/index.d.ts.map +1 -1
  27. package/dist/ai/providers/index.js +10 -0
  28. package/dist/ai/providers/index.js.map +1 -1
  29. package/dist/ai/providers/openai.d.ts +2 -1
  30. package/dist/ai/providers/openai.d.ts.map +1 -1
  31. package/dist/ai/providers/openai.js +4 -1
  32. package/dist/ai/providers/openai.js.map +1 -1
  33. package/dist/ai/runtime.d.ts +28 -1
  34. package/dist/ai/runtime.d.ts.map +1 -1
  35. package/dist/ai/runtime.js +73 -2
  36. package/dist/ai/runtime.js.map +1 -1
  37. package/dist/ai/usage.d.ts +6 -0
  38. package/dist/ai/usage.d.ts.map +1 -1
  39. package/dist/ai/usage.js +29 -0
  40. package/dist/ai/usage.js.map +1 -1
  41. package/dist/api/route-helpers.d.ts +2 -0
  42. package/dist/api/route-helpers.d.ts.map +1 -1
  43. package/dist/api/route-helpers.js +14 -0
  44. package/dist/api/route-helpers.js.map +1 -1
  45. package/dist/api/routes/ai-authoring.js +1 -1
  46. package/dist/api/routes/ai-authoring.js.map +1 -1
  47. package/dist/api/routes/ai-seo.d.ts.map +1 -1
  48. package/dist/api/routes/ai-seo.js +13 -5
  49. package/dist/api/routes/ai-seo.js.map +1 -1
  50. package/dist/api/routes/ai-settings.d.ts.map +1 -1
  51. package/dist/api/routes/ai-settings.js +39 -1
  52. package/dist/api/routes/ai-settings.js.map +1 -1
  53. package/dist/api/routes/redirects.d.ts.map +1 -1
  54. package/dist/api/routes/redirects.js +3 -2
  55. package/dist/api/routes/redirects.js.map +1 -1
  56. package/dist/api/routes/scheduling.d.ts.map +1 -1
  57. package/dist/api/routes/scheduling.js +17 -0
  58. package/dist/api/routes/scheduling.js.map +1 -1
  59. package/dist/api/routes/seo-public.d.ts.map +1 -1
  60. package/dist/api/routes/seo-public.js +205 -1
  61. package/dist/api/routes/seo-public.js.map +1 -1
  62. package/dist/api/routes/seo.d.ts.map +1 -1
  63. package/dist/api/routes/seo.js +216 -16
  64. package/dist/api/routes/seo.js.map +1 -1
  65. package/dist/db/model-types.d.ts +32 -0
  66. package/dist/db/model-types.d.ts.map +1 -1
  67. package/dist/generated/browser.d.ts +17 -0
  68. package/dist/generated/client.d.ts +17 -0
  69. package/dist/generated/commonInputTypes.d.ts +30 -30
  70. package/dist/generated/internal/class.d.ts +22 -0
  71. package/dist/generated/internal/class.js +4 -4
  72. package/dist/generated/internal/prismaNamespace.d.ts +198 -1
  73. package/dist/generated/internal/prismaNamespace.js +43 -0
  74. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +47 -0
  75. package/dist/generated/internal/prismaNamespaceBrowser.js +43 -0
  76. package/dist/generated/models/ApiRequestMetric.d.ts +0 -7
  77. package/dist/generated/models/SearchPageInsight.d.ts +1460 -0
  78. package/dist/generated/models/SearchPageInsight.js +1 -0
  79. package/dist/generated/models/SearchPerfDaily.d.ts +1167 -0
  80. package/dist/generated/models/SearchPerfDaily.js +1 -0
  81. package/dist/generated/models.d.ts +2 -0
  82. package/dist/redirects/recovery.d.ts +12 -0
  83. package/dist/redirects/recovery.d.ts.map +1 -1
  84. package/dist/redirects/recovery.js +42 -2
  85. package/dist/redirects/recovery.js.map +1 -1
  86. package/dist/redirects/rerank.d.ts +14 -1
  87. package/dist/redirects/rerank.d.ts.map +1 -1
  88. package/dist/redirects/rerank.js +66 -37
  89. package/dist/redirects/rerank.js.map +1 -1
  90. package/dist/seo/audit-engine.d.ts +21 -3
  91. package/dist/seo/audit-engine.d.ts.map +1 -1
  92. package/dist/seo/audit-engine.js +51 -6
  93. package/dist/seo/audit-engine.js.map +1 -1
  94. package/dist/seo/audit-runner.d.ts.map +1 -1
  95. package/dist/seo/audit-runner.js +18 -0
  96. package/dist/seo/audit-runner.js.map +1 -1
  97. package/dist/seo/config-store.d.ts +13 -0
  98. package/dist/seo/config-store.d.ts.map +1 -1
  99. package/dist/seo/config-store.js +6 -0
  100. package/dist/seo/config-store.js.map +1 -1
  101. package/dist/seo/crux.d.ts +58 -0
  102. package/dist/seo/crux.d.ts.map +1 -0
  103. package/dist/seo/crux.js +101 -0
  104. package/dist/seo/crux.js.map +1 -0
  105. package/dist/seo/issue-content-brief.d.ts.map +1 -1
  106. package/dist/seo/issue-content-brief.js +11 -0
  107. package/dist/seo/issue-content-brief.js.map +1 -1
  108. package/dist/seo/issue-content-research.d.ts.map +1 -1
  109. package/dist/seo/issue-content-research.js +3 -0
  110. package/dist/seo/issue-content-research.js.map +1 -1
  111. package/dist/seo/issue-fix-ai.d.ts.map +1 -1
  112. package/dist/seo/issue-fix-ai.js +3 -0
  113. package/dist/seo/issue-fix-ai.js.map +1 -1
  114. package/dist/seo/issue-fix-redirect.d.ts.map +1 -1
  115. package/dist/seo/issue-fix-redirect.js +1 -0
  116. package/dist/seo/issue-fix-redirect.js.map +1 -1
  117. package/dist/seo/search-console/client.d.ts +39 -0
  118. package/dist/seo/search-console/client.d.ts.map +1 -0
  119. package/dist/seo/search-console/client.js +226 -0
  120. package/dist/seo/search-console/client.js.map +1 -0
  121. package/dist/seo/search-console/prompt-context.d.ts +21 -0
  122. package/dist/seo/search-console/prompt-context.d.ts.map +1 -0
  123. package/dist/seo/search-console/prompt-context.js +93 -0
  124. package/dist/seo/search-console/prompt-context.js.map +1 -0
  125. package/dist/seo/search-console/sync.d.ts +67 -0
  126. package/dist/seo/search-console/sync.d.ts.map +1 -0
  127. package/dist/seo/search-console/sync.js +469 -0
  128. package/dist/seo/search-console/sync.js.map +1 -0
  129. package/dist/seo/search-console/types.d.ts +75 -0
  130. package/dist/seo/search-console/types.d.ts.map +1 -0
  131. package/dist/seo/search-console/types.js +16 -0
  132. package/dist/seo/search-console/types.js.map +1 -0
  133. package/dist/seo/search-insight-signals.d.ts +29 -0
  134. package/dist/seo/search-insight-signals.d.ts.map +1 -0
  135. package/dist/seo/search-insight-signals.js +103 -0
  136. package/dist/seo/search-insight-signals.js.map +1 -0
  137. package/package.json +1 -1
  138. package/prisma/migrations/0016_search_console_tables/migration.sql +56 -0
  139. package/prisma/schema.prisma +49 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/seo/search-console/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAsBH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAwB;IAErC,YAAY,IAA4B,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAA;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import type { AuditEntityInput, AuditExtraSignals } from './audit-engine.js';
2
+ /** Fire `declining-performance` when clicks drop by at least this fraction. */
3
+ export declare const DECLINE_DROP_RATIO = 0.4;
4
+ /** …and the prior window had at least this many clicks (noise floor). */
5
+ export declare const DECLINE_MIN_PREV_CLICKS = 50;
6
+ export interface SearchInsightRow {
7
+ path: string;
8
+ entityId: string | null;
9
+ clicks28d: number;
10
+ clicksPrev28d: number;
11
+ impressions28d: number;
12
+ lcpMs: number | null;
13
+ inpMs: number | null;
14
+ cls: number | null;
15
+ cwvStatus: string | null;
16
+ }
17
+ export interface SearchInsightSignals {
18
+ slowPages: NonNullable<AuditExtraSignals['slowPages']>;
19
+ decliningPages: NonNullable<AuditExtraSignals['decliningPages']>;
20
+ impressionsByEntityId: Record<string, number>;
21
+ }
22
+ /**
23
+ * Pure detector — maps insight rows onto audited entities. Exported for unit
24
+ * tests; `gatherSearchInsightSignals` is the DB-reading wrapper.
25
+ */
26
+ export declare function detectSearchInsightSignals(rows: SearchInsightRow[], entities: Pick<AuditEntityInput, 'entityId' | 'entityType' | 'title' | 'url'>[]): SearchInsightSignals;
27
+ /** Load insight rows and derive audit signals. Empty signals when the table is absent. */
28
+ export declare function gatherSearchInsightSignals(db: any, entities: Pick<AuditEntityInput, 'entityId' | 'entityType' | 'title' | 'url'>[]): Promise<SearchInsightSignals>;
29
+ //# sourceMappingURL=search-insight-signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-insight-signals.d.ts","sourceRoot":"","sources":["../../src/seo/search-insight-signals.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE5E,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,yEAAyE;AACzE,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,WAAW,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAA;IACtD,cAAc,EAAE,WAAW,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAChE,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C;AAYD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,gBAAgB,EAAE,EACxB,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,KAAK,CAAC,EAAE,GAC9E,oBAAoB,CA0CtB;AAED,0FAA0F;AAC1F,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,GAAG,EACP,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,KAAK,CAAC,EAAE,GAC9E,OAAO,CAAC,oBAAoB,CAAC,CA0B/B"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Audit signals derived from synced Search Console / CrUX data
3
+ * (`prenta_search_page_insights`). The sync engine only writes data — this
4
+ * module is how the audit engine (the single issue writer) consumes it:
5
+ *
6
+ * - `cwvStatus: 'poor'` rows become `slowPages` (the existing signal that
7
+ * finally moves the performance category off its neutral 70).
8
+ * - A ≥40% click drop vs the prior 28-day window (with meaningful prior
9
+ * volume) becomes a `decliningPages` entry → `declining-performance` issue.
10
+ * - `impressionsByEntityId` lets quick-wins ranking weight issue groups by
11
+ * real traffic.
12
+ *
13
+ * Only insight rows that match an audited entity produce signals — an issue
14
+ * row must point at a document an editor can act on.
15
+ */
16
+ import { hasModel } from '../api/route-helpers.js';
17
+ /** Fire `declining-performance` when clicks drop by at least this fraction. */
18
+ export const DECLINE_DROP_RATIO = 0.4;
19
+ /** …and the prior window had at least this many clicks (noise floor). */
20
+ export const DECLINE_MIN_PREV_CLICKS = 50;
21
+ /** Human-readable CWV summary for the slow-page issue description. */
22
+ function cwvNote(row) {
23
+ const parts = [];
24
+ if (row.lcpMs !== null)
25
+ parts.push(`LCP ${(row.lcpMs / 1000).toFixed(1)}s`);
26
+ if (row.inpMs !== null)
27
+ parts.push(`INP ${row.inpMs}ms`);
28
+ if (row.cls !== null)
29
+ parts.push(`CLS ${row.cls}`);
30
+ const detail = parts.length > 0 ? ` (p75: ${parts.join(', ')})` : '';
31
+ return `Real-user Core Web Vitals from the Chrome UX Report are in the "poor" range${detail}.`;
32
+ }
33
+ /**
34
+ * Pure detector — maps insight rows onto audited entities. Exported for unit
35
+ * tests; `gatherSearchInsightSignals` is the DB-reading wrapper.
36
+ */
37
+ export function detectSearchInsightSignals(rows, entities) {
38
+ const byId = new Map(entities.map((e) => [e.entityId, e]));
39
+ const byUrl = new Map(entities.map((e) => [e.url, e]));
40
+ const slowPages = [];
41
+ const decliningPages = [];
42
+ const impressionsByEntityId = {};
43
+ for (const row of rows) {
44
+ const entity = (row.entityId && byId.get(row.entityId)) || byUrl.get(row.path);
45
+ if (!entity)
46
+ continue;
47
+ if (row.impressions28d > 0) {
48
+ impressionsByEntityId[entity.entityId] =
49
+ (impressionsByEntityId[entity.entityId] ?? 0) + row.impressions28d;
50
+ }
51
+ if (row.cwvStatus === 'poor') {
52
+ slowPages.push({
53
+ entityId: entity.entityId,
54
+ title: entity.title,
55
+ url: entity.url,
56
+ note: cwvNote(row),
57
+ });
58
+ }
59
+ if (row.clicksPrev28d >= DECLINE_MIN_PREV_CLICKS &&
60
+ row.clicks28d <= row.clicksPrev28d * (1 - DECLINE_DROP_RATIO)) {
61
+ decliningPages.push({
62
+ entityId: entity.entityId,
63
+ entityType: entity.entityType,
64
+ title: entity.title,
65
+ url: entity.url,
66
+ clicks28d: row.clicks28d,
67
+ clicksPrev28d: row.clicksPrev28d,
68
+ });
69
+ }
70
+ }
71
+ return { slowPages, decliningPages, impressionsByEntityId };
72
+ }
73
+ /** Load insight rows and derive audit signals. Empty signals when the table is absent. */
74
+ export async function gatherSearchInsightSignals(db, entities) {
75
+ const empty = {
76
+ slowPages: [],
77
+ decliningPages: [],
78
+ impressionsByEntityId: {},
79
+ };
80
+ if (!db || !hasModel(db, 'searchPageInsight'))
81
+ return empty;
82
+ try {
83
+ const rows = (await db.searchPageInsight.findMany({
84
+ select: {
85
+ path: true,
86
+ entityId: true,
87
+ clicks28d: true,
88
+ clicksPrev28d: true,
89
+ impressions28d: true,
90
+ lcpMs: true,
91
+ inpMs: true,
92
+ cls: true,
93
+ cwvStatus: true,
94
+ },
95
+ take: 5000,
96
+ }));
97
+ return detectSearchInsightSignals(rows, entities);
98
+ }
99
+ catch {
100
+ return empty;
101
+ }
102
+ }
103
+ //# sourceMappingURL=search-insight-signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-insight-signals.js","sourceRoot":"","sources":["../../src/seo/search-insight-signals.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prenta/core",
3
- "version": "5.3.1",
3
+ "version": "5.4.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,56 @@
1
+ -- Search Console + CrUX integration (spec: 2026-08-29-search-console-integration-design.md).
2
+ -- Two additive tables written by the nightly search sync:
3
+ -- prenta_search_perf_daily — (date, path) GSC search analytics, 90-day rolling window
4
+ -- prenta_search_page_insights — per-page 28d totals/deltas, top-query snapshot, CrUX p75s
5
+ -- Dates are stored exactly as GSC reports them (property timezone), never converted to UTC.
6
+
7
+ CREATE TABLE "prenta_search_perf_daily" (
8
+ "id" TEXT NOT NULL,
9
+ "date" DATE NOT NULL,
10
+ "path" TEXT NOT NULL,
11
+ "clicks" INTEGER NOT NULL DEFAULT 0,
12
+ "impressions" INTEGER NOT NULL DEFAULT 0,
13
+ "ctr" DOUBLE PRECISION NOT NULL DEFAULT 0,
14
+ "position" DOUBLE PRECISION NOT NULL DEFAULT 0,
15
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+
17
+ CONSTRAINT "prenta_search_perf_daily_pkey" PRIMARY KEY ("id")
18
+ );
19
+
20
+ -- Upsert key for the nightly sync (idempotent re-fetch of lagging days).
21
+ CREATE UNIQUE INDEX "prenta_search_perf_daily_date_path_key" ON "prenta_search_perf_daily"("date", "path");
22
+
23
+ -- Per-page trend queries (28d windows).
24
+ CREATE INDEX "prenta_search_perf_daily_path_idx" ON "prenta_search_perf_daily"("path");
25
+
26
+ CREATE TABLE "prenta_search_page_insights" (
27
+ "id" TEXT NOT NULL,
28
+ "path" TEXT NOT NULL,
29
+ "entityId" TEXT,
30
+ "entityType" TEXT,
31
+ "topQueries" JSONB,
32
+ "clicks28d" INTEGER NOT NULL DEFAULT 0,
33
+ "impressions28d" INTEGER NOT NULL DEFAULT 0,
34
+ "avgPosition28d" DOUBLE PRECISION,
35
+ "clicksPrev28d" INTEGER NOT NULL DEFAULT 0,
36
+ "impressionsPrev28d" INTEGER NOT NULL DEFAULT 0,
37
+ "avgPositionPrev28d" DOUBLE PRECISION,
38
+ "lcpMs" INTEGER,
39
+ "inpMs" INTEGER,
40
+ "cls" DOUBLE PRECISION,
41
+ "cwvStatus" TEXT,
42
+ "cwvCollectedAt" TIMESTAMP(3),
43
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
44
+ "lastSyncedAt" TIMESTAMP(3) NOT NULL,
45
+
46
+ CONSTRAINT "prenta_search_page_insights_pkey" PRIMARY KEY ("id")
47
+ );
48
+
49
+ -- One insight row per normalized path.
50
+ CREATE UNIQUE INDEX "prenta_search_page_insights_path_key" ON "prenta_search_page_insights"("path");
51
+
52
+ -- "Top pages by impressions" ordering (overview table, CrUX rotation).
53
+ CREATE INDEX "prenta_search_page_insights_impressions28d_idx" ON "prenta_search_page_insights"("impressions28d");
54
+
55
+ -- Document-scoped lookups (editor pane, AI prompt context).
56
+ CREATE INDEX "prenta_search_page_insights_entityId_idx" ON "prenta_search_page_insights"("entityId");
@@ -571,6 +571,55 @@ model AiCrawlerHit {
571
571
  @@map("prenta_ai_crawler_hits")
572
572
  }
573
573
 
574
+ /// One row per (page path, day) of Google Search Console search analytics,
575
+ /// written by the nightly search sync. Dates are stored exactly as GSC
576
+ /// reports them (property timezone) — never converted to UTC. Rolling
577
+ /// 90-day window, pruned during sync.
578
+ model SearchPerfDaily {
579
+ id String @id @default(cuid())
580
+ date DateTime @db.Date
581
+ path String
582
+ clicks Int @default(0)
583
+ impressions Int @default(0)
584
+ ctr Float @default(0)
585
+ position Float @default(0)
586
+ createdAt DateTime @default(now())
587
+
588
+ @@unique([date, path])
589
+ @@index([path])
590
+ @@map("prenta_search_perf_daily")
591
+ }
592
+
593
+ /// Per-page search insight snapshot: trailing-28-day totals + prior-window
594
+ /// deltas (computed from SearchPerfDaily), top queries from GSC, and CrUX
595
+ /// Core Web Vitals p75s. One row per normalized path, upserted by the sync.
596
+ /// `cwvStatus` null means CrUX has no field data for the URL (normal for
597
+ /// low-traffic pages), never an error.
598
+ model SearchPageInsight {
599
+ id String @id @default(cuid())
600
+ path String @unique
601
+ entityId String?
602
+ entityType String?
603
+ topQueries Json?
604
+ clicks28d Int @default(0)
605
+ impressions28d Int @default(0)
606
+ avgPosition28d Float?
607
+ clicksPrev28d Int @default(0)
608
+ impressionsPrev28d Int @default(0)
609
+ avgPositionPrev28d Float?
610
+ lcpMs Int?
611
+ inpMs Int?
612
+ cls Float?
613
+ cwvStatus String?
614
+ cwvCollectedAt DateTime?
615
+ createdAt DateTime @default(now())
616
+ lastSyncedAt DateTime @updatedAt
617
+
618
+ @@index([impressions28d])
619
+ @@index([entityId])
620
+ @@map("prenta_search_page_insights")
621
+ }
622
+
574
623
  /// An AI/heuristic suggestion for a 404 destination. Confidence-scored and
575
624
  /// never auto-applied below the configured threshold.
576
625
  model RedirectSuggestion {