@rubytech/create-maxy-code 0.1.565 → 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.
- package/package.json +1 -1
- package/payload/platform/config/brand-registry.json +1 -1
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/neo4j/schema.cypher +9 -0
- package/payload/platform/package-lock.json +66 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +48 -1
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +5 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
- package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +47 -0
- package/payload/platform/plugins/inventory/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/inventory/PLUGIN.md +75 -0
- package/payload/platform/plugins/inventory/lib/mcp-spawn-tee/index.js +204 -0
- package/payload/platform/plugins/inventory/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/inventory/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/inventory/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/index.js +241 -0
- package/payload/platform/plugins/inventory/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.d.ts +20 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.js +29 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/forecast.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/read.d.ts +28 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/read.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/read.js +91 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/read.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.d.ts +56 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.js +107 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/replenish.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/types.d.ts +98 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/types.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/types.js +7 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/types.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.d.ts +12 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.js +49 -0
- package/payload/platform/plugins/inventory/mcp/dist/lib/write-rejects.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.d.ts +20 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.js +53 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/demand-record.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.d.ts +23 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.js +18 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/forecast.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.d.ts +25 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.js +37 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/policy-set.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.d.ts +25 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.js +43 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/purchase-order-record.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.d.ts +29 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.js +95 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/replenish-plan.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.d.ts +21 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.js +46 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/stock-record.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.d.ts +24 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.d.ts.map +1 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.js +29 -0
- package/payload/platform/plugins/inventory/mcp/dist/tools/supplier-terms-set.js.map +1 -0
- package/payload/platform/plugins/inventory/mcp/package.json +21 -0
- package/payload/platform/plugins/memory/references/schema-retail.md +20 -0
- package/payload/platform/scripts/vnc.sh +10 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +8 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +66 -5
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-spawn-model.d.ts +28 -0
- package/payload/platform/services/claude-session-manager/dist/public-spawn-model.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/public-spawn-model.js +58 -0
- package/payload/platform/services/claude-session-manager/dist/public-spawn-model.js.map +1 -0
- package/payload/platform/services/telegram-channel/dist/server.d.ts +5 -0
- package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/server.js +22 -2
- package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +34 -2
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.js +150 -14
- package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.d.ts +5 -0
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.js +22 -2
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +34 -2
- package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.js +150 -14
- package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts +10 -2
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +45 -6
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +34 -2
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +153 -15
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
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 function standardDeviation(values) {
|
|
11
|
+
if (values.length < 2)
|
|
12
|
+
return 0;
|
|
13
|
+
const mean = values.reduce((sum, n) => sum + n, 0) / values.length;
|
|
14
|
+
const variance = values.reduce((sum, n) => sum + (n - mean) ** 2, 0) / values.length;
|
|
15
|
+
return Math.sqrt(variance);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* How many standard deviations of buffer a given service level needs.
|
|
19
|
+
*
|
|
20
|
+
* A lookup table with linear interpolation rather than an inverse normal
|
|
21
|
+
* function: the table is exact at the levels operators actually pick, it is
|
|
22
|
+
* readable by anyone auditing a recommendation, and it needs no special
|
|
23
|
+
* function implementation. Levels between table points interpolate; the
|
|
24
|
+
* function refuses outside the open interval rather than clamping, because a
|
|
25
|
+
* clamp would silently answer a question the operator did not ask.
|
|
26
|
+
*/
|
|
27
|
+
const Z_TABLE = [
|
|
28
|
+
[0.5, 0],
|
|
29
|
+
[0.8, 0.8416],
|
|
30
|
+
[0.9, 1.2816],
|
|
31
|
+
[0.95, 1.6449],
|
|
32
|
+
[0.975, 1.96],
|
|
33
|
+
[0.99, 2.3263],
|
|
34
|
+
[0.999, 3.0902],
|
|
35
|
+
];
|
|
36
|
+
export function serviceLevelZ(serviceLevel) {
|
|
37
|
+
if (!(serviceLevel > 0 && serviceLevel < 1)) {
|
|
38
|
+
throw new Error(`[inventory] serviceLevelZ: service level must be strictly between 0 and 1, got ${serviceLevel}`);
|
|
39
|
+
}
|
|
40
|
+
if (serviceLevel <= Z_TABLE[0][0])
|
|
41
|
+
return Z_TABLE[0][1];
|
|
42
|
+
const last = Z_TABLE[Z_TABLE.length - 1];
|
|
43
|
+
if (serviceLevel >= last[0])
|
|
44
|
+
return last[1];
|
|
45
|
+
for (let i = 1; i < Z_TABLE.length; i += 1) {
|
|
46
|
+
const [hiLevel, hiZ] = Z_TABLE[i];
|
|
47
|
+
if (serviceLevel <= hiLevel) {
|
|
48
|
+
const [loLevel, loZ] = Z_TABLE[i - 1];
|
|
49
|
+
const t = (serviceLevel - loLevel) / (hiLevel - loLevel);
|
|
50
|
+
return loZ + t * (hiZ - loZ);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return last[1];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The stock level at which to reorder.
|
|
57
|
+
*
|
|
58
|
+
* Expected demand across the whole exposure window (the lead time plus the
|
|
59
|
+
* review period, because an order placed just after a review waits a full
|
|
60
|
+
* cycle before the next chance), plus a buffer scaled by how much demand
|
|
61
|
+
* actually varies over that same window.
|
|
62
|
+
*/
|
|
63
|
+
export function reorderPoint(input) {
|
|
64
|
+
const { perPeriod, stdDevPerPeriod, periodDays, leadTimeDays, reviewPeriodDays, serviceLevel, } = input;
|
|
65
|
+
if (!(periodDays > 0)) {
|
|
66
|
+
throw new Error(`[inventory] reorderPoint: periodDays must be positive, got ${periodDays}`);
|
|
67
|
+
}
|
|
68
|
+
const exposureDays = leadTimeDays + reviewPeriodDays;
|
|
69
|
+
const exposurePeriods = exposureDays / periodDays;
|
|
70
|
+
const expected = perPeriod * exposurePeriods;
|
|
71
|
+
const buffer = serviceLevelZ(serviceLevel) * stdDevPerPeriod * Math.sqrt(exposurePeriods);
|
|
72
|
+
return expected + buffer;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* How much to order.
|
|
76
|
+
*
|
|
77
|
+
* Order sequence matters and is the usual source of bugs: raise to the
|
|
78
|
+
* supplier's minimum FIRST, then round up to their pack multiple. Doing it the
|
|
79
|
+
* other way round can round a raised quantity back below the minimum when the
|
|
80
|
+
* minimum is not itself a multiple of the pack size.
|
|
81
|
+
*/
|
|
82
|
+
export function orderQuantity(input) {
|
|
83
|
+
const { target, available, minimumOrderQuantity, orderMultiple } = input;
|
|
84
|
+
if (!(orderMultiple > 0)) {
|
|
85
|
+
throw new Error(`[inventory] orderQuantity: order multiple must be positive, got ${orderMultiple}`);
|
|
86
|
+
}
|
|
87
|
+
const gap = target - available;
|
|
88
|
+
if (gap <= 0)
|
|
89
|
+
return 0;
|
|
90
|
+
const raised = Math.max(gap, minimumOrderQuantity);
|
|
91
|
+
return Math.ceil(raised / orderMultiple) * orderMultiple;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* How many days the stock on hand lasts at the expected rate.
|
|
95
|
+
*
|
|
96
|
+
* Null when nothing is expected to sell: cover is undefined then, and
|
|
97
|
+
* returning a large number would rank a dead product alongside a healthy one.
|
|
98
|
+
*/
|
|
99
|
+
export function daysOfCover(quantityOnHand, perPeriod, periodDays) {
|
|
100
|
+
if (!(periodDays > 0)) {
|
|
101
|
+
throw new Error(`[inventory] daysOfCover: periodDays must be positive, got ${periodDays}`);
|
|
102
|
+
}
|
|
103
|
+
if (perPeriod <= 0)
|
|
104
|
+
return null;
|
|
105
|
+
return quantityOnHand / (perPeriod / periodDays);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=replenish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replenish.js","sourceRoot":"","sources":["../../src/lib/replenish.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — shared shapes for the inventory tools.
|
|
3
|
+
*
|
|
4
|
+
* `write` and the `resolve*` reads are injected rather than imported by the
|
|
5
|
+
* tool modules so unit tests exercise the decision logic without loading the
|
|
6
|
+
* Neo4j driver. `index.ts` passes the real implementations.
|
|
7
|
+
*/
|
|
8
|
+
import type { Session } from "neo4j-driver";
|
|
9
|
+
/**
|
|
10
|
+
* The real graph-write contracts, imported rather than redeclared. A
|
|
11
|
+
* redeclared GraphRelationship carrying `targetLabel` instead of
|
|
12
|
+
* `targetNodeId` shipped a dead write path in the ledger plugin: every create
|
|
13
|
+
* was rejected at runtime with `reason=unresolved-target requested=1 found=0`.
|
|
14
|
+
* Importing the real types makes that a compile error. Never redeclare them.
|
|
15
|
+
*/
|
|
16
|
+
import type { CreatedBy, GraphRelationship } from "../../../../../lib/graph-write/dist/index.js";
|
|
17
|
+
export type { CreatedBy, GraphRelationship };
|
|
18
|
+
export interface WriteParams {
|
|
19
|
+
session: Session;
|
|
20
|
+
labels: string[];
|
|
21
|
+
props: Record<string, unknown>;
|
|
22
|
+
relationships: GraphRelationship[];
|
|
23
|
+
createdBy: CreatedBy;
|
|
24
|
+
}
|
|
25
|
+
export interface WriteResult {
|
|
26
|
+
nodesCreated?: number;
|
|
27
|
+
edgesCreated?: number;
|
|
28
|
+
}
|
|
29
|
+
export type WriteFn = (params: WriteParams) => Promise<WriteResult>;
|
|
30
|
+
export interface ToolResult {
|
|
31
|
+
content: Array<{
|
|
32
|
+
type: "text";
|
|
33
|
+
text: string;
|
|
34
|
+
}>;
|
|
35
|
+
isError?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function ok(text: string): ToolResult;
|
|
38
|
+
export declare function fail(text: string): ToolResult;
|
|
39
|
+
/** One stock line as the agent parsed it out of an upload. */
|
|
40
|
+
export interface StockRow {
|
|
41
|
+
/** Product SKU. Resolved to a :Product element id by the tool. */
|
|
42
|
+
sku: string;
|
|
43
|
+
/** Element id of an existing :StoreLocation. */
|
|
44
|
+
location: string;
|
|
45
|
+
quantityOnHand: number;
|
|
46
|
+
quantityOnOrder: number;
|
|
47
|
+
/** ISO timestamp the count was taken. */
|
|
48
|
+
asOf: string;
|
|
49
|
+
}
|
|
50
|
+
/** One period of observed demand for one product at one location. */
|
|
51
|
+
export interface DemandRow {
|
|
52
|
+
sku: string;
|
|
53
|
+
location: string;
|
|
54
|
+
periodStart: string;
|
|
55
|
+
periodEnd: string;
|
|
56
|
+
quantitySold: number;
|
|
57
|
+
}
|
|
58
|
+
/** Everything the plan needs about one product at one location, read in a
|
|
59
|
+
* single query. Any of the optional fields being absent is what produces a
|
|
60
|
+
* skipped line rather than a guessed one. */
|
|
61
|
+
export interface PlanCandidate {
|
|
62
|
+
sku: string;
|
|
63
|
+
productNodeId: string;
|
|
64
|
+
location: string;
|
|
65
|
+
quantityOnHand: number;
|
|
66
|
+
quantityOnOrder: number;
|
|
67
|
+
/** ISO timestamp of the stock count. */
|
|
68
|
+
asOf: string;
|
|
69
|
+
/** Units sold per period, oldest first. Empty when no history exists. */
|
|
70
|
+
history: number[];
|
|
71
|
+
serviceLevel: number | null;
|
|
72
|
+
reviewPeriodDays: number | null;
|
|
73
|
+
leadTimeDays: number | null;
|
|
74
|
+
minimumOrderQuantity: number | null;
|
|
75
|
+
orderMultiple: number | null;
|
|
76
|
+
supplierNodeId: string | null;
|
|
77
|
+
}
|
|
78
|
+
export interface PlanLine {
|
|
79
|
+
sku: string;
|
|
80
|
+
location: string;
|
|
81
|
+
daysOfCover: number | null;
|
|
82
|
+
reorderPoint: number;
|
|
83
|
+
recommendedQuantity: number;
|
|
84
|
+
supplierNodeId: string;
|
|
85
|
+
/** ISO date the order would arrive if placed today. */
|
|
86
|
+
expectedArrival: string;
|
|
87
|
+
}
|
|
88
|
+
export type PlanMissing = "leadTime" | "serviceLevel" | "history";
|
|
89
|
+
export interface PlanSkip {
|
|
90
|
+
sku: string;
|
|
91
|
+
location: string;
|
|
92
|
+
missing: PlanMissing;
|
|
93
|
+
}
|
|
94
|
+
/** Resolves a SKU to a :Product element id on this account, or null. */
|
|
95
|
+
export type ResolveProductFn = (session: Session, accountId: string, sku: string) => Promise<string | null>;
|
|
96
|
+
/** Confirms a :StoreLocation element id exists on this account. */
|
|
97
|
+
export type ResolveLocationFn = (session: Session, accountId: string, nodeId: string) => Promise<boolean>;
|
|
98
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AA0CA,MAAM,UAAU,EAAE,CAAC,IAAY;IAC7B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counts `[graph-write] reject` lines across the inventory MCP tee logs
|
|
3
|
+
* modified within the trailing 24 hours.
|
|
4
|
+
*
|
|
5
|
+
* Uses file mtime rather than parsing per-line timestamps: the tee log carries
|
|
6
|
+
* no timestamp of its own, and a file untouched for a day cannot hold a reject
|
|
7
|
+
* from the last one. Best-effort; an unreadable log directory yields 0 rather
|
|
8
|
+
* than throwing, because a census that dies on a missing directory tells the
|
|
9
|
+
* operator nothing at all.
|
|
10
|
+
*/
|
|
11
|
+
export declare function countWriteRejects24h(logDir: string, now: number): number;
|
|
12
|
+
//# sourceMappingURL=write-rejects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-rejects.d.ts","sourceRoot":"","sources":["../../src/lib/write-rejects.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAuBxE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — count recent write rejections from the inventory MCP logs.
|
|
3
|
+
*
|
|
4
|
+
* A rejected write cannot record its own failure in the graph, because the
|
|
5
|
+
* whole point is that nothing was written. The only durable trace is the
|
|
6
|
+
* stderr line graph-write emits, which mcp-spawn-tee captures per session.
|
|
7
|
+
* Counting those lines is what lets an operator distinguish a healthy empty
|
|
8
|
+
* inventory from one whose write path is dead. The ledger plugin printed
|
|
9
|
+
* those two states identically for a full day on 2026-07-18.
|
|
10
|
+
*/
|
|
11
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const DAY_MS = 86_400_000;
|
|
14
|
+
/**
|
|
15
|
+
* Counts `[graph-write] reject` lines across the inventory MCP tee logs
|
|
16
|
+
* modified within the trailing 24 hours.
|
|
17
|
+
*
|
|
18
|
+
* Uses file mtime rather than parsing per-line timestamps: the tee log carries
|
|
19
|
+
* no timestamp of its own, and a file untouched for a day cannot hold a reject
|
|
20
|
+
* from the last one. Best-effort; an unreadable log directory yields 0 rather
|
|
21
|
+
* than throwing, because a census that dies on a missing directory tells the
|
|
22
|
+
* operator nothing at all.
|
|
23
|
+
*/
|
|
24
|
+
export function countWriteRejects24h(logDir, now) {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = readdirSync(logDir).filter((f) => f.startsWith("mcp-inventory-") && f.endsWith(".log"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
let total = 0;
|
|
33
|
+
for (const name of entries) {
|
|
34
|
+
const path = join(logDir, name);
|
|
35
|
+
try {
|
|
36
|
+
if (now - statSync(path).mtimeMs > DAY_MS)
|
|
37
|
+
continue;
|
|
38
|
+
for (const line of readFileSync(path, "utf-8").split("\n")) {
|
|
39
|
+
if (line.includes("[graph-write] reject"))
|
|
40
|
+
total += 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return total;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=write-rejects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-rejects.js","sourceRoot":"","sources":["../../src/lib/write-rejects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,MAAM,GAAG,UAAU,CAAC;AAE1B;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,GAAW;IAC9D,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC5D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC;YACH,IAAI,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,MAAM;gBAAE,SAAS;YACpD,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC;oBAAE,KAAK,IAAI,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — append observed demand for one product at one location.
|
|
3
|
+
*
|
|
4
|
+
* A zero-sales period is accepted deliberately: it is the difference between
|
|
5
|
+
* "sold nothing" and "no data", and collapsing the two would make the forecast
|
|
6
|
+
* read a quiet week as an absent one.
|
|
7
|
+
*/
|
|
8
|
+
import type { Session } from "neo4j-driver";
|
|
9
|
+
import { type CreatedBy, type DemandRow, type ResolveLocationFn, type ResolveProductFn, type ToolResult, type WriteFn } from "../lib/types.js";
|
|
10
|
+
export interface DemandRecordParams {
|
|
11
|
+
session: Session;
|
|
12
|
+
write: WriteFn;
|
|
13
|
+
resolveProduct: ResolveProductFn;
|
|
14
|
+
resolveLocation: ResolveLocationFn;
|
|
15
|
+
accountId: string;
|
|
16
|
+
createdBy: CreatedBy;
|
|
17
|
+
rows: DemandRow[];
|
|
18
|
+
}
|
|
19
|
+
export declare function demandRecord(params: DemandRecordParams): Promise<ToolResult>;
|
|
20
|
+
//# sourceMappingURL=demand-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demand-record.d.ts","sourceRoot":"","sources":["../../src/tools/demand-record.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EACd,KAAK,SAAS,EAEd,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,OAAO,EAGb,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,gBAAgB,CAAC;IACjC,eAAe,EAAE,iBAAiB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAoElF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { fail, ok, } from "../lib/types.js";
|
|
2
|
+
export async function demandRecord(params) {
|
|
3
|
+
const { session, write, resolveProduct, resolveLocation, accountId, createdBy, rows } = params;
|
|
4
|
+
if (rows.length === 0) {
|
|
5
|
+
return fail(`[inventory] op=reject reason=empty-batch accountId=${accountId} — no demand rows supplied. Nothing was written.`);
|
|
6
|
+
}
|
|
7
|
+
let accepted = 0;
|
|
8
|
+
const rejected = [];
|
|
9
|
+
for (const row of rows) {
|
|
10
|
+
const sku = row.sku.trim();
|
|
11
|
+
const location = row.location.trim();
|
|
12
|
+
if (!(Date.parse(row.periodEnd) > Date.parse(row.periodStart))) {
|
|
13
|
+
rejected.push(`${sku} (period ${row.periodStart}..${row.periodEnd} does not advance)`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (!(row.quantitySold >= 0)) {
|
|
17
|
+
rejected.push(`${sku} (negative quantity ${row.quantitySold})`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const productNodeId = await resolveProduct(session, accountId, sku);
|
|
21
|
+
if (productNodeId === null) {
|
|
22
|
+
rejected.push(`${sku} (unknown sku)`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!(await resolveLocation(session, accountId, location))) {
|
|
26
|
+
rejected.push(`${sku} (unknown location ${location})`);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const relationships = [
|
|
30
|
+
{ type: "OF_PRODUCT", direction: "outgoing", targetNodeId: productNodeId },
|
|
31
|
+
{ type: "AT_LOCATION", direction: "outgoing", targetNodeId: location },
|
|
32
|
+
];
|
|
33
|
+
await write({
|
|
34
|
+
session,
|
|
35
|
+
labels: ["DemandObservation"],
|
|
36
|
+
props: {
|
|
37
|
+
accountId,
|
|
38
|
+
product: productNodeId,
|
|
39
|
+
location,
|
|
40
|
+
periodStart: row.periodStart,
|
|
41
|
+
periodEnd: row.periodEnd,
|
|
42
|
+
quantitySold: row.quantitySold,
|
|
43
|
+
},
|
|
44
|
+
relationships,
|
|
45
|
+
createdBy,
|
|
46
|
+
});
|
|
47
|
+
accepted += 1;
|
|
48
|
+
}
|
|
49
|
+
console.error(`[inventory] op=demand-record accountId=${accountId} rows=${rows.length} accepted=${accepted} rejected=${rejected.length}`);
|
|
50
|
+
const tail = rejected.length === 0 ? "" : ` Rejected: ${rejected.join("; ")}.`;
|
|
51
|
+
return ok(`op=demand-record rows=${rows.length} accepted=${accepted} rejected=${rejected.length}.${tail}`);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=demand-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demand-record.js","sourceRoot":"","sources":["../../src/tools/demand-record.ts"],"names":[],"mappings":"AAQA,OAAO,EAQL,IAAI,EACJ,EAAE,GACH,MAAM,iBAAiB,CAAC;AAYzB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAA0B;IAC3D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,GACnF,MAAM,CAAC;IAET,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CACT,sDAAsD,SAAS,kDAAkD,CAClH,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAErC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YAC/D,QAAQ,CAAC,IAAI,CACX,GAAG,GAAG,YAAY,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC,SAAS,oBAAoB,CACxE,CAAC;YACF,SAAS;QACX,CAAC;QACD,IAAI,CAAC,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,uBAAuB,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC;YAChE,SAAS;QACX,CAAC;QAED,MAAM,aAAa,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACpE,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,gBAAgB,CAAC,CAAC;YACtC,SAAS;QACX,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,sBAAsB,QAAQ,GAAG,CAAC,CAAC;YACvD,SAAS;QACX,CAAC;QAED,MAAM,aAAa,GAAwB;YACzC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE;YAC1E,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE;SACvE,CAAC;QAEF,MAAM,KAAK,CAAC;YACV,OAAO;YACP,MAAM,EAAE,CAAC,mBAAmB,CAAC;YAC7B,KAAK,EAAE;gBACL,SAAS;gBACT,OAAO,EAAE,aAAa;gBACtB,QAAQ;gBACR,WAAW,EAAE,GAAG,CAAC,WAAW;gBAC5B,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,YAAY,EAAE,GAAG,CAAC,YAAY;aAC/B;YACD,aAAa;YACb,SAAS;SACV,CAAC,CAAC;QACH,QAAQ,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,KAAK,CACX,0CAA0C,SAAS,SAAS,IAAI,CAAC,MAAM,aAAa,QAAQ,aAAa,QAAQ,CAAC,MAAM,EAAE,CAC3H,CAAC;IAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IAC/E,OAAO,EAAE,CACP,yBAAyB,IAAI,CAAC,MAAM,aAAa,QAAQ,aAAa,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,CAChG,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — expected demand per period for one product at one location.
|
|
3
|
+
*
|
|
4
|
+
* Nothing is stored. The number is derived from :DemandObservation on every
|
|
5
|
+
* call, which is why replacing the method in Task 2449 needs no migration.
|
|
6
|
+
* An empty history is an error, not a forecast of zero: those two mean
|
|
7
|
+
* opposite things to an operator deciding whether to order.
|
|
8
|
+
*/
|
|
9
|
+
import type { Session } from "neo4j-driver";
|
|
10
|
+
import { type ResolveProductFn, type ToolResult } from "../lib/types.js";
|
|
11
|
+
export type ReadHistoryFn = (session: Session, accountId: string, productNodeId: string, location: string) => Promise<number[]>;
|
|
12
|
+
export interface ForecastParams {
|
|
13
|
+
session: Session;
|
|
14
|
+
resolveProduct: ResolveProductFn;
|
|
15
|
+
readHistory: ReadHistoryFn;
|
|
16
|
+
accountId: string;
|
|
17
|
+
sku: string;
|
|
18
|
+
location: string;
|
|
19
|
+
window: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const FORECAST_METHOD = "moving-average";
|
|
22
|
+
export declare function forecast(params: ForecastParams): Promise<ToolResult>;
|
|
23
|
+
//# sourceMappingURL=forecast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forecast.d.ts","sourceRoot":"","sources":["../../src/tools/forecast.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAY,MAAM,iBAAiB,CAAC;AAEnF,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,WAAW,EAAE,aAAa,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,wBAAsB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA2B1E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { movingAverage } from "../lib/forecast.js";
|
|
2
|
+
import { fail, ok } from "../lib/types.js";
|
|
3
|
+
export const FORECAST_METHOD = "moving-average";
|
|
4
|
+
export async function forecast(params) {
|
|
5
|
+
const { session, resolveProduct, readHistory, accountId, sku, location, window } = params;
|
|
6
|
+
const productNodeId = await resolveProduct(session, accountId, sku.trim());
|
|
7
|
+
if (productNodeId === null) {
|
|
8
|
+
return fail(`[inventory] op=reject reason=unknown-sku sku=${sku.trim()} — no product with that SKU on this account, or more than one.`);
|
|
9
|
+
}
|
|
10
|
+
const history = await readHistory(session, accountId, productNodeId, location.trim());
|
|
11
|
+
if (history.length === 0) {
|
|
12
|
+
return fail(`[inventory] op=reject reason=no-history sku=${sku.trim()} location=${location.trim()} — record demand with inventory-demand-record first. No history is not the same as no demand, so no figure is returned.`);
|
|
13
|
+
}
|
|
14
|
+
const perPeriod = movingAverage(history, window);
|
|
15
|
+
console.error(`[inventory] op=forecast accountId=${accountId} method=${FORECAST_METHOD} window=${window} periods=${history.length} sku=${sku.trim()} location=${location.trim()} perPeriod=${perPeriod}`);
|
|
16
|
+
return ok(`op=forecast method=${FORECAST_METHOD} window=${window} periods=${history.length} perPeriod=${perPeriod}. Expected demand for ${sku.trim()} is ${perPeriod} units per period, averaged over the trailing ${Math.min(window, history.length)} of ${history.length} recorded periods.`);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=forecast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forecast.js","sourceRoot":"","sources":["../../src/tools/forecast.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAA0C,IAAI,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAmBnF,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAsB;IACnD,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,GAC9E,MAAM,CAAC;IAET,MAAM,aAAa,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3E,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CACT,gDAAgD,GAAG,CAAC,IAAI,EAAE,gEAAgE,CAC3H,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACtF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CACT,+CAA+C,GAAG,CAAC,IAAI,EAAE,aAAa,QAAQ,CAAC,IAAI,EAAE,yHAAyH,CAC/M,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAEjD,OAAO,CAAC,KAAK,CACX,qCAAqC,SAAS,WAAW,eAAe,WAAW,MAAM,YAAY,OAAO,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,aAAa,QAAQ,CAAC,IAAI,EAAE,cAAc,SAAS,EAAE,CAC3L,CAAC;IAEF,OAAO,EAAE,CACP,sBAAsB,eAAe,WAAW,MAAM,YAAY,OAAO,CAAC,MAAM,cAAc,SAAS,yBAAyB,GAAG,CAAC,IAAI,EAAE,OAAO,SAAS,iDAAiD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,OAAO,CAAC,MAAM,oBAAoB,CACrR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — set the replenishment policy for one product at one location.
|
|
3
|
+
*
|
|
4
|
+
* A service level of exactly 1 is refused rather than clamped: perfect
|
|
5
|
+
* availability needs infinite safety stock, and silently substituting 0.99
|
|
6
|
+
* would hand the operator a number they did not choose.
|
|
7
|
+
*/
|
|
8
|
+
import type { Session } from "neo4j-driver";
|
|
9
|
+
import { type CreatedBy, type ResolveLocationFn, type ResolveProductFn, type ToolResult, type WriteFn } from "../lib/types.js";
|
|
10
|
+
export interface PolicySetParams {
|
|
11
|
+
session: Session;
|
|
12
|
+
write: WriteFn;
|
|
13
|
+
resolveProduct: ResolveProductFn;
|
|
14
|
+
resolveLocation: ResolveLocationFn;
|
|
15
|
+
accountId: string;
|
|
16
|
+
createdBy: CreatedBy;
|
|
17
|
+
sku: string;
|
|
18
|
+
location: string;
|
|
19
|
+
/** Target in-stock probability, strictly between 0 and 1. */
|
|
20
|
+
serviceLevel: number;
|
|
21
|
+
/** How often the operator actually reviews and reorders, in days. */
|
|
22
|
+
reviewPeriodDays: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function policySet(params: PolicySetParams): Promise<ToolResult>;
|
|
25
|
+
//# sourceMappingURL=policy-set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-set.d.ts","sourceRoot":"","sources":["../../src/tools/policy-set.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,OAAO,EAGb,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,gBAAgB,CAAC;IACjC,eAAe,EAAE,iBAAiB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CA+D5E"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { fail, ok, } from "../lib/types.js";
|
|
2
|
+
export async function policySet(params) {
|
|
3
|
+
const { session, write, resolveProduct, resolveLocation, accountId, createdBy, sku, location, serviceLevel, reviewPeriodDays, } = params;
|
|
4
|
+
if (!(serviceLevel > 0 && serviceLevel < 1)) {
|
|
5
|
+
return fail(`[inventory] op=reject reason=service-level-out-of-range value=${serviceLevel} — must be strictly between 0 and 1. A service level of 1 needs infinite safety stock. Nothing was written.`);
|
|
6
|
+
}
|
|
7
|
+
if (!(reviewPeriodDays > 0)) {
|
|
8
|
+
return fail(`[inventory] op=reject reason=non-positive-review-period value=${reviewPeriodDays} — how many days between reorder reviews. Nothing was written.`);
|
|
9
|
+
}
|
|
10
|
+
const productNodeId = await resolveProduct(session, accountId, sku.trim());
|
|
11
|
+
if (productNodeId === null) {
|
|
12
|
+
return fail(`[inventory] op=reject reason=unknown-sku sku=${sku.trim()} — no product with that SKU on this account, or more than one. Nothing was written.`);
|
|
13
|
+
}
|
|
14
|
+
if (!(await resolveLocation(session, accountId, location.trim()))) {
|
|
15
|
+
return fail(`[inventory] op=reject reason=unknown-location location=${location.trim()} — no store location with that element id on this account. Nothing was written.`);
|
|
16
|
+
}
|
|
17
|
+
const relationships = [
|
|
18
|
+
{ type: "OF_PRODUCT", direction: "outgoing", targetNodeId: productNodeId },
|
|
19
|
+
{ type: "AT_LOCATION", direction: "outgoing", targetNodeId: location.trim() },
|
|
20
|
+
];
|
|
21
|
+
const result = await write({
|
|
22
|
+
session,
|
|
23
|
+
labels: ["ReplenishmentPolicy"],
|
|
24
|
+
props: {
|
|
25
|
+
accountId,
|
|
26
|
+
product: productNodeId,
|
|
27
|
+
location: location.trim(),
|
|
28
|
+
serviceLevel,
|
|
29
|
+
reviewPeriodDays,
|
|
30
|
+
},
|
|
31
|
+
relationships,
|
|
32
|
+
createdBy,
|
|
33
|
+
});
|
|
34
|
+
console.error(`[inventory] op=policy-set accountId=${accountId} sku=${sku.trim()} location=${location.trim()} serviceLevel=${serviceLevel} reviewPeriodDays=${reviewPeriodDays} nodesCreated=${result.nodesCreated ?? 0}`);
|
|
35
|
+
return ok(`Policy set for ${sku.trim()} at that location: ${(serviceLevel * 100).toFixed(1)}% service level, reviewed every ${reviewPeriodDays} days.`);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=policy-set.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-set.js","sourceRoot":"","sources":["../../src/tools/policy-set.ts"],"names":[],"mappings":"AAQA,OAAO,EAOL,IAAI,EACJ,EAAE,GACH,MAAM,iBAAiB,CAAC;AAiBzB,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAuB;IACrD,MAAM,EACJ,OAAO,EACP,KAAK,EACL,cAAc,EACd,eAAe,EACf,SAAS,EACT,SAAS,EACT,GAAG,EACH,QAAQ,EACR,YAAY,EACZ,gBAAgB,GACjB,GAAG,MAAM,CAAC;IAEX,IAAI,CAAC,CAAC,YAAY,GAAG,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5C,OAAO,IAAI,CACT,iEAAiE,YAAY,6GAA6G,CAC3L,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,gBAAgB,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CACT,iEAAiE,gBAAgB,gEAAgE,CAClJ,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3E,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CACT,gDAAgD,GAAG,CAAC,IAAI,EAAE,qFAAqF,CAChJ,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAClE,OAAO,IAAI,CACT,0DAA0D,QAAQ,CAAC,IAAI,EAAE,iFAAiF,CAC3J,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAwB;QACzC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE;QAC1E,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE;KAC9E,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;QACzB,OAAO;QACP,MAAM,EAAE,CAAC,qBAAqB,CAAC;QAC/B,KAAK,EAAE;YACL,SAAS;YACT,OAAO,EAAE,aAAa;YACtB,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE;YACzB,YAAY;YACZ,gBAAgB;SACjB;QACD,aAAa;QACb,SAAS;KACV,CAAC,CAAC;IAEH,OAAO,CAAC,KAAK,CACX,uCAAuC,SAAS,QAAQ,GAAG,CAAC,IAAI,EAAE,aAAa,QAAQ,CAAC,IAAI,EAAE,iBAAiB,YAAY,qBAAqB,gBAAgB,iBAAiB,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE,CAC5M,CAAC;IAEF,OAAO,EAAE,CACP,kBAAkB,GAAG,CAAC,IAAI,EAAE,sBAAsB,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAmC,gBAAgB,QAAQ,CAC7I,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — record a purchase order the operator confirmed.
|
|
3
|
+
*
|
|
4
|
+
* This records; it does not send. Sending is Task 2450 and is a different
|
|
5
|
+
* risk class. The reply says so explicitly, because an operator who believes
|
|
6
|
+
* the order went out will not send it themselves.
|
|
7
|
+
*/
|
|
8
|
+
import type { Session } from "neo4j-driver";
|
|
9
|
+
import { type CreatedBy, type ToolResult, type WriteFn } from "../lib/types.js";
|
|
10
|
+
export interface PurchaseOrderLine {
|
|
11
|
+
sku: string;
|
|
12
|
+
quantity: number;
|
|
13
|
+
}
|
|
14
|
+
export interface PurchaseOrderRecordParams {
|
|
15
|
+
session: Session;
|
|
16
|
+
write: WriteFn;
|
|
17
|
+
accountId: string;
|
|
18
|
+
createdBy: CreatedBy;
|
|
19
|
+
supplierNodeId: string;
|
|
20
|
+
orderDate: string;
|
|
21
|
+
expectedDelivery: string;
|
|
22
|
+
lines: PurchaseOrderLine[];
|
|
23
|
+
}
|
|
24
|
+
export declare function purchaseOrderRecord(params: PurchaseOrderRecordParams): Promise<ToolResult>;
|
|
25
|
+
//# sourceMappingURL=purchase-order-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purchase-order-record.d.ts","sourceRoot":"","sources":["../../src/tools/purchase-order-record.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,UAAU,EACf,KAAK,OAAO,EAGb,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,UAAU,CAAC,CAmErB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { fail, ok, } from "../lib/types.js";
|
|
2
|
+
export async function purchaseOrderRecord(params) {
|
|
3
|
+
const { session, write, accountId, createdBy, supplierNodeId, orderDate, expectedDelivery, lines, } = params;
|
|
4
|
+
if (lines.length === 0) {
|
|
5
|
+
return fail(`[inventory] op=reject reason=empty-order accountId=${accountId} — an order with no lines is not an order. Nothing was written.`);
|
|
6
|
+
}
|
|
7
|
+
for (const line of lines) {
|
|
8
|
+
if (!(line.quantity > 0)) {
|
|
9
|
+
return fail(`[inventory] op=reject reason=non-positive-quantity sku=${line.sku} quantity=${line.quantity} — nothing was written.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (!(Date.parse(expectedDelivery) >= Date.parse(orderDate))) {
|
|
13
|
+
return fail(`[inventory] op=reject reason=delivery-before-order orderDate=${orderDate} expectedDelivery=${expectedDelivery} — nothing was written.`);
|
|
14
|
+
}
|
|
15
|
+
if (supplierNodeId.trim() === "") {
|
|
16
|
+
return fail(`[inventory] op=reject reason=missing-supplier — pass the element id of the supplier organisation. Nothing was written.`);
|
|
17
|
+
}
|
|
18
|
+
// schema-retail.md requires lineItems on :PurchaseOrder. Neo4j has no map
|
|
19
|
+
// property type, so the lines are serialised to a JSON string rather than
|
|
20
|
+
// split across parallel arrays that could drift out of step.
|
|
21
|
+
const lineItems = JSON.stringify(lines);
|
|
22
|
+
const relationships = [
|
|
23
|
+
{ type: "FROM_SUPPLIER", direction: "outgoing", targetNodeId: supplierNodeId.trim() },
|
|
24
|
+
];
|
|
25
|
+
const result = await write({
|
|
26
|
+
session,
|
|
27
|
+
labels: ["PurchaseOrder"],
|
|
28
|
+
props: {
|
|
29
|
+
accountId,
|
|
30
|
+
supplier: supplierNodeId.trim(),
|
|
31
|
+
lineItems,
|
|
32
|
+
orderDate,
|
|
33
|
+
expectedDelivery,
|
|
34
|
+
orderStatus: "open",
|
|
35
|
+
},
|
|
36
|
+
relationships,
|
|
37
|
+
createdBy,
|
|
38
|
+
});
|
|
39
|
+
const units = lines.reduce((sum, l) => sum + l.quantity, 0);
|
|
40
|
+
console.error(`[inventory] op=purchase-order-record accountId=${accountId} supplier=${supplierNodeId.trim()} lines=${lines.length} units=${units} nodesCreated=${result.nodesCreated ?? 0} edgesCreated=${result.edgesCreated ?? 0}`);
|
|
41
|
+
return ok(`Recorded a purchase order for ${lines.length} line${lines.length === 1 ? "" : "s"}, ${units} units, expected ${expectedDelivery}. It has not been sent to the supplier: this records the order only.`);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=purchase-order-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purchase-order-record.js","sourceRoot":"","sources":["../../src/tools/purchase-order-record.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,IAAI,EACJ,EAAE,GACH,MAAM,iBAAiB,CAAC;AAkBzB,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAiC;IAEjC,MAAM,EACJ,OAAO,EACP,KAAK,EACL,SAAS,EACT,SAAS,EACT,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,KAAK,GACN,GAAG,MAAM,CAAC;IAEX,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CACT,sDAAsD,SAAS,iEAAiE,CACjI,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CACT,0DAA0D,IAAI,CAAC,GAAG,aAAa,IAAI,CAAC,QAAQ,yBAAyB,CACtH,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAC7D,OAAO,IAAI,CACT,gEAAgE,SAAS,qBAAqB,gBAAgB,yBAAyB,CACxI,CAAC;IACJ,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACjC,OAAO,IAAI,CACT,wHAAwH,CACzH,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,0EAA0E;IAC1E,6DAA6D;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAExC,MAAM,aAAa,GAAwB;QACzC,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,CAAC,IAAI,EAAE,EAAE;KACtF,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;QACzB,OAAO;QACP,MAAM,EAAE,CAAC,eAAe,CAAC;QACzB,KAAK,EAAE;YACL,SAAS;YACT,QAAQ,EAAE,cAAc,CAAC,IAAI,EAAE;YAC/B,SAAS;YACT,SAAS;YACT,gBAAgB;YAChB,WAAW,EAAE,MAAM;SACpB;QACD,aAAa;QACb,SAAS;KACV,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,KAAK,CACX,kDAAkD,SAAS,aAAa,cAAc,CAAC,IAAI,EAAE,UAAU,KAAK,CAAC,MAAM,UAAU,KAAK,iBAAiB,MAAM,CAAC,YAAY,IAAI,CAAC,iBAAiB,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE,CACvN,CAAC;IAEF,OAAO,EAAE,CACP,iCAAiC,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,oBAAoB,gBAAgB,sEAAsE,CACvM,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 2448 — what to reorder at one location, how much, and from whom.
|
|
3
|
+
*
|
|
4
|
+
* Two things this deliberately does not do. It never substitutes a default
|
|
5
|
+
* for a missing lead time, service level or history: those lines come back
|
|
6
|
+
* named as skipped, because a default would hand the operator a confident
|
|
7
|
+
* wrong quantity. And it never asserts the freshness of its own answer: the
|
|
8
|
+
* oldest and newest stock timestamps it actually read are returned, so the
|
|
9
|
+
* agent reports data age as a measured fact.
|
|
10
|
+
*/
|
|
11
|
+
import type { Session } from "neo4j-driver";
|
|
12
|
+
import { type PlanCandidate, type ToolResult } from "../lib/types.js";
|
|
13
|
+
export type ReadCandidatesFn = (session: Session, accountId: string, location: string) => Promise<PlanCandidate[]>;
|
|
14
|
+
export interface ReplenishPlanParams {
|
|
15
|
+
session: Session;
|
|
16
|
+
readCandidates: ReadCandidatesFn;
|
|
17
|
+
accountId: string;
|
|
18
|
+
location: string;
|
|
19
|
+
window: number;
|
|
20
|
+
periodDays: number;
|
|
21
|
+
/** ISO date used as the order date. Injected rather than read from the clock
|
|
22
|
+
* so the expected-arrival arithmetic is testable. */
|
|
23
|
+
today: string;
|
|
24
|
+
/** Recent refused writes, injected. Zero when the caller does not supply
|
|
25
|
+
* one, so the plan never fails because a log directory is unreadable. */
|
|
26
|
+
recentWriteRejects?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function replenishPlan(params: ReplenishPlanParams): Promise<ToolResult>;
|
|
29
|
+
//# sourceMappingURL=replenish-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replenish-plan.d.ts","sourceRoot":"","sources":["../../src/tools/replenish-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ5C,OAAO,EACL,KAAK,aAAa,EAGlB,KAAK,UAAU,EAEhB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;AAE9B,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB;0DACsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd;8EAC0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAQD,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CA2GpF"}
|