@rubytech/create-realagent-code 0.1.117 → 0.1.120

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 (137) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +124 -1
  3. package/payload/platform/package-lock.json +141 -125
  4. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  5. package/payload/platform/plugins/aeo/.claude-plugin/plugin.json +17 -0
  6. package/payload/platform/plugins/aeo/PLUGIN.md +81 -0
  7. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts +2 -0
  8. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts.map +1 -0
  9. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js +121 -0
  10. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js.map +1 -0
  11. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts +2 -0
  12. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts.map +1 -0
  13. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js +129 -0
  14. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js.map +1 -0
  15. package/payload/platform/plugins/aeo/mcp/dist/index.d.ts +2 -0
  16. package/payload/platform/plugins/aeo/mcp/dist/index.d.ts.map +1 -0
  17. package/payload/platform/plugins/aeo/mcp/dist/index.js +171 -0
  18. package/payload/platform/plugins/aeo/mcp/dist/index.js.map +1 -0
  19. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts +27 -0
  20. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts.map +1 -0
  21. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js +274 -0
  22. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js.map +1 -0
  23. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts +5 -0
  24. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts.map +1 -0
  25. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js +38 -0
  26. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js.map +1 -0
  27. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts +48 -0
  28. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts.map +1 -0
  29. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js +254 -0
  30. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js.map +1 -0
  31. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts +25 -0
  32. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts.map +1 -0
  33. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js +78 -0
  34. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js.map +1 -0
  35. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts +18 -0
  36. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts.map +1 -0
  37. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js +56 -0
  38. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js.map +1 -0
  39. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +37 -0
  40. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -0
  41. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +94 -0
  42. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -0
  43. package/payload/platform/plugins/aeo/mcp/package.json +22 -0
  44. package/payload/platform/plugins/aeo/mcp/vitest.config.ts +9 -0
  45. package/payload/platform/plugins/aeo/skills/structured-answer/SKILL.md +53 -0
  46. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts +12 -1
  47. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -1
  48. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +34 -1
  49. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -1
  50. package/payload/platform/plugins/docs/references/aeo.md +87 -0
  51. package/payload/platform/plugins/docs/references/visitor-graph.md +82 -0
  52. package/payload/platform/plugins/email/PLUGIN.md +6 -0
  53. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +180 -0
  54. package/payload/platform/plugins/memory/mcp/dist/index.js +5 -0
  55. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  56. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.d.ts +2 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.d.ts.map +1 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js +62 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js.map +1 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts +18 -0
  61. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts.map +1 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js +29 -0
  63. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js.map +1 -0
  64. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +2 -0
  65. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
  66. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +1 -1
  67. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
  68. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +3 -0
  69. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  70. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +9 -1
  71. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  72. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +8 -0
  74. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  75. package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
  76. package/payload/platform/plugins/memory/references/schema-base.md +49 -1
  77. package/payload/platform/scripts/smoke-boot-services.sh +30 -2
  78. package/payload/platform/templates/specialists/agents/signal-detector.md +1 -1
  79. package/payload/premium-plugins/real-agent/plugins/buyers/.claude-plugin/plugin.json +1 -1
  80. package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +33 -3
  81. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.d.ts +2 -0
  82. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.d.ts.map +1 -0
  83. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js +97 -0
  84. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js.map +1 -0
  85. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.d.ts +5 -0
  86. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.d.ts.map +1 -0
  87. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.js +40 -0
  88. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.js.map +1 -0
  89. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts +24 -0
  90. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts.map +1 -0
  91. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js +156 -0
  92. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js.map +1 -0
  93. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.d.ts +21 -0
  94. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.d.ts.map +1 -0
  95. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.js +65 -0
  96. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.js.map +1 -0
  97. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.d.ts +26 -0
  98. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.d.ts.map +1 -0
  99. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.js +78 -0
  100. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.js.map +1 -0
  101. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.d.ts +19 -0
  102. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.d.ts.map +1 -0
  103. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.js +41 -0
  104. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.js.map +1 -0
  105. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.d.ts +20 -0
  106. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.d.ts.map +1 -0
  107. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.js +44 -0
  108. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.js.map +1 -0
  109. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.d.ts +22 -0
  110. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.d.ts.map +1 -0
  111. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.js +41 -0
  112. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.js.map +1 -0
  113. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.d.ts +32 -0
  114. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.d.ts.map +1 -0
  115. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.js +87 -0
  116. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.js.map +1 -0
  117. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/package-lock.json +2566 -0
  118. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/package.json +21 -0
  119. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/index.ts +145 -0
  120. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/lib/neo4j.ts +45 -0
  121. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-backfill-from-logs.ts +189 -0
  122. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-engagement-score.ts +87 -0
  123. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-event-ingest.ts +119 -0
  124. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-recent-by-page.ts +61 -0
  125. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-recent-by-person.ts +65 -0
  126. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-recommendation-ctr.ts +67 -0
  127. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-session-detail.ts +127 -0
  128. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/tsconfig.json +20 -0
  129. package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +11 -0
  130. package/payload/premium-plugins/real-agent/plugins/leads/skills/lead-nurturing/SKILL.md +9 -0
  131. package/payload/premium-plugins/real-agent/plugins/leads/skills/morning-round/SKILL.md +11 -0
  132. package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-updates/SKILL.md +13 -0
  133. package/payload/server/{chunk-FJCI6X3H.js → chunk-HCYM5FLU.js} +2 -0
  134. package/payload/server/maxy-edge.js +1 -1
  135. package/payload/server/public/privacy.html +66 -0
  136. package/payload/server/public/v.js +191 -0
  137. package/payload/server/server.js +473 -85
@@ -0,0 +1,97 @@
1
+ // buyers MCP server — visitor-graph tools (Task 357).
2
+ //
3
+ // Seven tools, all admin-side (none exposed on the public agent):
4
+ // visitor-recent-by-person query
5
+ // visitor-recent-by-page query
6
+ // visitor-engagement-score query
7
+ // visitor-recommendation-ctr query (replaces 336 log-scan after parity)
8
+ // visitor-session-detail query
9
+ // visitor-event-ingest write (test harness / manual replay)
10
+ // visitor-backfill-from-logs write (one-shot importer + parity check)
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { z } from "zod";
14
+ import { closeDriver } from "./lib/neo4j.js";
15
+ import { visitorRecentByPerson } from "./tools/visitor-recent-by-person.js";
16
+ import { visitorRecentByPage } from "./tools/visitor-recent-by-page.js";
17
+ import { visitorEngagementScore } from "./tools/visitor-engagement-score.js";
18
+ import { visitorRecommendationCtr } from "./tools/visitor-recommendation-ctr.js";
19
+ import { visitorSessionDetail } from "./tools/visitor-session-detail.js";
20
+ import { visitorEventIngest } from "./tools/visitor-event-ingest.js";
21
+ import { visitorBackfillFromLogs } from "./tools/visitor-backfill-from-logs.js";
22
+ const accountId = process.env.ACCOUNT_ID ?? "local";
23
+ const server = new McpServer({ name: "maxy-buyers", version: "0.1.0" });
24
+ console.error(`[buyers] server started, account=${accountId}`);
25
+ function jsonHandler(fn) {
26
+ return async (params) => {
27
+ try {
28
+ const result = await fn({ ...params, accountId });
29
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
30
+ }
31
+ catch (err) {
32
+ return {
33
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
34
+ isError: true,
35
+ };
36
+ }
37
+ };
38
+ }
39
+ server.tool("visitor-recent-by-person", "List recent visitor sessions for a known :Person. Returns per-session pageview/click/scroll/dwell counts and the listings each session touched. Use for morning-round briefings and 1:1 outreach context.", {
40
+ personId: z.string().min(1).describe(":Person elementId"),
41
+ sinceDays: z.number().int().positive().optional().describe("Lookback window in days (default 30)"),
42
+ limit: z.number().int().positive().max(200).optional().describe("Max sessions to return (default 50)"),
43
+ }, jsonHandler((p) => visitorRecentByPerson(p)));
44
+ server.tool("visitor-recent-by-page", "List recent visitors (Person + AnonVisitor) of a given listing slug or URL. Use to answer 'who has shown interest in 12 Oak Street this week?'.", {
45
+ listingSlug: z.string().optional().describe("Listing slug (one of slug/url required)"),
46
+ url: z.string().optional().describe("Page URL (one of slug/url required)"),
47
+ sinceDays: z.number().int().positive().optional().describe("Lookback window in days (default 7)"),
48
+ limit: z.number().int().positive().max(200).optional().describe("Max visitors to return (default 50)"),
49
+ }, jsonHandler((p) => visitorRecentByPage(p)));
50
+ server.tool("visitor-engagement-score", "Engagement-ranked :Person list. score = visits * recency * scroll_depth * dwell. Use to build the nurture-list and prioritise outbound.", {
51
+ sinceDays: z.number().int().positive().optional().describe("Lookback window in days (default 30)"),
52
+ limit: z.number().int().positive().max(200).optional().describe("Max persons (default 50)"),
53
+ }, jsonHandler((p) => visitorEngagementScore(p)));
54
+ server.tool("visitor-recommendation-ctr", "Graph-backed click-through rate for property recommendations over a window. Replaces the Task 336 log-scan after the 14-day parity cutover.", {
55
+ sinceDays: z.number().int().positive().optional().describe("Lookback window in days (default 7)"),
56
+ listingSlug: z.string().optional().describe("Optional listing-slug filter"),
57
+ }, jsonHandler((p) => visitorRecommendationCtr(p)));
58
+ server.tool("visitor-session-detail", "Full event timeline for one :Session (pageviews, clicks, scroll milestones in chronological order). Use for diagnosis and outreach prep.", {
59
+ sessionId: z.string().min(1).describe(":Session.sessionId"),
60
+ }, jsonHandler((p) => visitorSessionDetail(p)));
61
+ server.tool("visitor-event-ingest", "Admin-side companion to POST /v/event for test harness and manual replay. Writes a single event into the graph.", {
62
+ type: z.enum(["pageview", "click", "scroll", "dwell"]).describe("Event type"),
63
+ sessionId: z.string().min(1).describe("Client-provided session id"),
64
+ visitorId: z.string().min(1).describe("Client-provided visitor id"),
65
+ personId: z.string().optional().nullable().describe("Optional :Person elementId override"),
66
+ url: z.string().min(1).describe("Page URL"),
67
+ path: z.string().optional(),
68
+ title: z.string().optional(),
69
+ pageViewId: z.string().optional(),
70
+ site: z.string().optional(),
71
+ referrer: z.string().optional(),
72
+ label: z.string().optional().describe("Click label (data-track value)"),
73
+ href: z.string().optional(),
74
+ depth: z.number().int().min(0).max(100).optional().describe("Scroll depth (25/50/75/100)"),
75
+ dwellMs: z.number().int().min(0).optional(),
76
+ occurredAt: z.string().optional().describe("ISO timestamp override"),
77
+ }, jsonHandler((p) => visitorEventIngest(p)));
78
+ server.tool("visitor-backfill-from-logs", "One-shot importer: parse [property-recommended] and [property-card-click] log lines and write into the visitor graph. Idempotent for Recommendations; immutable for Click events. Reports a parityCheck comparing graph counts to log counts.", {
79
+ logPath: z.string().min(1).describe("Absolute path to the platform log file"),
80
+ sinceIso: z.string().optional().describe("ISO timestamp lower bound (inclusive)"),
81
+ untilIso: z.string().optional().describe("ISO timestamp upper bound (exclusive)"),
82
+ batchSize: z.number().int().positive().max(2000).optional().describe("Lines per Cypher batch (default 200)"),
83
+ }, jsonHandler((p) => visitorBackfillFromLogs(p)));
84
+ process.on("SIGINT", async () => {
85
+ console.error("[buyers] SIGINT — closing driver");
86
+ await closeDriver();
87
+ process.exit(0);
88
+ });
89
+ process.on("SIGTERM", async () => {
90
+ console.error("[buyers] SIGTERM — closing driver");
91
+ await closeDriver();
92
+ process.exit(0);
93
+ });
94
+ const transport = new StdioServerTransport();
95
+ await server.connect(transport);
96
+ console.error("[buyers] ready");
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,kEAAkE;AAClE,wCAAwC;AACxC,wCAAwC;AACxC,wCAAwC;AACxC,6EAA6E;AAC7E,wCAAwC;AACxC,uEAAuE;AACvE,2EAA2E;AAE3E,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;AAEpD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAExE,OAAO,CAAC,KAAK,CAAC,oCAAoC,SAAS,EAAE,CAAC,CAAC;AAE/D,SAAS,WAAW,CAAI,EAA8C;IACpE,OAAO,KAAK,EAAE,MAA+B,EAAE,EAAE;QAC/C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAClD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACzF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACxG,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,2MAA2M,EAC3M;IACE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACzD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IAClG,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;CACvG,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,CAA2D,CAAC,CAAC,CACvG,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,iJAAiJ,EACjJ;IACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;IACtF,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IAC1E,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IACjG,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;CACvG,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAyD,CAAC,CAAC,CACnG,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,yIAAyI,EACzI;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IAClG,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;CAC5F,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,CAA4D,CAAC,CAAC,CACzG,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,4BAA4B,EAC5B,6IAA6I,EAC7I;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IACjG,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;CAC5E,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,wBAAwB,CAAC,CAA8D,CAAC,CAAC,CAC7G,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,0IAA0I,EAC1I;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC;CAC5D,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,CAA0D,CAAC,CAAC,CACrG,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,iHAAiH,EACjH;IACE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;IAC7E,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,4BAA4B,CAAC;IACnE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,4BAA4B,CAAC;IACnE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IAC1F,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IACvE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;IAC1F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;CACrE,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAwD,CAAC,CAAC,CACjG,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,4BAA4B,EAC5B,+OAA+O,EAC/O;IACE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;IAC7E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACjF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACjF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;CAC7G,EACD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,CAA6D,CAAC,CAAC,CAC3G,CAAC;AAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAClD,MAAM,WAAW,EAAE,CAAC;IACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IAC/B,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACnD,MAAM,WAAW,EAAE,CAAC;IACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAChC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Driver, Session } from "neo4j-driver";
2
+ export declare function getDriver(): Driver;
3
+ export declare function getSession(): Session;
4
+ export declare function closeDriver(): Promise<void>;
5
+ //# sourceMappingURL=neo4j.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neo4j.d.ts","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAIA,OAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAmBtD,wBAAgB,SAAS,IAAI,MAAM,CAUlC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjD"}
@@ -0,0 +1,40 @@
1
+ // Neo4j driver/session helper — same pattern as loop/mcp/src/lib/neo4j.ts.
2
+ // NEO4J_URI must be explicit; defaulting to localhost cross-pollinates
3
+ // Maxy and Real Agent databases on a shared dev host.
4
+ import neo4j from "neo4j-driver";
5
+ import { readFileSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ let driver = null;
8
+ function readPassword() {
9
+ if (process.env.NEO4J_PASSWORD)
10
+ return process.env.NEO4J_PASSWORD;
11
+ const passwordFile = resolve(process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../.."), "config/.neo4j-password");
12
+ try {
13
+ return readFileSync(passwordFile, "utf-8").trim();
14
+ }
15
+ catch {
16
+ throw new Error(`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`);
17
+ }
18
+ }
19
+ export function getDriver() {
20
+ if (!driver) {
21
+ const uri = process.env.NEO4J_URI;
22
+ if (!uri)
23
+ throw new Error("[buyers] NEO4J_URI unset — refusing to default to bolt://localhost:7687");
24
+ const user = process.env.NEO4J_USER ?? "neo4j";
25
+ const password = readPassword();
26
+ console.error(`[buyers] resolved neo4j_uri=${uri}`);
27
+ driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
28
+ }
29
+ return driver;
30
+ }
31
+ export function getSession() {
32
+ return getDriver().session();
33
+ }
34
+ export async function closeDriver() {
35
+ if (driver) {
36
+ await driver.close();
37
+ driver = null;
38
+ }
39
+ }
40
+ //# sourceMappingURL=neo4j.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neo4j.js","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,uEAAuE;AACvE,sDAAsD;AAEtD,OAAO,KAA0B,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,SAAS,YAAY;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAClE,MAAM,YAAY,GAAG,OAAO,CAC1B,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,EACxE,wBAAwB,CACzB,CAAC;IACF,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,YAAY,gCAAgC,CAAC,CAAC;IACzG,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;QACrG,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;QAC/C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;QACpD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface BackfillParams {
2
+ accountId: string;
3
+ logPath: string;
4
+ sinceIso?: string;
5
+ untilIso?: string;
6
+ batchSize?: number;
7
+ }
8
+ export interface BackfillResult {
9
+ windowStart: string | null;
10
+ windowEnd: string | null;
11
+ recommendedParsed: number;
12
+ recommendedWritten: number;
13
+ recommendedSkippedDuplicate: number;
14
+ clickedParsed: number;
15
+ clickedWritten: number;
16
+ unparseable: number;
17
+ parityCheck: "ok" | "mismatch";
18
+ parityDelta: {
19
+ recommended: number;
20
+ clicked: number;
21
+ };
22
+ }
23
+ export declare function visitorBackfillFromLogs(p: BackfillParams): Promise<BackfillResult>;
24
+ //# sourceMappingURL=visitor-backfill-from-logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-backfill-from-logs.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,IAAI,GAAG,UAAU,CAAC;IAC/B,WAAW,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD;AA2BD,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAmHxF"}
@@ -0,0 +1,156 @@
1
+ // visitor-backfill-from-logs — one-shot importer.
2
+ //
3
+ // Parses [property-recommended] and [property-card-click] log lines from
4
+ // the platform log and writes them into the visitor graph:
5
+ //
6
+ // [property-recommended] sessionKey=<sk> listingSlug=<slug> ts=<iso>
7
+ // -> MERGE (Session {sessionKey})
8
+ // CREATE (Recommendation {sessionKey, listingSlug, recommendedAt})
9
+ // MERGE (Recommendation)-[:FOR_SESSION]->(Session)
10
+ //
11
+ // [property-card-click] sessionKey=<sk> listingSlug=<slug> ts=<iso>
12
+ // -> MATCH/MERGE Session, CREATE Click {label:'card', listingSlug}
13
+ // MERGE Session-[:HAS_EVENT]->Click
14
+ //
15
+ // Parity check after backfill: graph counts vs log-scan counts for the
16
+ // same window. parityCheck is 'ok' iff both deltas are zero, otherwise
17
+ // 'mismatch' with the delta surfaced for diagnosis.
18
+ //
19
+ // Idempotency: re-running over the same log window does not duplicate
20
+ // Recommendation nodes (MERGE on the exact sessionKey + slug + ts tuple).
21
+ // Click nodes are immutable event records by design; if you re-run on a
22
+ // window already processed you get duplicates. Operator runs once per
23
+ // cutover.
24
+ import { readFileSync } from "node:fs";
25
+ import { getSession } from "../lib/neo4j.js";
26
+ const REC_RE = /\[property-recommended\]\s+sessionKey=(\S+)\s+listingSlug=(\S+)\s+ts=(\S+)/;
27
+ const CLICK_RE = /\[property-card-click\]\s+sessionKey=(\S+)\s+listingSlug=(\S+)\s+ts=(\S+)/;
28
+ const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
29
+ function parseLine(line) {
30
+ let m = REC_RE.exec(line);
31
+ if (m)
32
+ return { kind: "rec", sessionKey: m[1], listingSlug: m[2], ts: m[3] };
33
+ m = CLICK_RE.exec(line);
34
+ if (m)
35
+ return { kind: "click", sessionKey: m[1], listingSlug: m[2], ts: m[3] };
36
+ return null;
37
+ }
38
+ function inWindow(iso, sinceMs, untilMs) {
39
+ if (!ISO_RE.test(iso))
40
+ return false;
41
+ const t = Date.parse(iso);
42
+ if (Number.isNaN(t))
43
+ return false;
44
+ if (sinceMs !== null && t < sinceMs)
45
+ return false;
46
+ if (untilMs !== null && t >= untilMs)
47
+ return false;
48
+ return true;
49
+ }
50
+ export async function visitorBackfillFromLogs(p) {
51
+ const sinceMs = p.sinceIso ? Date.parse(p.sinceIso) : null;
52
+ const untilMs = p.untilIso ? Date.parse(p.untilIso) : null;
53
+ const batchSize = Math.max(10, Math.min(p.batchSize ?? 200, 2000));
54
+ let body;
55
+ try {
56
+ body = readFileSync(p.logPath, "utf-8");
57
+ }
58
+ catch (err) {
59
+ throw new Error(`[visitor-backfill] log read failed path=${p.logPath} err=${err.message}`);
60
+ }
61
+ const rec = [];
62
+ const click = [];
63
+ let unparseable = 0;
64
+ let windowStart = null;
65
+ let windowEnd = null;
66
+ for (const raw of body.split("\n")) {
67
+ if (!raw || (!raw.includes("[property-recommended]") && !raw.includes("[property-card-click]")))
68
+ continue;
69
+ const parsed = parseLine(raw);
70
+ if (!parsed) {
71
+ unparseable += 1;
72
+ continue;
73
+ }
74
+ if (!inWindow(parsed.ts, sinceMs, untilMs))
75
+ continue;
76
+ if (parsed.kind === "rec")
77
+ rec.push(parsed);
78
+ else
79
+ click.push(parsed);
80
+ if (windowStart === null || parsed.ts < windowStart)
81
+ windowStart = parsed.ts;
82
+ if (windowEnd === null || parsed.ts > windowEnd)
83
+ windowEnd = parsed.ts;
84
+ }
85
+ const session = getSession();
86
+ let recommendedWritten = 0;
87
+ let recommendedSkippedDuplicate = 0;
88
+ let clickedWritten = 0;
89
+ console.error(`[visitor-backfill] start accountId=${p.accountId} window=${windowStart}..${windowEnd} rec=${rec.length} click=${click.length}`);
90
+ try {
91
+ for (let i = 0; i < rec.length; i += batchSize) {
92
+ const slice = rec.slice(i, i + batchSize);
93
+ const result = await session.run(`UNWIND $rows AS row
94
+ MERGE (s:Session {accountId: $accountId, sessionId: row.sessionKey})
95
+ ON CREATE SET s.startedAt = datetime(row.ts), s.lastSeenAt = datetime(row.ts), s.backfilled = true
96
+ ON MATCH SET s.lastSeenAt = CASE WHEN s.lastSeenAt > datetime(row.ts) THEN s.lastSeenAt ELSE datetime(row.ts) END
97
+ MERGE (r:Recommendation {accountId: $accountId, sessionKey: row.sessionKey, listingSlug: row.listingSlug, recommendedAt: datetime(row.ts)})
98
+ ON CREATE SET r.createdAt = datetime(), r.created = true
99
+ ON MATCH SET r.created = false
100
+ MERGE (r)-[:FOR_SESSION]->(s)
101
+ RETURN sum(CASE WHEN r.created THEN 1 ELSE 0 END) AS written,
102
+ sum(CASE WHEN r.created THEN 0 ELSE 1 END) AS skipped`, { accountId: p.accountId, rows: slice });
103
+ const row = result.records[0];
104
+ recommendedWritten += Number(row?.get("written") ?? 0);
105
+ recommendedSkippedDuplicate += Number(row?.get("skipped") ?? 0);
106
+ }
107
+ for (let i = 0; i < click.length; i += batchSize) {
108
+ const slice = click.slice(i, i + batchSize);
109
+ const result = await session.run(`UNWIND $rows AS row
110
+ MERGE (s:Session {accountId: $accountId, sessionId: row.sessionKey})
111
+ ON CREATE SET s.startedAt = datetime(row.ts), s.lastSeenAt = datetime(row.ts), s.backfilled = true
112
+ ON MATCH SET s.lastSeenAt = CASE WHEN s.lastSeenAt > datetime(row.ts) THEN s.lastSeenAt ELSE datetime(row.ts) END
113
+ CREATE (cl:Click {accountId: $accountId, label: 'card', href: '', listingSlug: row.listingSlug, pageViewId: '', occurredAt: datetime(row.ts), backfilled: true})
114
+ MERGE (s)-[:HAS_EVENT]->(cl)
115
+ RETURN count(cl) AS written`, { accountId: p.accountId, rows: slice });
116
+ const row = result.records[0];
117
+ clickedWritten += Number(row?.get("written") ?? 0);
118
+ }
119
+ const parityRes = await session.run(`MATCH (rec:Recommendation {accountId: $accountId})
120
+ WHERE rec.recommendedAt >= datetime($since) AND rec.recommendedAt < datetime($until)
121
+ WITH count(DISTINCT rec) AS recCount
122
+ OPTIONAL MATCH (cl:Click {accountId: $accountId, label: 'card'})
123
+ WHERE cl.occurredAt >= datetime($since) AND cl.occurredAt < datetime($until)
124
+ AND cl.backfilled = true
125
+ RETURN recCount, count(DISTINCT cl) AS clickCount`, {
126
+ accountId: p.accountId,
127
+ since: windowStart ?? new Date(0).toISOString(),
128
+ until: windowEnd ? new Date(Date.parse(windowEnd) + 1).toISOString() : new Date().toISOString(),
129
+ });
130
+ const head = parityRes.records[0];
131
+ const recGraph = Number(head?.get("recCount") ?? 0);
132
+ const clickGraph = Number(head?.get("clickCount") ?? 0);
133
+ const parityDelta = {
134
+ recommended: recGraph - rec.length,
135
+ clicked: clickGraph - click.length,
136
+ };
137
+ const parityCheck = parityDelta.recommended === 0 && parityDelta.clicked === 0 ? "ok" : "mismatch";
138
+ console.error(`[visitor-backfill] done parityCheck=${parityCheck} recParsed=${rec.length} recWritten=${recommendedWritten} clickParsed=${click.length} clickWritten=${clickedWritten} unparseable=${unparseable}`);
139
+ return {
140
+ windowStart,
141
+ windowEnd,
142
+ recommendedParsed: rec.length,
143
+ recommendedWritten,
144
+ recommendedSkippedDuplicate,
145
+ clickedParsed: click.length,
146
+ clickedWritten,
147
+ unparseable,
148
+ parityCheck,
149
+ parityDelta,
150
+ };
151
+ }
152
+ finally {
153
+ await session.close();
154
+ }
155
+ }
156
+ //# sourceMappingURL=visitor-backfill-from-logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-backfill-from-logs.js","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface EngagementParams {
2
+ accountId: string;
3
+ sinceDays?: number;
4
+ limit?: number;
5
+ }
6
+ export interface EngagementRow {
7
+ personId: string;
8
+ personName: string | null;
9
+ email: string | null;
10
+ telephone: string | null;
11
+ visitCount: number;
12
+ lastSeenAt: string;
13
+ daysSinceLast: number;
14
+ avgScrollDepth: number;
15
+ totalDwellMs: number;
16
+ score: number;
17
+ }
18
+ export declare function visitorEngagementScore(p: EngagementParams): Promise<{
19
+ persons: EngagementRow[];
20
+ }>;
21
+ //# sourceMappingURL=visitor-engagement-score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-engagement-score.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-engagement-score.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAsDvG"}
@@ -0,0 +1,65 @@
1
+ // visitor-engagement-score — engagement-ranked Person list.
2
+ //
3
+ // score = visit_count * recency_weight * depth_weight * dwell_weight
4
+ // visit_count = number of :Session in the window
5
+ // recency_weight = exp(-daysSinceLast / 14) (half-life 14d)
6
+ // depth_weight = avg(maxScrollDepth / 100)
7
+ // dwell_weight = log1p(totalDwellMs / 60000) (per minute)
8
+ //
9
+ // Score is unitless; the nurture-list skill consumes it for ranking, not
10
+ // absolute meaning. Returns top N most engaged Persons within sinceDays.
11
+ import { getSession } from "../lib/neo4j.js";
12
+ export async function visitorEngagementScore(p) {
13
+ const since = p.sinceDays ?? 30;
14
+ const limit = Math.max(1, Math.min(p.limit ?? 50, 200));
15
+ const session = getSession();
16
+ try {
17
+ const result = await session.run(`MATCH (per:Person {accountId: $accountId})-[:VISITED]->(s:Session)
18
+ WHERE s.startedAt >= datetime() - duration({days: $since})
19
+ OPTIONAL MATCH (s)-[:HAS_EVENT]->(sm:ScrollMilestone)
20
+ OPTIONAL MATCH (s)-[:HAS_EVENT]->(pv:PageView)
21
+ WITH per, s,
22
+ coalesce(avg(sm.maxDepth), 0) AS avgDepth,
23
+ coalesce(sum(pv.dwellMs), 0) AS dwellMs
24
+ WITH per, count(s) AS visits, max(s.lastSeenAt) AS lastSeenAt,
25
+ avg(avgDepth) AS avgDepth, sum(dwellMs) AS totalDwellMs
26
+ WITH per, visits, lastSeenAt, avgDepth, totalDwellMs,
27
+ duration.between(lastSeenAt, datetime()).days AS daysSinceLast
28
+ WITH per, visits, lastSeenAt, avgDepth, totalDwellMs, daysSinceLast,
29
+ visits
30
+ * exp(-daysSinceLast / 14.0)
31
+ * (avgDepth / 100.0 + 0.1)
32
+ * log(1.0 + totalDwellMs / 60000.0)
33
+ AS score
34
+ RETURN elementId(per) AS personId,
35
+ per.name AS personName,
36
+ per.email AS email,
37
+ per.telephone AS telephone,
38
+ visits AS visitCount,
39
+ toString(lastSeenAt) AS lastSeenAt,
40
+ daysSinceLast,
41
+ avgDepth AS avgScrollDepth,
42
+ totalDwellMs,
43
+ score
44
+ ORDER BY score DESC
45
+ LIMIT $limit`, { accountId: p.accountId, since, limit });
46
+ return {
47
+ persons: result.records.map((r) => ({
48
+ personId: r.get("personId"),
49
+ personName: r.get("personName"),
50
+ email: r.get("email"),
51
+ telephone: r.get("telephone"),
52
+ visitCount: Number(r.get("visitCount") ?? 0),
53
+ lastSeenAt: r.get("lastSeenAt"),
54
+ daysSinceLast: Number(r.get("daysSinceLast") ?? 0),
55
+ avgScrollDepth: Number(r.get("avgScrollDepth") ?? 0),
56
+ totalDwellMs: Number(r.get("totalDwellMs") ?? 0),
57
+ score: Number(r.get("score") ?? 0),
58
+ })),
59
+ };
60
+ }
61
+ finally {
62
+ await session.close();
63
+ }
64
+ }
65
+ //# sourceMappingURL=visitor-engagement-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-engagement-score.js","sourceRoot":"","sources":["../../src/tools/visitor-engagement-score.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,qEAAqE;AACrE,sDAAsD;AACtD,+DAA+D;AAC/D,+CAA+C;AAC/C,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAqB7C,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,CAAmB;IAC9D,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA4Bc,EACd,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CACzC,CAAC;QACF,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAClC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;gBAC3B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;gBAC/B,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;gBACrB,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;gBAC7B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;gBAC5C,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;gBAC/B,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;gBAClD,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;gBACpD,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;gBAChD,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aACnC,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface IngestParams {
2
+ accountId: string;
3
+ type: "pageview" | "click" | "scroll" | "dwell";
4
+ sessionId: string;
5
+ visitorId: string;
6
+ /** optional Person elementId — overrides identity resolution */
7
+ personId?: string | null;
8
+ url: string;
9
+ path?: string;
10
+ title?: string;
11
+ pageViewId?: string;
12
+ site?: string;
13
+ referrer?: string;
14
+ label?: string;
15
+ href?: string;
16
+ depth?: number;
17
+ dwellMs?: number;
18
+ occurredAt?: string;
19
+ }
20
+ export interface IngestResult {
21
+ ok: boolean;
22
+ type: string;
23
+ sessionId: string;
24
+ }
25
+ export declare function visitorEventIngest(p: IngestParams): Promise<IngestResult>;
26
+ //# sourceMappingURL=visitor-event-ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-event-ingest.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-event-ingest.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAqF/E"}
@@ -0,0 +1,78 @@
1
+ // visitor-event-ingest — admin-side companion to POST /v/event for test
2
+ // harness work and manual replay. Takes a single event payload identical
3
+ // in shape to what the pixel sends, writes it into the graph against the
4
+ // caller's accountId. Not exposed on the public agent surface.
5
+ import { getSession } from "../lib/neo4j.js";
6
+ export async function visitorEventIngest(p) {
7
+ const session = getSession();
8
+ const occurredAt = p.occurredAt ?? new Date().toISOString();
9
+ try {
10
+ if (p.type === "pageview") {
11
+ await session.run(`MERGE (s:Session {accountId: $accountId, sessionId: $sid})
12
+ ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
13
+ ON MATCH SET s.lastSeenAt = datetime($occurredAt)
14
+ MERGE (pg:Page {accountId: $accountId, url: $url})
15
+ ON CREATE SET pg.firstSeenAt = datetime($occurredAt), pg.title = $title
16
+ CREATE (pv:PageView {
17
+ accountId: $accountId, pageViewId: $pvId, occurredAt: datetime($occurredAt),
18
+ referrer: $referrer, path: $path
19
+ })
20
+ MERGE (s)-[:HAS_EVENT]->(pv)
21
+ MERGE (pv)-[:OF_PAGE]->(pg)`, {
22
+ accountId: p.accountId,
23
+ sid: p.sessionId,
24
+ url: p.url,
25
+ title: p.title ?? "",
26
+ pvId: p.pageViewId ?? `${p.sessionId}_${Date.parse(occurredAt)}`,
27
+ path: p.path ?? "",
28
+ referrer: p.referrer ?? "",
29
+ occurredAt,
30
+ });
31
+ }
32
+ else if (p.type === "click") {
33
+ await session.run(`MERGE (s:Session {accountId: $accountId, sessionId: $sid})
34
+ ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
35
+ ON MATCH SET s.lastSeenAt = datetime($occurredAt)
36
+ CREATE (cl:Click {accountId: $accountId, label: $label, href: $href, pageViewId: $pvId, occurredAt: datetime($occurredAt)})
37
+ MERGE (s)-[:HAS_EVENT]->(cl)`, {
38
+ accountId: p.accountId,
39
+ sid: p.sessionId,
40
+ label: (p.label ?? "unknown").slice(0, 64),
41
+ href: (p.href ?? "").slice(0, 2048),
42
+ pvId: p.pageViewId ?? "",
43
+ occurredAt,
44
+ });
45
+ }
46
+ else if (p.type === "scroll") {
47
+ await session.run(`MERGE (s:Session {accountId: $accountId, sessionId: $sid})
48
+ MERGE (sm:ScrollMilestone {accountId: $accountId, sessionId: $sid, pageViewId: $pvId})
49
+ ON CREATE SET sm.maxDepth = $depth, sm.firstAt = datetime($occurredAt), sm.lastAt = datetime($occurredAt)
50
+ ON MATCH SET sm.maxDepth = CASE WHEN sm.maxDepth >= $depth THEN sm.maxDepth ELSE $depth END,
51
+ sm.lastAt = datetime($occurredAt)
52
+ MERGE (s)-[:HAS_EVENT]->(sm)`, { accountId: p.accountId, sid: p.sessionId, pvId: p.pageViewId ?? "", depth: p.depth ?? 0, occurredAt });
53
+ }
54
+ else if (p.type === "dwell") {
55
+ await session.run(`MATCH (pv:PageView {accountId: $accountId, pageViewId: $pvId})
56
+ SET pv.dwellMs = coalesce(pv.dwellMs, 0) + $ms,
57
+ pv.dwellLastAt = datetime($occurredAt)`, { accountId: p.accountId, pvId: p.pageViewId ?? "", ms: p.dwellMs ?? 0, occurredAt });
58
+ }
59
+ if (p.personId) {
60
+ await session.run(`MATCH (per:Person {accountId: $accountId}) WHERE elementId(per) = $personId
61
+ MATCH (s:Session {accountId: $accountId, sessionId: $sid})
62
+ MERGE (per)-[:VISITED {accountId: $accountId}]->(s)`, { accountId: p.accountId, personId: p.personId, sid: p.sessionId });
63
+ }
64
+ else {
65
+ await session.run(`MERGE (av:AnonVisitor {accountId: $accountId, visitorId: $vid})
66
+ ON CREATE SET av.firstSeenAt = datetime($occurredAt), av.lastSeenAt = datetime($occurredAt)
67
+ ON MATCH SET av.lastSeenAt = datetime($occurredAt)
68
+ WITH av
69
+ MATCH (s:Session {accountId: $accountId, sessionId: $sid})
70
+ MERGE (av)-[:VISITED {accountId: $accountId}]->(s)`, { accountId: p.accountId, vid: p.visitorId, sid: p.sessionId, occurredAt });
71
+ }
72
+ return { ok: true, type: p.type, sessionId: p.sessionId };
73
+ }
74
+ finally {
75
+ await session.close();
76
+ }
77
+ }
78
+ //# sourceMappingURL=visitor-event-ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-event-ingest.js","sourceRoot":"","sources":["../../src/tools/visitor-event-ingest.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,yEAAyE;AACzE,yEAAyE;AACzE,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4B7C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,CAAe;IACtD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5D,IAAI,CAAC;QACH,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC1B,MAAM,OAAO,CAAC,GAAG,CACf;;;;;;;;;;qCAU6B,EAC7B;gBACE,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,GAAG,EAAE,CAAC,CAAC,SAAS;gBAChB,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;gBACpB,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE;gBAChE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;gBAC1B,UAAU;aACX,CACF,CAAC;QACJ,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9B,MAAM,OAAO,CAAC,GAAG,CACf;;;;sCAI8B,EAC9B;gBACE,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,GAAG,EAAE,CAAC,CAAC,SAAS;gBAChB,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;gBAC1C,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;gBACnC,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;gBACxB,UAAU;aACX,CACF,CAAC;QACJ,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,OAAO,CAAC,GAAG,CACf;;;;;sCAK8B,EAC9B,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,UAAU,EAAE,CACxG,CAAC;QACJ,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9B,MAAM,OAAO,CAAC,GAAG,CACf;;oDAE4C,EAC5C,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,EAAE,UAAU,EAAE,CACrF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,OAAO,CAAC,GAAG,CACf;;6DAEqD,EACrD,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,CACnE,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,CAAC,GAAG,CACf;;;;;4DAKoD,EACpD,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,CAC3E,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;IAC5D,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface RecentByPageParams {
2
+ accountId: string;
3
+ /** Either listingSlug or url; one is required. */
4
+ listingSlug?: string;
5
+ url?: string;
6
+ sinceDays?: number;
7
+ limit?: number;
8
+ }
9
+ export interface VisitorSummary {
10
+ personId: string | null;
11
+ personName: string | null;
12
+ anonVisitorId: string | null;
13
+ pageviewCount: number;
14
+ lastSeenAt: string;
15
+ }
16
+ export declare function visitorRecentByPage(p: RecentByPageParams): Promise<{
17
+ visitors: VisitorSummary[];
18
+ }>;
19
+ //# sourceMappingURL=visitor-recent-by-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-recent-by-page.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-recent-by-page.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,CAAC,CAsCxG"}