@rubytech/create-maxy-code 0.1.566 → 0.1.567

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 (105) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +9 -0
  3. package/payload/platform/package-lock.json +66 -1
  4. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  5. package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
  6. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +48 -1
  7. package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +5 -1
  8. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  9. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +47 -0
  10. package/payload/platform/plugins/inventory/.claude-plugin/plugin.json +21 -0
  11. package/payload/platform/plugins/inventory/PLUGIN.md +75 -0
  12. package/payload/platform/plugins/inventory/lib/mcp-spawn-tee/index.js +204 -0
  13. package/payload/platform/plugins/inventory/lib/mcp-spawn-tee/package.json +3 -0
  14. package/payload/platform/plugins/inventory/mcp/dist/index.d.ts +2 -0
  15. package/payload/platform/plugins/inventory/mcp/dist/index.d.ts.map +1 -0
  16. package/payload/platform/plugins/inventory/mcp/dist/index.js +241 -0
  17. package/payload/platform/plugins/inventory/mcp/dist/index.js.map +1 -0
  18. package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.d.ts +20 -0
  19. package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.d.ts.map +1 -0
  20. package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.js +29 -0
  21. package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.js.map +1 -0
  22. package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.d.ts +5 -0
  23. package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.d.ts.map +1 -0
  24. package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.js +40 -0
  25. package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.js.map +1 -0
  26. package/payload/platform/plugins/inventory/mcp/dist/lib/read.d.ts +28 -0
  27. package/payload/platform/plugins/inventory/mcp/dist/lib/read.d.ts.map +1 -0
  28. package/payload/platform/plugins/inventory/mcp/dist/lib/read.js +91 -0
  29. package/payload/platform/plugins/inventory/mcp/dist/lib/read.js.map +1 -0
  30. package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.d.ts +56 -0
  31. package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.d.ts.map +1 -0
  32. package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.js +107 -0
  33. package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.js.map +1 -0
  34. package/payload/platform/plugins/inventory/mcp/dist/lib/types.d.ts +98 -0
  35. package/payload/platform/plugins/inventory/mcp/dist/lib/types.d.ts.map +1 -0
  36. package/payload/platform/plugins/inventory/mcp/dist/lib/types.js +7 -0
  37. package/payload/platform/plugins/inventory/mcp/dist/lib/types.js.map +1 -0
  38. package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.d.ts +12 -0
  39. package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.d.ts.map +1 -0
  40. package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.js +49 -0
  41. package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.js.map +1 -0
  42. package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.d.ts +20 -0
  43. package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.d.ts.map +1 -0
  44. package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.js +53 -0
  45. package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.js.map +1 -0
  46. package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.d.ts +23 -0
  47. package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.d.ts.map +1 -0
  48. package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.js +18 -0
  49. package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.js.map +1 -0
  50. package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.d.ts +25 -0
  51. package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.d.ts.map +1 -0
  52. package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.js +37 -0
  53. package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.js.map +1 -0
  54. package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.d.ts +25 -0
  55. package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.d.ts.map +1 -0
  56. package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.js +43 -0
  57. package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.js.map +1 -0
  58. package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.d.ts +29 -0
  59. package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.d.ts.map +1 -0
  60. package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.js +95 -0
  61. package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.js.map +1 -0
  62. package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.d.ts +21 -0
  63. package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.d.ts.map +1 -0
  64. package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.js +46 -0
  65. package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.js.map +1 -0
  66. package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.d.ts +24 -0
  67. package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.d.ts.map +1 -0
  68. package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.js +29 -0
  69. package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.js.map +1 -0
  70. package/payload/platform/plugins/inventory/mcp/package.json +21 -0
  71. package/payload/platform/plugins/memory/references/schema-retail.md +20 -0
  72. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +8 -0
  74. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  76. package/payload/platform/services/claude-session-manager/dist/http-server.js +66 -5
  77. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  78. package/payload/platform/services/claude-session-manager/dist/public-spawn-model.d.ts +28 -0
  79. package/payload/platform/services/claude-session-manager/dist/public-spawn-model.d.ts.map +1 -0
  80. package/payload/platform/services/claude-session-manager/dist/public-spawn-model.js +58 -0
  81. package/payload/platform/services/claude-session-manager/dist/public-spawn-model.js.map +1 -0
  82. package/payload/platform/services/telegram-channel/dist/server.d.ts +5 -0
  83. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  84. package/payload/platform/services/telegram-channel/dist/server.js +22 -2
  85. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  86. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +34 -2
  87. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -1
  88. package/payload/platform/services/telegram-channel/dist/turn-follow.js +150 -14
  89. package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -1
  90. package/payload/platform/services/webchat-channel/dist/server.d.ts +5 -0
  91. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  92. package/payload/platform/services/webchat-channel/dist/server.js +22 -2
  93. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  94. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +34 -2
  95. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
  96. package/payload/platform/services/webchat-channel/dist/turn-follow.js +150 -14
  97. package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
  98. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +10 -2
  99. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  100. package/payload/platform/services/whatsapp-channel/dist/server.js +45 -6
  101. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  102. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +34 -2
  103. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -1
  104. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +153 -15
  105. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -1
@@ -0,0 +1,241 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { eagerTool } from "../../../../lib/mcp-eager/dist/index.js";
4
+ import { writeNodeWithEdges } from "../../../../lib/graph-write/dist/index.js";
5
+ import { z } from "zod";
6
+ import { closeDriver, getSession } from "./lib/neo4j.js";
7
+ import { readDemandHistory, readPlanCandidates, resolveLocation, resolveProductBySku, setSupplierTerms, } from "./lib/read.js";
8
+ import { replenishPlan, } from "./tools/replenish-plan.js";
9
+ import { purchaseOrderRecord, } from "./tools/purchase-order-record.js";
10
+ import { forecast } from "./tools/forecast.js";
11
+ import { policySet } from "./tools/policy-set.js";
12
+ import { supplierTermsSet, } from "./tools/supplier-terms-set.js";
13
+ import { stockRecord } from "./tools/stock-record.js";
14
+ import { demandRecord } from "./tools/demand-record.js";
15
+ import { countWriteRejects24h } from "./lib/write-rejects.js";
16
+ const server = new McpServer({ name: "maxy-inventory", version: "0.1.0" });
17
+ // Plugin-system boot tolerance. Module init must never throw: plugin-system
18
+ // spawn enumerates tools/list with empty env, and a boot throw empties the
19
+ // registry for every plugin. Refusal is per call, which preserves account
20
+ // isolation without breaking enumeration.
21
+ const accountId = process.env.ACCOUNT_ID ?? null;
22
+ const logDir = process.env.LOG_DIR;
23
+ const agentSlug = process.env.AGENT_SLUG;
24
+ const sessionId = process.env.SESSION_ID;
25
+ process.stderr.write(`[inventory] boot accountId=${accountId ?? "-"} sessionId=${sessionId || "-"} agentSlug=${agentSlug || "-"}\n`);
26
+ function refuseNoAccount(toolName) {
27
+ process.stderr.write(`[inventory] tool=${toolName} refuse reason=no-account-context\n`);
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text",
32
+ text: `${toolName} cannot execute: this MCP instance was launched without ACCOUNT_ID (plugin-system metadata-only mode). Per-account writes require ACCOUNT_ID at spawn.`,
33
+ },
34
+ ],
35
+ isError: true,
36
+ };
37
+ }
38
+ // `agent` stays undefined when AGENT_SLUG is unset rather than defaulting to
39
+ // "". graph-write resolves `agent ?? source ?? "unknown"` and an empty string
40
+ // is not nullish, so a default prints a blank agent= on every reject.
41
+ const createdBy = (tool) => ({
42
+ agent: agentSlug,
43
+ session: sessionId,
44
+ tool,
45
+ source: "inventory",
46
+ });
47
+ // No cast. Casting writeNodeWithEdges is what let a dead write path ship in
48
+ // the ledger plugin.
49
+ const write = writeNodeWithEdges;
50
+ async function withSession(fn) {
51
+ const session = getSession();
52
+ try {
53
+ return await fn(session);
54
+ }
55
+ finally {
56
+ await session.close();
57
+ }
58
+ }
59
+ const stockRowShape = z.object({
60
+ sku: z.string().min(1).describe("Product SKU as it appears on the :Product node"),
61
+ location: z
62
+ .string()
63
+ .min(1)
64
+ .describe("Element id of the existing :StoreLocation. Look it up first; this tool never creates locations"),
65
+ quantityOnHand: z.number().describe("Units physically in stock at this location"),
66
+ quantityOnOrder: z
67
+ .number()
68
+ .describe("Units already on an open purchase order for this location"),
69
+ asOf: z.string().describe("ISO timestamp the count was taken, e.g. 2026-08-05T09:00:00Z"),
70
+ });
71
+ eagerTool(server, "inventory-stock-record", "Record measured stock for one or more products at a location. Parse the operator's stock export into rows first. Rows whose SKU or location cannot be resolved are rejected and named back to you; they are never written with a guessed target.", {
72
+ rows: z.array(stockRowShape).min(1).describe("One entry per product per location"),
73
+ }, async (args) => {
74
+ if (accountId === null)
75
+ return refuseNoAccount("inventory-stock-record");
76
+ return withSession((session) => stockRecord({
77
+ session,
78
+ write,
79
+ resolveProduct: resolveProductBySku,
80
+ resolveLocation,
81
+ accountId,
82
+ createdBy: createdBy("inventory-stock-record"),
83
+ ...args,
84
+ }));
85
+ });
86
+ const demandRowShape = z.object({
87
+ sku: z.string().min(1).describe("Product SKU as it appears on the :Product node"),
88
+ location: z.string().min(1).describe("Element id of the existing :StoreLocation"),
89
+ periodStart: z.string().describe("ISO date the period opens, e.g. 2026-07-01"),
90
+ periodEnd: z.string().describe("ISO date the period closes, must be after periodStart"),
91
+ quantitySold: z
92
+ .number()
93
+ .min(0)
94
+ .describe("Units sold in the period. Zero is meaningful and is stored; omit the row only when there is genuinely no data"),
95
+ });
96
+ eagerTool(server, "inventory-demand-record", "Record observed sales per product per location per period. Parse the operator's sales export into equal-length periods first, one row per product per location per period. A period with zero sales should be recorded as zero, not omitted, because the forecast reads a missing period differently from a quiet one.", {
97
+ rows: z
98
+ .array(demandRowShape)
99
+ .min(1)
100
+ .describe("One entry per product per location per period"),
101
+ }, async (args) => {
102
+ if (accountId === null)
103
+ return refuseNoAccount("inventory-demand-record");
104
+ return withSession((session) => demandRecord({
105
+ session,
106
+ write,
107
+ resolveProduct: resolveProductBySku,
108
+ resolveLocation,
109
+ accountId,
110
+ createdBy: createdBy("inventory-demand-record"),
111
+ ...args,
112
+ }));
113
+ });
114
+ eagerTool(server, "inventory-policy-set", "Set how hard to try to stay in stock for one product at one location, and how often the operator reviews reordering. Ask the operator for both rather than assuming: a 95% service level and a weekly review are common but neither is a safe default.", {
115
+ sku: z.string().min(1).describe("Product SKU"),
116
+ location: z.string().min(1).describe("Element id of the :StoreLocation"),
117
+ serviceLevel: z
118
+ .number()
119
+ .gt(0)
120
+ .lt(1)
121
+ .describe("Target in-stock probability, e.g. 0.95. Strictly between 0 and 1"),
122
+ reviewPeriodDays: z
123
+ .number()
124
+ .positive()
125
+ .describe("Days between reorder reviews, e.g. 7 for a weekly order"),
126
+ }, async (args) => {
127
+ if (accountId === null)
128
+ return refuseNoAccount("inventory-policy-set");
129
+ return withSession((session) => policySet({
130
+ session,
131
+ write,
132
+ resolveProduct: resolveProductBySku,
133
+ resolveLocation,
134
+ accountId,
135
+ createdBy: createdBy("inventory-policy-set"),
136
+ ...args,
137
+ }));
138
+ });
139
+ eagerTool(server, "inventory-supplier-terms-set", "Set lead time, minimum order, pack multiple and unit cost for a product from a given supplier. The product must already be linked to the supplier; this tool never creates that link.", {
140
+ sku: z.string().min(1).describe("Product SKU"),
141
+ supplierNodeId: z
142
+ .string()
143
+ .min(1)
144
+ .describe("Element id of the existing :Organization the product is SUPPLIED_BY"),
145
+ leadTimeDays: z
146
+ .number()
147
+ .min(0)
148
+ .describe("Whole days from placing the order to it arriving"),
149
+ minimumOrderQuantity: z
150
+ .number()
151
+ .min(0)
152
+ .describe("Smallest order the supplier accepts. Use 0 when there is no minimum"),
153
+ orderMultiple: z
154
+ .number()
155
+ .positive()
156
+ .describe("Pack size orders must be a multiple of. Use 1 when they ship singles"),
157
+ unitCost: z.number().min(0).describe("Cost per unit in the account's currency"),
158
+ }, async (args) => {
159
+ if (accountId === null)
160
+ return refuseNoAccount("inventory-supplier-terms-set");
161
+ return withSession((session) => supplierTermsSet({
162
+ session,
163
+ resolveProduct: resolveProductBySku,
164
+ setTerms: setSupplierTerms,
165
+ accountId,
166
+ ...args,
167
+ }));
168
+ });
169
+ eagerTool(server, "inventory-forecast", "Expected demand per period for one product at one location, worked out from recorded sales history. Nothing is stored; the figure is derived on every call. An empty history is reported as an error rather than as zero demand, because those mean opposite things.", {
170
+ sku: z.string().min(1).describe("Product SKU"),
171
+ location: z.string().min(1).describe("Element id of the :StoreLocation"),
172
+ window: z
173
+ .number()
174
+ .int()
175
+ .positive()
176
+ .describe("How many trailing periods to average, e.g. 8 for eight weeks of weekly data"),
177
+ }, async (args) => {
178
+ if (accountId === null)
179
+ return refuseNoAccount("inventory-forecast");
180
+ return withSession((session) => forecast({
181
+ session,
182
+ resolveProduct: resolveProductBySku,
183
+ readHistory: readDemandHistory,
184
+ accountId,
185
+ ...args,
186
+ }));
187
+ });
188
+ eagerTool(server, "inventory-replenish-plan", "What to reorder at one location, how much, and from whom. Returns the oldest and newest stock timestamps it read, so state the age of the data when you report the result. Lines that could not be worked out come back named with the missing input; do not fill those in yourself, ask the operator for the value and record it with the matching set tool.", {
189
+ location: z.string().min(1).describe("Element id of the :StoreLocation to plan for"),
190
+ window: z
191
+ .number()
192
+ .int()
193
+ .positive()
194
+ .describe("How many trailing demand periods to average, e.g. 8"),
195
+ periodDays: z
196
+ .number()
197
+ .positive()
198
+ .describe("Length of one demand period in days, e.g. 7 when the sales export is weekly"),
199
+ today: z.string().describe("ISO date to treat as the order date, e.g. 2026-08-05"),
200
+ }, async (args) => {
201
+ if (accountId === null)
202
+ return refuseNoAccount("inventory-replenish-plan");
203
+ return withSession((session) => replenishPlan({
204
+ session,
205
+ readCandidates: readPlanCandidates,
206
+ accountId,
207
+ recentWriteRejects: logDir ? countWriteRejects24h(logDir, Date.now()) : 0,
208
+ ...args,
209
+ }));
210
+ });
211
+ eagerTool(server, "inventory-purchase-order-record", "Record a purchase order the operator has confirmed. Confirm the lines and the supplier with the operator before calling this. It records the order in the graph and does not send anything to the supplier, so tell the operator they still need to place it.", {
212
+ supplierNodeId: z.string().min(1).describe("Element id of the supplier :Organization"),
213
+ orderDate: z.string().describe("ISO date the order is placed, e.g. 2026-08-05"),
214
+ expectedDelivery: z
215
+ .string()
216
+ .describe("ISO date the order should arrive, from the plan's expectedArrival"),
217
+ lines: z
218
+ .array(z.object({
219
+ sku: z.string().min(1).describe("Product SKU"),
220
+ quantity: z.number().positive().describe("Units ordered"),
221
+ }))
222
+ .min(1)
223
+ .describe("The confirmed order lines"),
224
+ }, async (args) => {
225
+ if (accountId === null)
226
+ return refuseNoAccount("inventory-purchase-order-record");
227
+ return withSession((session) => purchaseOrderRecord({
228
+ session,
229
+ write,
230
+ accountId,
231
+ createdBy: createdBy("inventory-purchase-order-record"),
232
+ ...args,
233
+ }));
234
+ });
235
+ const transport = new StdioServerTransport();
236
+ await server.connect(transport);
237
+ process.on("SIGTERM", async () => {
238
+ await closeDriver();
239
+ process.exit(0);
240
+ });
241
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Task 2448 — demand forecast.
3
+ *
4
+ * A trailing moving average, deliberately: it is the smallest method that is
5
+ * defensible and testable with plain numbers. It lags a trend and flattens a
6
+ * seasonal peak; replacing it is Task 2449, and because no forecast is ever
7
+ * stored, that replacement needs no migration.
8
+ */
9
+ /**
10
+ * Mean of the trailing `window` periods.
11
+ *
12
+ * A history shorter than the window averages what exists rather than padding
13
+ * with zeros: padding would read a new product's short history as a collapse
14
+ * in demand.
15
+ *
16
+ * @param quantities units sold per period, oldest first
17
+ * @param window how many trailing periods to average, at least 1
18
+ */
19
+ export declare function movingAverage(quantities: number[], window: number): number;
20
+ //# sourceMappingURL=forecast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forecast.d.ts","sourceRoot":"","sources":["../../src/lib/forecast.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAU1E"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Task 2448 — demand forecast.
3
+ *
4
+ * A trailing moving average, deliberately: it is the smallest method that is
5
+ * defensible and testable with plain numbers. It lags a trend and flattens a
6
+ * seasonal peak; replacing it is Task 2449, and because no forecast is ever
7
+ * stored, that replacement needs no migration.
8
+ */
9
+ /**
10
+ * Mean of the trailing `window` periods.
11
+ *
12
+ * A history shorter than the window averages what exists rather than padding
13
+ * with zeros: padding would read a new product's short history as a collapse
14
+ * in demand.
15
+ *
16
+ * @param quantities units sold per period, oldest first
17
+ * @param window how many trailing periods to average, at least 1
18
+ */
19
+ export function movingAverage(quantities, window) {
20
+ if (!Number.isFinite(window) || window < 1) {
21
+ throw new Error(`[inventory] movingAverage: window must be at least 1, got ${window}`);
22
+ }
23
+ if (quantities.length === 0)
24
+ return 0;
25
+ const slice = quantities.slice(-Math.floor(window));
26
+ const total = slice.reduce((sum, n) => sum + n, 0);
27
+ return total / slice.length;
28
+ }
29
+ //# sourceMappingURL=forecast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forecast.js","sourceRoot":"","sources":["../../src/lib/forecast.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,UAAoB,EAAE,MAAc;IAChE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,6DAA6D,MAAM,EAAE,CACtE,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;AAC9B,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":"AAAA,OAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAuBtD,wBAAgB,SAAS,IAAI,MAAM,CAgBlC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjD"}
@@ -0,0 +1,40 @@
1
+ import neo4j from "neo4j-driver";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ let driver = null;
5
+ function readPassword() {
6
+ if (process.env.NEO4J_PASSWORD)
7
+ return process.env.NEO4J_PASSWORD;
8
+ const passwordFile = resolve(process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../.."), "config/.neo4j-password");
9
+ try {
10
+ return readFileSync(passwordFile, "utf-8").trim();
11
+ }
12
+ catch {
13
+ throw new Error(`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`);
14
+ }
15
+ }
16
+ export function getDriver() {
17
+ if (!driver) {
18
+ // NEO4J_URI must be explicit. A silent default to :7687 was the vector for
19
+ // orphan data in one brand's database written by another brand's install.
20
+ const uri = process.env.NEO4J_URI;
21
+ if (!uri) {
22
+ throw new Error("[inventory] NEO4J_URI unset — refusing to default to bolt://localhost:7687");
23
+ }
24
+ const user = process.env.NEO4J_USER ?? "neo4j";
25
+ const password = readPassword();
26
+ console.error(`[inventory] 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,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;IAElE,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;IAEF,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yCAAyC,YAAY,gCAAgC,CACtF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;QAC/C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;QACvD,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,28 @@
1
+ import type { Session } from "neo4j-driver";
2
+ import type { PlanCandidate } from "./types.js";
3
+ /** Everything the plan needs for one location, in one query. OPTIONAL MATCH
4
+ * throughout rather than MATCH: a product missing its policy or supplier
5
+ * terms must still come back, as a skipped line naming what is missing. A
6
+ * plain MATCH would drop it and the plan would look complete while silently
7
+ * omitting exactly the products that need operator attention. */
8
+ export declare function readPlanCandidates(session: Session, accountId: string, location: string): Promise<PlanCandidate[]>;
9
+ /** Resolves a SKU to a :Product element id on this account. Null when absent
10
+ * or when the SKU is ambiguous — two products sharing a SKU is a data fault
11
+ * the operator must fix, not something to guess past. */
12
+ export declare function resolveProductBySku(session: Session, accountId: string, sku: string): Promise<string | null>;
13
+ /** Units sold per period for one product at one location, oldest first. */
14
+ export declare function readDemandHistory(session: Session, accountId: string, productNodeId: string, location: string): Promise<number[]>;
15
+ export interface SupplierTerms {
16
+ leadTimeDays: number;
17
+ minimumOrderQuantity: number;
18
+ orderMultiple: number;
19
+ unitCost: number;
20
+ }
21
+ /** Sets the commercial terms on an existing SUPPLIED_BY edge. Returns false
22
+ * when no such edge exists: the edge is the operator's assertion that this
23
+ * supplier supplies this product, and creating it here would let a typo
24
+ * invent a supply relationship. */
25
+ export declare function setSupplierTerms(session: Session, accountId: string, productNodeId: string, supplierNodeId: string, terms: SupplierTerms): Promise<boolean>;
26
+ /** Confirms a :StoreLocation element id exists on this account. */
27
+ export declare function resolveLocation(session: Session, accountId: string, nodeId: string): Promise<boolean>;
28
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/lib/read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD;;;;kEAIkE;AAClE,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,EAAE,CAAC,CAwC1B;AAED;;0DAE0D;AAC1D,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB;AAED,2EAA2E;AAC3E,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,EAAE,CAAC,CAWnB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;oCAGoC;AACpC,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,mEAAmE;AACnE,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CASlB"}
@@ -0,0 +1,91 @@
1
+ const num = (v) => typeof v?.toNumber === "function"
2
+ ? v.toNumber()
3
+ : Number(v);
4
+ const numOrNull = (v) => v === null || v === undefined ? null : num(v);
5
+ /** Everything the plan needs for one location, in one query. OPTIONAL MATCH
6
+ * throughout rather than MATCH: a product missing its policy or supplier
7
+ * terms must still come back, as a skipped line naming what is missing. A
8
+ * plain MATCH would drop it and the plan would look complete while silently
9
+ * omitting exactly the products that need operator attention. */
10
+ export async function readPlanCandidates(session, accountId, location) {
11
+ const res = await session.run(`MATCH (sp:StockPosition {accountId: $accountId, location: $location})
12
+ MATCH (p:Product {accountId: $accountId}) WHERE elementId(p) = sp.product
13
+ OPTIONAL MATCH (pol:ReplenishmentPolicy {accountId: $accountId, product: sp.product, location: $location})
14
+ OPTIONAL MATCH (p)-[terms:SUPPLIED_BY]->(o:Organization {accountId: $accountId})
15
+ OPTIONAL MATCH (d:DemandObservation {accountId: $accountId, product: sp.product, location: $location})
16
+ WITH sp, p, pol, terms, o, d ORDER BY d.periodStart ASC
17
+ WITH sp, p, pol, terms, o, collect(d.quantitySold) AS history
18
+ RETURN p.sku AS sku,
19
+ sp.product AS productNodeId,
20
+ sp.location AS location,
21
+ sp.quantityOnHand AS quantityOnHand,
22
+ sp.quantityOnOrder AS quantityOnOrder,
23
+ sp.asOf AS asOf,
24
+ history,
25
+ pol.serviceLevel AS serviceLevel,
26
+ pol.reviewPeriodDays AS reviewPeriodDays,
27
+ terms.leadTimeDays AS leadTimeDays,
28
+ terms.minimumOrderQuantity AS minimumOrderQuantity,
29
+ terms.orderMultiple AS orderMultiple,
30
+ CASE WHEN o IS NULL THEN null ELSE elementId(o) END AS supplierNodeId`, { accountId, location });
31
+ return res.records.map((r) => ({
32
+ sku: r.get("sku"),
33
+ productNodeId: r.get("productNodeId"),
34
+ location: r.get("location"),
35
+ quantityOnHand: num(r.get("quantityOnHand")),
36
+ quantityOnOrder: num(r.get("quantityOnOrder")),
37
+ asOf: r.get("asOf"),
38
+ history: r.get("history").map(num),
39
+ serviceLevel: numOrNull(r.get("serviceLevel")),
40
+ reviewPeriodDays: numOrNull(r.get("reviewPeriodDays")),
41
+ leadTimeDays: numOrNull(r.get("leadTimeDays")),
42
+ minimumOrderQuantity: numOrNull(r.get("minimumOrderQuantity")),
43
+ orderMultiple: numOrNull(r.get("orderMultiple")),
44
+ supplierNodeId: r.get("supplierNodeId") ?? null,
45
+ }));
46
+ }
47
+ /** Resolves a SKU to a :Product element id on this account. Null when absent
48
+ * or when the SKU is ambiguous — two products sharing a SKU is a data fault
49
+ * the operator must fix, not something to guess past. */
50
+ export async function resolveProductBySku(session, accountId, sku) {
51
+ const res = await session.run(`MATCH (p:Product {accountId: $accountId, sku: $sku})
52
+ RETURN elementId(p) AS id
53
+ LIMIT 2`, { accountId, sku });
54
+ if (res.records.length !== 1)
55
+ return null;
56
+ return res.records[0].get("id");
57
+ }
58
+ /** Units sold per period for one product at one location, oldest first. */
59
+ export async function readDemandHistory(session, accountId, productNodeId, location) {
60
+ const res = await session.run(`MATCH (d:DemandObservation {accountId: $accountId, product: $productNodeId, location: $location})
61
+ RETURN d.quantitySold AS q
62
+ ORDER BY d.periodStart ASC`, { accountId, productNodeId, location });
63
+ return res.records.map((r) => {
64
+ const q = r.get("q");
65
+ return typeof q?.toNumber === "function" ? q.toNumber() : Number(q);
66
+ });
67
+ }
68
+ /** Sets the commercial terms on an existing SUPPLIED_BY edge. Returns false
69
+ * when no such edge exists: the edge is the operator's assertion that this
70
+ * supplier supplies this product, and creating it here would let a typo
71
+ * invent a supply relationship. */
72
+ export async function setSupplierTerms(session, accountId, productNodeId, supplierNodeId, terms) {
73
+ const res = await session.run(`MATCH (p:Product {accountId: $accountId})-[r:SUPPLIED_BY]->(o:Organization {accountId: $accountId})
74
+ WHERE elementId(p) = $productNodeId AND elementId(o) = $supplierNodeId
75
+ SET r.leadTimeDays = $leadTimeDays,
76
+ r.minimumOrderQuantity = $minimumOrderQuantity,
77
+ r.orderMultiple = $orderMultiple,
78
+ r.unitCost = $unitCost
79
+ RETURN count(r) AS n`, { accountId, productNodeId, supplierNodeId, ...terms });
80
+ const n = res.records[0]?.get("n");
81
+ return (typeof n?.toNumber === "function" ? n.toNumber() : Number(n ?? 0)) > 0;
82
+ }
83
+ /** Confirms a :StoreLocation element id exists on this account. */
84
+ export async function resolveLocation(session, accountId, nodeId) {
85
+ const res = await session.run(`MATCH (l:StoreLocation {accountId: $accountId})
86
+ WHERE elementId(l) = $nodeId
87
+ RETURN count(l) AS n`, { accountId, nodeId });
88
+ const n = res.records[0]?.get("n");
89
+ return (typeof n?.toNumber === "function" ? n.toNumber() : Number(n ?? 0)) === 1;
90
+ }
91
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/lib/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Task 2448 — the replenishment arithmetic.
3
+ *
4
+ * Pure functions over plain numbers, deliberately free of any graph or driver
5
+ * import, so the whole of the decision maths is testable without a database.
6
+ */
7
+ /** Population standard deviation. Returns 0 for zero or one value, where
8
+ * spread is undefined, rather than NaN: NaN would propagate silently into a
9
+ * reorder point and surface as a nonsense order quantity. */
10
+ export declare function standardDeviation(values: number[]): number;
11
+ export declare function serviceLevelZ(serviceLevel: number): number;
12
+ export interface ReorderPointInput {
13
+ /** Expected units sold per period, from the forecast. */
14
+ perPeriod: number;
15
+ /** Spread of units sold per period, across the same history. */
16
+ stdDevPerPeriod: number;
17
+ /** Length of one demand period in days. */
18
+ periodDays: number;
19
+ leadTimeDays: number;
20
+ reviewPeriodDays: number;
21
+ serviceLevel: number;
22
+ }
23
+ /**
24
+ * The stock level at which to reorder.
25
+ *
26
+ * Expected demand across the whole exposure window (the lead time plus the
27
+ * review period, because an order placed just after a review waits a full
28
+ * cycle before the next chance), plus a buffer scaled by how much demand
29
+ * actually varies over that same window.
30
+ */
31
+ export declare function reorderPoint(input: ReorderPointInput): number;
32
+ export interface OrderQuantityInput {
33
+ /** Order-up-to level, which is the reorder point under periodic review. */
34
+ target: number;
35
+ /** On hand plus already on order. */
36
+ available: number;
37
+ minimumOrderQuantity: number;
38
+ orderMultiple: number;
39
+ }
40
+ /**
41
+ * How much to order.
42
+ *
43
+ * Order sequence matters and is the usual source of bugs: raise to the
44
+ * supplier's minimum FIRST, then round up to their pack multiple. Doing it the
45
+ * other way round can round a raised quantity back below the minimum when the
46
+ * minimum is not itself a multiple of the pack size.
47
+ */
48
+ export declare function orderQuantity(input: OrderQuantityInput): number;
49
+ /**
50
+ * How many days the stock on hand lasts at the expected rate.
51
+ *
52
+ * Null when nothing is expected to sell: cover is undefined then, and
53
+ * returning a large number would rank a dead product alongside a healthy one.
54
+ */
55
+ export declare function daysOfCover(quantityOnHand: number, perPeriod: number, periodDays: number): number | null;
56
+ //# sourceMappingURL=replenish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replenish.d.ts","sourceRoot":"","sources":["../../src/lib/replenish.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;8DAE8D;AAC9D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAK1D;AAsBD,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAmB1D;AAED,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAwB7D;AAED,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAc/D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAQf"}