@you-agent-factory/factory-replay 0.0.0 → 0.0.2
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/LICENSE.md +1 -1
- package/README.md +88 -56
- package/dist/activity-contract.d.ts +62 -0
- package/dist/activity-contract.js +1 -0
- package/dist/activity-replay.d.ts +3 -0
- package/dist/activity-replay.js +165 -0
- package/dist/activity.d.ts +7 -0
- package/dist/activity.js +306 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/load-contract.d.ts +57 -0
- package/dist/load-contract.js +1 -0
- package/dist/load-replay.d.ts +3 -0
- package/dist/load-replay.js +156 -0
- package/dist/load.d.ts +3 -0
- package/dist/load.js +260 -0
- package/dist/progress.d.ts +44 -0
- package/dist/progress.js +278 -0
- package/dist/replay.d.ts +98 -0
- package/dist/replay.js +144 -0
- package/dist/topology-connection.d.ts +3 -0
- package/dist/topology-connection.js +80 -0
- package/dist/topology-contract.d.ts +187 -0
- package/dist/topology-contract.js +67 -0
- package/dist/topology-identity.d.ts +5 -0
- package/dist/topology-identity.js +8 -0
- package/dist/topology.d.ts +9 -0
- package/dist/topology.js +255 -0
- package/package.json +36 -11
- package/src/activity.js +0 -208
- package/src/index.d.ts +0 -324
- package/src/index.js +0 -477
- package/src/progress.js +0 -117
- package/src/topology.js +0 -398
package/dist/topology.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { canonicalizeFactoryEvents } from "./replay.js";
|
|
2
|
+
import { projectFactoryTopologyConnection } from "./topology-connection.js";
|
|
3
|
+
import { FACTORY_TOPOLOGY_RELATIONSHIPS } from "./topology-contract.js";
|
|
4
|
+
import { factoryTopologyEntityId, factoryTopologyNodeId, } from "./topology-identity.js";
|
|
5
|
+
export { projectFactoryTopologyConnection } from "./topology-connection.js";
|
|
6
|
+
export { FACTORY_TOPOLOGY_RELATIONSHIPS } from "./topology-contract.js";
|
|
7
|
+
export { factoryTopologyEntityId, factoryTopologyNodeId, } from "./topology-identity.js";
|
|
8
|
+
function handlesFor(kind) {
|
|
9
|
+
const handles = new Map();
|
|
10
|
+
for (const relationship of Object.values(FACTORY_TOPOLOGY_RELATIONSHIPS)) {
|
|
11
|
+
if (relationship.source.nodeKind === kind) {
|
|
12
|
+
handles.set(relationship.source.handleId, {
|
|
13
|
+
id: relationship.source.handleId,
|
|
14
|
+
role: "source",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (relationship.target.nodeKind === kind) {
|
|
18
|
+
handles.set(relationship.target.handleId, {
|
|
19
|
+
id: relationship.target.handleId,
|
|
20
|
+
role: "target",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...handles.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
25
|
+
}
|
|
26
|
+
function sortedByIdentity(values) {
|
|
27
|
+
return [...(values ?? [])].sort((left, right) => {
|
|
28
|
+
const identityDifference = factoryTopologyEntityId(left.id, left.name).localeCompare(factoryTopologyEntityId(right.id, right.name));
|
|
29
|
+
return identityDifference || left.name.localeCompare(right.name);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function createContext() {
|
|
33
|
+
return {
|
|
34
|
+
connections: new Map(),
|
|
35
|
+
issues: new Map(),
|
|
36
|
+
nodes: new Map(),
|
|
37
|
+
resourcesByName: new Map(),
|
|
38
|
+
workersByName: new Map(),
|
|
39
|
+
workStatesByName: new Map(),
|
|
40
|
+
workTypesByName: new Map(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function addNode(context, node) {
|
|
44
|
+
if (context.nodes.has(node.id)) {
|
|
45
|
+
const id = `duplicate-entity:${node.id}`;
|
|
46
|
+
context.issues.set(id, {
|
|
47
|
+
code: "DUPLICATE_ENTITY_ID",
|
|
48
|
+
id,
|
|
49
|
+
message: `Factory topology contains duplicate node id ${node.id}.`,
|
|
50
|
+
nodeId: node.id,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
context.nodes.set(node.id, node);
|
|
55
|
+
}
|
|
56
|
+
function addConnection(context, kind, sourceNodeId, targetNodeId, sourceReference, targetReference) {
|
|
57
|
+
const result = projectFactoryTopologyConnection([...context.nodes.values()], {
|
|
58
|
+
kind,
|
|
59
|
+
sourceNodeId,
|
|
60
|
+
sourceReference,
|
|
61
|
+
targetNodeId,
|
|
62
|
+
targetReference,
|
|
63
|
+
});
|
|
64
|
+
if (!result.ok) {
|
|
65
|
+
context.issues.set(result.issue.id, result.issue);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
context.connections.set(result.connection.id, result.connection);
|
|
69
|
+
}
|
|
70
|
+
function appendNodes(factory, context) {
|
|
71
|
+
for (const resource of sortedByIdentity(factory.resources)) {
|
|
72
|
+
const id = factoryTopologyEntityId(resource.id, resource.name);
|
|
73
|
+
context.resourcesByName.set(resource.name, id);
|
|
74
|
+
addNode(context, {
|
|
75
|
+
capacity: resource.capacity,
|
|
76
|
+
entityId: id,
|
|
77
|
+
handles: handlesFor("resource"),
|
|
78
|
+
id: factoryTopologyNodeId("resource", id),
|
|
79
|
+
kind: "resource",
|
|
80
|
+
label: resource.name,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
for (const worker of sortedByIdentity(factory.workers)) {
|
|
84
|
+
const id = factoryTopologyEntityId(worker.id, worker.name);
|
|
85
|
+
context.workersByName.set(worker.name, id);
|
|
86
|
+
addNode(context, {
|
|
87
|
+
entityId: id,
|
|
88
|
+
handles: handlesFor("worker"),
|
|
89
|
+
id: factoryTopologyNodeId("worker", id),
|
|
90
|
+
kind: "worker",
|
|
91
|
+
label: worker.name,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
for (const workType of sortedByIdentity(factory.workTypes)) {
|
|
95
|
+
const id = factoryTopologyEntityId(workType.id, workType.name);
|
|
96
|
+
context.workTypesByName.set(workType.name, id);
|
|
97
|
+
addNode(context, {
|
|
98
|
+
entityId: id,
|
|
99
|
+
handles: handlesFor("work-type"),
|
|
100
|
+
id: factoryTopologyNodeId("work-type", id),
|
|
101
|
+
kind: "work-type",
|
|
102
|
+
label: workType.name,
|
|
103
|
+
});
|
|
104
|
+
const states = new Map();
|
|
105
|
+
for (const state of sortedByIdentity(workType.states)) {
|
|
106
|
+
const stateId = `${id}:${factoryTopologyEntityId(state.id, state.name)}`;
|
|
107
|
+
states.set(state.name, stateId);
|
|
108
|
+
addNode(context, {
|
|
109
|
+
category: state.type,
|
|
110
|
+
entityId: stateId,
|
|
111
|
+
handles: handlesFor("work-state"),
|
|
112
|
+
id: factoryTopologyNodeId("work-state", stateId),
|
|
113
|
+
kind: "work-state",
|
|
114
|
+
label: state.name,
|
|
115
|
+
workTypeId: id,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
context.workStatesByName.set(workType.name, states);
|
|
119
|
+
}
|
|
120
|
+
for (const workstation of sortedByIdentity(factory.workstations)) {
|
|
121
|
+
const id = factoryTopologyEntityId(workstation.id, workstation.name);
|
|
122
|
+
addNode(context, {
|
|
123
|
+
entityId: id,
|
|
124
|
+
handles: handlesFor("workstation"),
|
|
125
|
+
id: factoryTopologyNodeId("workstation", id),
|
|
126
|
+
kind: "workstation",
|
|
127
|
+
label: workstation.name,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function appendFoundationConnections(factory, context) {
|
|
132
|
+
for (const worker of sortedByIdentity(factory.workers)) {
|
|
133
|
+
const workerEntityId = context.workersByName.get(worker.name);
|
|
134
|
+
for (const resource of sortedByIdentity(worker.resources)) {
|
|
135
|
+
const resourceEntityId = context.resourcesByName.get(resource.name);
|
|
136
|
+
addConnection(context, "worker-resource", resourceEntityId && factoryTopologyNodeId("resource", resourceEntityId), workerEntityId && factoryTopologyNodeId("worker", workerEntityId), resource.name, worker.name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const workType of sortedByIdentity(factory.workTypes)) {
|
|
140
|
+
const workTypeEntityId = context.workTypesByName.get(workType.name);
|
|
141
|
+
for (const state of sortedByIdentity(workType.states)) {
|
|
142
|
+
const stateEntityId = context.workStatesByName
|
|
143
|
+
.get(workType.name)
|
|
144
|
+
?.get(state.name);
|
|
145
|
+
addConnection(context, "work-type-state", workTypeEntityId &&
|
|
146
|
+
factoryTopologyNodeId("work-type", workTypeEntityId), stateEntityId && factoryTopologyNodeId("work-state", stateEntityId), workType.name, `${workType.name}:${state.name}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function sortedRoutes(routes) {
|
|
151
|
+
const normalizedRoutes = !routes
|
|
152
|
+
? []
|
|
153
|
+
: Array.isArray(routes)
|
|
154
|
+
? routes
|
|
155
|
+
: [routes];
|
|
156
|
+
return [...normalizedRoutes].sort((left, right) => `${left.workType}:${left.state}`.localeCompare(`${right.workType}:${right.state}`));
|
|
157
|
+
}
|
|
158
|
+
function appendRoutes(context, workstation, workstationNodeId) {
|
|
159
|
+
const routes = [
|
|
160
|
+
["workstation-input", workstation.inputs],
|
|
161
|
+
["workstation-output", workstation.outputs],
|
|
162
|
+
["workstation-on-continue", workstation.onContinue],
|
|
163
|
+
["workstation-on-failure", workstation.onFailure],
|
|
164
|
+
["workstation-on-rejection", workstation.onRejection],
|
|
165
|
+
[
|
|
166
|
+
"workstation-output",
|
|
167
|
+
workstation.classificationRoutes?.flatMap((route) => route.outputs),
|
|
168
|
+
],
|
|
169
|
+
];
|
|
170
|
+
for (const [kind, routeList] of routes) {
|
|
171
|
+
for (const route of sortedRoutes(routeList)) {
|
|
172
|
+
const stateEntityId = context.workStatesByName
|
|
173
|
+
.get(route.workType)
|
|
174
|
+
?.get(route.state);
|
|
175
|
+
if (!stateEntityId &&
|
|
176
|
+
route.state === "available" &&
|
|
177
|
+
context.resourcesByName.has(route.workType)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const stateNodeId = stateEntityId && factoryTopologyNodeId("work-state", stateEntityId);
|
|
181
|
+
const isInput = kind === "workstation-input";
|
|
182
|
+
addConnection(context, kind, isInput ? stateNodeId : workstationNodeId, isInput ? workstationNodeId : stateNodeId, isInput ? `${route.workType}:${route.state}` : workstation.name, isInput ? workstation.name : `${route.workType}:${route.state}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function appendWorkstationConnections(factory, context) {
|
|
187
|
+
for (const workstation of sortedByIdentity(factory.workstations)) {
|
|
188
|
+
const workstationNodeId = factoryTopologyNodeId("workstation", factoryTopologyEntityId(workstation.id, workstation.name));
|
|
189
|
+
const workerEntityId = context.workersByName.get(workstation.worker);
|
|
190
|
+
if (workstation.worker.trim()) {
|
|
191
|
+
addConnection(context, "worker-assignment", workerEntityId && factoryTopologyNodeId("worker", workerEntityId), workstationNodeId, workstation.worker, workstation.name);
|
|
192
|
+
}
|
|
193
|
+
for (const resource of sortedByIdentity(workstation.resources)) {
|
|
194
|
+
const resourceEntityId = context.resourcesByName.get(resource.name);
|
|
195
|
+
addConnection(context, "workstation-resource", resourceEntityId && factoryTopologyNodeId("resource", resourceEntityId), workstationNodeId, resource.name, workstation.name);
|
|
196
|
+
}
|
|
197
|
+
appendRoutes(context, workstation, workstationNodeId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Project one public Factory definition without mutating caller-owned data. */
|
|
201
|
+
export function projectFactoryTopology(input) {
|
|
202
|
+
if (!input.factory) {
|
|
203
|
+
return {
|
|
204
|
+
connections: [],
|
|
205
|
+
issues: [
|
|
206
|
+
{
|
|
207
|
+
code: "MISSING_FACTORY",
|
|
208
|
+
id: "missing-factory",
|
|
209
|
+
message: "No Factory topology is available at the selected tick.",
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
nodes: [],
|
|
213
|
+
ok: false,
|
|
214
|
+
selectedTick: input.selectedTick,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const context = createContext();
|
|
218
|
+
appendNodes(input.factory, context);
|
|
219
|
+
appendFoundationConnections(input.factory, context);
|
|
220
|
+
appendWorkstationConnections(input.factory, context);
|
|
221
|
+
const issues = [...context.issues.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
222
|
+
if (issues.length > 0) {
|
|
223
|
+
return {
|
|
224
|
+
connections: [],
|
|
225
|
+
issues,
|
|
226
|
+
nodes: [],
|
|
227
|
+
ok: false,
|
|
228
|
+
selectedTick: input.selectedTick,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
connections: [...context.connections.values()].sort((left, right) => left.id.localeCompare(right.id)),
|
|
233
|
+
issues,
|
|
234
|
+
nodes: [...context.nodes.values()].sort((left, right) => left.id.localeCompare(right.id)),
|
|
235
|
+
ok: true,
|
|
236
|
+
selectedTick: input.selectedTick,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** Reconstruct the last canonical Factory topology effective at one tick. */
|
|
240
|
+
export function projectFactoryTopologyAtTick(input) {
|
|
241
|
+
let factory;
|
|
242
|
+
for (const event of canonicalizeFactoryEvents(input.events)) {
|
|
243
|
+
if (event.context.tick > input.tick)
|
|
244
|
+
break;
|
|
245
|
+
if (event.type !== "INITIAL_STRUCTURE_REQUEST" &&
|
|
246
|
+
event.type !== "FACTORY_CHANGE") {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const payload = event.payload;
|
|
250
|
+
if (payload.factory && typeof payload.factory === "object") {
|
|
251
|
+
factory = payload.factory;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return projectFactoryTopology({ factory, selectedTick: input.tick });
|
|
255
|
+
}
|
package/package.json
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@you-agent-factory/factory-replay",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Deterministic, framework-independent Factory event replay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/portpowered/you-agent-factory.git"
|
|
8
|
+
"url": "git+https://github.com/portpowered/you-agent-factory.git",
|
|
9
|
+
"directory": "ui/packages/factory-replay"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
9
13
|
},
|
|
10
|
-
"type": "module",
|
|
11
14
|
"files": [
|
|
12
|
-
"
|
|
15
|
+
"dist",
|
|
13
16
|
"LICENSE.md",
|
|
14
|
-
"
|
|
17
|
+
"README.md"
|
|
15
18
|
],
|
|
16
|
-
"
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
17
21
|
"exports": {
|
|
18
22
|
".": {
|
|
19
|
-
"types": "./
|
|
20
|
-
"import": "./
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "bun run prepare:client && node scripts/build-package.mjs",
|
|
30
|
+
"check:installed-consumer": "node scripts/verify-installed-consumer.mjs",
|
|
31
|
+
"check:pack": "node scripts/verify-package-pack.mjs",
|
|
32
|
+
"check:package-boundary": "node scripts/check-package-boundary.mjs",
|
|
33
|
+
"prepack": "bun run build",
|
|
34
|
+
"prepare:client": "bun run --cwd ../client build",
|
|
35
|
+
"test": "bun run prepare:client && vitest run --config vitest.config.ts",
|
|
36
|
+
"typecheck": "bun run prepare:client && tsc -p tsconfig.json --noEmit --pretty false",
|
|
37
|
+
"verify": "bun run typecheck && bun run test && bun run build && bun run check:package-boundary && bun run check:pack && bun run check:installed-consumer"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@you-agent-factory/client": "0.0.2"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@you-agent-factory/client": {
|
|
44
|
+
"optional": true
|
|
21
45
|
}
|
|
22
46
|
},
|
|
23
|
-
"
|
|
24
|
-
"
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "~5.9.3",
|
|
49
|
+
"vitest": "4.1.3"
|
|
25
50
|
}
|
|
26
51
|
}
|
package/src/activity.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/** @param {string | undefined} explicitID @param {string} name */
|
|
2
|
-
function entityID(explicitID, name) {
|
|
3
|
-
return explicitID?.trim() || name;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/** @param {string} id */
|
|
7
|
-
function workstationNodeID(id) {
|
|
8
|
-
return `workstation:${id}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** @param {string} id */
|
|
12
|
-
function resourceNodeID(id) {
|
|
13
|
-
return `resource:${id}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Project active Dispatch and resource occupancy evidence from one selected
|
|
18
|
-
* canonical replay state without mutating caller-owned data.
|
|
19
|
-
*
|
|
20
|
-
* @param {import("./index.d.ts").FactoryActivityProjectionInput} input
|
|
21
|
-
* @returns {import("./index.d.ts").FactoryActivityProjection}
|
|
22
|
-
*/
|
|
23
|
-
export function projectFactoryActivity(input) {
|
|
24
|
-
const issues = new Map();
|
|
25
|
-
const { resourcesByName, workstationsByTransition } = indexFactory(
|
|
26
|
-
input.factory,
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const occupiedByResource = new Map();
|
|
30
|
-
const occupancyEvidenceAvailable = input.activeDispatches.every(
|
|
31
|
-
(dispatch) => dispatch.resourceNames !== undefined,
|
|
32
|
-
);
|
|
33
|
-
const activeDispatches = input.activeDispatches.map((evidence) => {
|
|
34
|
-
const resolved = workstationsByTransition.get(evidence.transitionId);
|
|
35
|
-
const resourceIds = [];
|
|
36
|
-
for (const resourceName of evidence.resourceNames ?? []) {
|
|
37
|
-
const resource = resourcesByName.get(resourceName);
|
|
38
|
-
if (!resource) {
|
|
39
|
-
addIssue(
|
|
40
|
-
issues,
|
|
41
|
-
"UNRESOLVED_RESOURCE",
|
|
42
|
-
`dispatch:${evidence.id}:resource:${resourceName}`,
|
|
43
|
-
`Dispatch ${evidence.id} refers to unknown resource ${resourceName}.`,
|
|
44
|
-
evidence.id,
|
|
45
|
-
resourceName,
|
|
46
|
-
);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
resourceIds.push(resource.id);
|
|
50
|
-
occupiedByResource.set(
|
|
51
|
-
resource.id,
|
|
52
|
-
(occupiedByResource.get(resource.id) ?? 0) + 1,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
if (!resolved) {
|
|
56
|
-
addIssue(
|
|
57
|
-
issues,
|
|
58
|
-
"UNRESOLVED_WORKSTATION",
|
|
59
|
-
`dispatch:${evidence.id}:workstation:${evidence.transitionId}`,
|
|
60
|
-
`Dispatch ${evidence.id} refers to unknown workstation transition ${evidence.transitionId}.`,
|
|
61
|
-
evidence.id,
|
|
62
|
-
evidence.transitionId,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
const workerName = resolved?.workstation.worker?.trim();
|
|
66
|
-
const worker = workerName
|
|
67
|
-
? (input.factory?.workers ?? []).find(
|
|
68
|
-
(candidate) =>
|
|
69
|
-
candidate.name === workerName || candidate.id === workerName,
|
|
70
|
-
)
|
|
71
|
-
: undefined;
|
|
72
|
-
if (workerName && !worker) {
|
|
73
|
-
addIssue(
|
|
74
|
-
issues,
|
|
75
|
-
"UNRESOLVED_WORKER",
|
|
76
|
-
`dispatch:${evidence.id}:worker:${workerName}`,
|
|
77
|
-
`Dispatch ${evidence.id} refers to unknown worker ${workerName}.`,
|
|
78
|
-
evidence.id,
|
|
79
|
-
workerName,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
id: evidence.id,
|
|
84
|
-
resourceIds: [...resourceIds].sort(),
|
|
85
|
-
startedTick: evidence.startedTick,
|
|
86
|
-
transitionId: evidence.transitionId,
|
|
87
|
-
workIds: [...new Set(evidence.workIds)].sort(),
|
|
88
|
-
...(resolved
|
|
89
|
-
? {
|
|
90
|
-
workstationId: resolved.id,
|
|
91
|
-
workstationNodeId: workstationNodeID(resolved.id),
|
|
92
|
-
}
|
|
93
|
-
: {}),
|
|
94
|
-
...(worker
|
|
95
|
-
? { workerId: entityID(worker.id, worker.name) }
|
|
96
|
-
: {}),
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const resourceOccupancy = projectResourceOccupancy(
|
|
101
|
-
resourcesByName,
|
|
102
|
-
occupiedByResource,
|
|
103
|
-
occupancyEvidenceAvailable,
|
|
104
|
-
issues,
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
activeDispatches: activeDispatches.sort((left, right) =>
|
|
109
|
-
left.id.localeCompare(right.id),
|
|
110
|
-
),
|
|
111
|
-
activeWorkstationIds: [
|
|
112
|
-
...new Set(
|
|
113
|
-
activeDispatches.flatMap((dispatch) =>
|
|
114
|
-
dispatch.workstationId ? [dispatch.workstationId] : [],
|
|
115
|
-
),
|
|
116
|
-
),
|
|
117
|
-
].sort(),
|
|
118
|
-
issues: [...issues.values()].sort((left, right) =>
|
|
119
|
-
left.id.localeCompare(right.id),
|
|
120
|
-
),
|
|
121
|
-
resourceOccupancy: resourceOccupancy.sort((left, right) =>
|
|
122
|
-
left.resourceId.localeCompare(right.resourceId),
|
|
123
|
-
),
|
|
124
|
-
selectedTick: input.selectedTick,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** @param {import("@you-agent-factory/client").FactoryDefinition | undefined} factory */
|
|
129
|
-
function indexFactory(factory) {
|
|
130
|
-
const resourcesByName = new Map();
|
|
131
|
-
const workstationsByTransition = new Map();
|
|
132
|
-
for (const resource of factory?.resources ?? []) {
|
|
133
|
-
resourcesByName.set(resource.name, {
|
|
134
|
-
capacity: Math.max(0, resource.capacity),
|
|
135
|
-
id: entityID(resource.id, resource.name),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
for (const workstation of factory?.workstations ?? []) {
|
|
139
|
-
const id = entityID(workstation.id, workstation.name);
|
|
140
|
-
workstationsByTransition.set(workstation.name, { id, workstation });
|
|
141
|
-
if (workstation.id?.trim()) {
|
|
142
|
-
workstationsByTransition.set(workstation.id, { id, workstation });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return { resourcesByName, workstationsByTransition };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @param {Map<string, {capacity: number, id: string}>} resourcesByName
|
|
150
|
-
* @param {Map<string, number>} occupiedByResource
|
|
151
|
-
* @param {boolean} evidenceAvailable
|
|
152
|
-
* @param {Map<string, import("./index.d.ts").FactoryActivityProjectionIssue>} issues
|
|
153
|
-
* @returns {import("./index.d.ts").FactoryResourceOccupancyProjection[]}
|
|
154
|
-
*/
|
|
155
|
-
function projectResourceOccupancy(
|
|
156
|
-
resourcesByName,
|
|
157
|
-
occupiedByResource,
|
|
158
|
-
evidenceAvailable,
|
|
159
|
-
issues,
|
|
160
|
-
) {
|
|
161
|
-
return [...resourcesByName.values()].map((resource) => {
|
|
162
|
-
if (!evidenceAvailable) {
|
|
163
|
-
return {
|
|
164
|
-
capacity: resource.capacity,
|
|
165
|
-
evidence: "unavailable",
|
|
166
|
-
resourceId: resource.id,
|
|
167
|
-
resourceNodeId: resourceNodeID(resource.id),
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
const occupiedQuantity = occupiedByResource.get(resource.id) ?? 0;
|
|
171
|
-
if (occupiedQuantity > resource.capacity) {
|
|
172
|
-
addIssue(
|
|
173
|
-
issues,
|
|
174
|
-
"RESOURCE_CAPACITY_EXCEEDED",
|
|
175
|
-
`resource:${resource.id}:capacity-exceeded`,
|
|
176
|
-
`Resource ${resource.id} has ${occupiedQuantity} occupied units for capacity ${resource.capacity}.`,
|
|
177
|
-
undefined,
|
|
178
|
-
resource.id,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
return {
|
|
182
|
-
availableQuantity: Math.max(0, resource.capacity - occupiedQuantity),
|
|
183
|
-
capacity: resource.capacity,
|
|
184
|
-
evidence: "known",
|
|
185
|
-
occupiedQuantity,
|
|
186
|
-
resourceId: resource.id,
|
|
187
|
-
resourceNodeId: resourceNodeID(resource.id),
|
|
188
|
-
};
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* @param {Map<string, import("./index.d.ts").FactoryActivityProjectionIssue>} issues
|
|
194
|
-
* @param {import("./index.d.ts").FactoryActivityProjectionIssue["code"]} code
|
|
195
|
-
* @param {string} id
|
|
196
|
-
* @param {string} message
|
|
197
|
-
* @param {string | undefined} dispatchId
|
|
198
|
-
* @param {string | undefined} reference
|
|
199
|
-
*/
|
|
200
|
-
function addIssue(issues, code, id, message, dispatchId, reference) {
|
|
201
|
-
issues.set(id, {
|
|
202
|
-
code,
|
|
203
|
-
id,
|
|
204
|
-
message,
|
|
205
|
-
...(dispatchId ? { dispatchId } : {}),
|
|
206
|
-
...(reference ? { reference } : {}),
|
|
207
|
-
});
|
|
208
|
-
}
|