259 | {headings.map((heading, index) => (
260 |
294 |
295 |
296 | {customers.map(customer => (
>> 297 |
> 318 | onClick={() => {
319 | setCustomerUuid(customer.uuid);
320 | setLimbUuid('');
321 | setProjectUuid('');
322 | setSessionUuid('');
323 | setStep(1);
324 | }}
325 | />
326 | ))}
--- context around line 343 ---
338 | Patient: {patient?.full_name}
339 |
340 |
341 |
342 | {relatedLimbs.map(item => (
>> 343 | > 359 | onClick={() => {
360 | setLimbUuid(item.uuid);
361 | setProjectUuid('');
362 | setSessionUuid('');
363 | setStep(2);
364 | }}
365 | />
366 | ))}
367 |
--- context around line 385 ---
380 | {limb?.profile_name || limb?.configuration}
381 |
382 |
383 |
384 | {relatedProjects.map(item => (
>> 385 |
> 396 | onClick={() => {
397 | setProjectUuid(item.uuid);
398 |
399 | const matches =
400 | sessions.filter(
401 | session =>
402 | session.project_uuid
403 | === item.uuid
404 | );
--- context around line 423 ---
418 | )}
419 |
420 | {step === 3 && (
421 | <>
422 |
>> 423 | Confirm measurement session
424 |
425 |
426 |
427 |
428 | {patient?.full_name}
429 |
430 |
431 |
--- context around line 444 ---
439 |
440 |
441 |
442 |
443 | {relatedSessions.map(item => (
>> 444 | > 453 | item.measured_date
454 | ? `Session date: ${item.measured_date}`
455 | : 'Ready to begin'
456 | }
457 | selected={
458 | sessionUuid === item.uuid
459 | }
460 | onClick={() =>
461 | setSessionUuid(item.uuid)
--- context around line 454 ---
449 | `${item.status || 'Not started'}`
450 | + ` · ${item.progress_percent || 0}% complete`
451 | }
452 | detail={
453 | item.measured_date
>> 454 | ? `Session date: ${item.measured_date}`
455 | : 'Ready to begin'
456 | }
457 | selected={
458 | sessionUuid === item.uuid
459 | }
460 | onClick={() =>
461 | setSessionUuid(item.uuid)
462 | }
--- context around line 460 ---
455 | : 'Ready to begin'
456 | }
457 | selected={
458 | sessionUuid === item.uuid
459 | }
>> 460 | onClick={() =>
461 | setSessionUuid(item.uuid)
462 | }
463 | />
464 | ))}
465 |
466 | >
467 | )}
468 |
--- context around line 470 ---
465 |
466 | >
467 | )}
468 |
469 |
>> 470 |
500 |
501 |
502 |
503 |
504 | );
505 | }
------------------------------------------------------------
4. DATABASE AND SESSION OPERATIONS
------------------------------------------------------------
32: measurementService,
33:} from '@/services/measurementService';
121: measurementService.sessions(),
132: (customerRecords || []).filter(activeRecord)
136: (limbRecords || []).filter(activeRecord)
140: (projectRecords || []).filter(activeRecord)
144: (sessionRecords || []).filter(activeRecord)
167: limbs.filter(
176: projects.filter(
191: .filter(
307: projects.filter(
354: ].filter(Boolean).join(' · ')}
400: sessions.filter(
492: navigate(
------------------------------------------------------------
5. FULL FUNCTION DEFINITIONS
------------------------------------------------------------
========================================================================
44 | function SelectCard({
45 | icon: Icon,
46 | title,
47 | subtitle,
48 | detail,
49 | selected,
50 | onClick,
51 | }) {
52 | return (
53 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | {title}
71 |
72 |
73 | {subtitle && (
74 |
75 | {subtitle}
76 |
77 | )}
78 |
79 | {detail && (
80 |
81 | {detail}
82 |
83 | )}
84 |
85 |
86 | {selected && (
87 |
88 | Selected
89 |
90 | )}
91 |
92 |
93 | );
94 | }
95 |
96 |
97 | export default function TakeMeasurements() {
98 | const navigate = useNavigate();
99 |
100 | const [loaded, setLoaded] = useState(false);
101 | const [error, setError] = useState('');
102 |
103 | const [customers, setCustomers] = useState([]);
104 | const [limbs, setLimbs] = useState([]);
105 | const [projects, setProjects] = useState([]);
106 | const [sessions, setSessions] = useState([]);
107 |
108 | const [step, setStep] = useState(0);
109 | const [customerUuid, setCustomerUuid] = useState('');
110 | const [limbUuid, setLimbUuid] = useState('');
111 | const [projectUuid, setProjectUuid] = useState('');
112 | const [sessionUuid, setSessionUuid] = useState('');
113 |
114 | useEffect(() => {
115 | let mounted = true;
116 |
117 | Promise.all([
118 | customerService.list(),
119 | adapters.database.list('LimbProfile'),
120 | projectService.list(),
121 | measurementService.sessions(),
122 | ])
123 | .then(([
124 | customerRecords,
125 | limbRecords,
126 | projectRecords,
127 | sessionRecords,
128 | ]) => {
129 | if (!mounted) return;
130 |
131 | setCustomers(
132 | (customerRecords || []).filter(activeRecord)
133 | );
134 |
135 | setLimbs(
136 | (limbRecords || []).filter(activeRecord)
137 | );
138 |
139 | setProjects(
140 | (projectRecords || []).filter(activeRecord)
141 | );
142 |
143 | setSessions(
144 | (sessionRecords || []).filter(activeRecord)
145 | );
146 |
147 | setLoaded(true);
148 | })
149 | .catch(err => {
150 | if (!mounted) return;
151 |
152 | setError(
153 | err?.message
154 | || 'Patient measurement records could not be loaded.'
155 | );
156 |
157 | setLoaded(true);
158 | });
159 |
160 | return () => {
161 | mounted = false;
162 | };
163 | }, []);
164 |
165 | const relatedLimbs = useMemo(
166 | () =>
167 | limbs.filter(
168 | limb =>
169 | limb.customer_uuid === customerUuid
170 | ),
171 | [limbs, customerUuid]
172 | );
173 |
174 | const relatedProjects = useMemo(
175 | () =>
176 | projects.filter(
177 | project =>
178 | project.customer_uuid === customerUuid
179 | && project.limb_profile_uuid === limbUuid
180 | ),
181 | [
182 | projects,
183 | customerUuid,
184 | limbUuid,
185 | ]
186 | );
187 |
188 | const relatedSessions = useMemo(
189 | () =>
190 | sessions
191 | .filter(
192 | session =>
193 | session.customer_uuid === customerUuid
194 | && session.limb_profile_uuid === limbUuid
195 | && session.project_uuid === projectUuid
196 | )
197 | .sort(
198 | (a, b) =>
199 | Number(a.progress_percent || 0)
200 | - Number(b.progress_percent || 0)
201 | ),
202 | [
203 | sessions,
204 | customerUuid,
205 | limbUuid,
206 | projectUuid,
207 | ]
208 | );
209 |
210 | const patient =
211 | customers.find(
212 | item => item.uuid === customerUuid
213 | );
214 |
215 | const limb =
216 | limbs.find(
217 | item => item.uuid === limbUuid
218 | );
219 |
220 | const project =
221 | projects.find(
222 | item => item.uuid === projectUuid
223 | );
224 |
225 | if (!loaded) {
226 | return (
227 |
228 | );
229 | }
230 |
231 | const headings = [
232 | 'Patient',
233 | 'Limb',
234 | 'Project',
235 | 'Session',
236 | ];
237 |
238 | return (
239 |
240 |
241 |
242 |
243 |
244 |
245 | Patient-first measurement workflow
246 |
247 |
248 |
249 | Select who you are measuring
250 |
251 |
252 |
253 | Confirm the patient, exact limb, cover project and
254 | measurement session before opening the measurement guide.
255 |
256 |
257 |
258 |
259 | {headings.map((heading, index) => (
260 |
271 |
272 | Step {index + 1}
273 |
274 |
275 |
276 | {heading}
277 |
278 |
279 | ))}
280 |
281 |
282 |
283 | {error && (
284 |
285 | {error}
286 |
287 | )}
288 |
289 | {step === 0 && (
290 | <>
291 |
292 | Select patient
293 |
294 |
295 |
296 | {customers.map(customer => (
297 |
309 | item.customer_uuid
310 | === customer.uuid
311 | ).length
312 | + ' linked project(s)'
313 | }
314 | selected={
315 | customerUuid
316 | === customer.uuid
317 | }
318 | onClick={() => {
319 | setCustomerUuid(customer.uuid);
320 | setLimbUuid('');
321 | setProjectUuid('');
322 | setSessionUuid('');
323 | setStep(1);
324 | }}
325 | />
326 | ))}
327 |
328 | >
329 | )}
330 |
331 | {step === 1 && (
332 | <>
333 |
334 | Select exact limb
335 |
336 |
337 |
338 | Patient: {patient?.full_name}
339 |
340 |
341 |
342 | {relatedLimbs.map(item => (
343 | {
360 | setLimbUuid(item.uuid);
361 | setProjectUuid('');
362 | setSessionUuid('');
363 | setStep(2);
364 | }}
365 | />
366 | ))}
367 |
368 | >
369 | )}
370 |
371 | {step === 2 && (
372 | <>
373 |
374 | Select cover project
375 |
376 |
377 |
378 | {patient?.full_name}
379 | {' · '}
380 | {limb?.profile_name || limb?.configuration}
381 |
382 |
383 |
384 | {relatedProjects.map(item => (
385 | {
397 | setProjectUuid(item.uuid);
398 |
399 | const matches =
400 | sessions.filter(
401 | session =>
402 | session.project_uuid
403 | === item.uuid
404 | );
405 |
406 | setSessionUuid(
407 | matches.length === 1
408 | ? matches[0].uuid
409 | : ''
410 | );
411 |
412 | setStep(3);
413 | }}
414 | />
415 | ))}
416 |
417 | >
418 | )}
419 |
420 | {step === 3 && (
421 | <>
422 |
423 | Confirm measurement session
424 |
425 |
426 |
427 |
428 | {patient?.full_name}
429 |
430 |
431 |
432 | {limb?.profile_name || limb?.configuration}
433 |
434 |
435 |
436 | {project?.project_id}
437 | {' · '}
438 | {project?.prosthesis_configuration}
439 |
440 |
441 |
442 |
443 | {relatedSessions.map(item => (
444 |
461 | setSessionUuid(item.uuid)
462 | }
463 | />
464 | ))}
465 |
466 | >
467 | )}
468 |
469 |
470 |
475 | setStep(value =>
476 | Math.max(0, value - 1)
477 | )
478 | }
479 | >
480 |
481 | Back
482 |
483 |
484 |
492 | navigate(
493 | `/measurements/${sessionUuid}`
494 | )
495 | }
496 | >
497 | Begin measurements
498 |
499 |
500 |
501 |
502 |
503 |
504 | );
505 | }
------------------------------------------------------------
6. IMPORTED LOCAL COMPONENTS AND SERVICES
------------------------------------------------------------
IMPORT: @/components/workshop/BoundaryNotice
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/components/workshop/BoundaryNotice.jsx
IMPORT: @/components/workshop/LoadingPanel
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/components/workshop/LoadingPanel.jsx
IMPORT: @/components/workshop/StatusPill
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/components/workshop/StatusPill.jsx
IMPORT: @/services/customerService
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/services/customerService.js
2 | import { auditService } from '@/services/auditService';
3 | export const customerService = {
4 | list: () => adapters.database.list('Customer'),
>> 5 | get: async (uuid) => (await adapters.database.filter('Customer', { uuid }, '-updated_date', 1))[0],
6 | history: async (uuid) => ({ limbs: await adapters.database.filter('LimbProfile', { customer_uuid: uuid }), prostheses: await adapters.database.filter('Prosthesis', { customer_uuid: uuid }), projects: await adapters.database.filter('CoverProject', { customer_uuid: uuid }) }),
7 | async create(data, actor) { const record = await adapters.database.create('Customer', { ...data, uuid: newUuid('cust'), customer_number: `SLC-${Date.now().toString().slice(-6)}`, data_label: data.data_label || 'WORKSHOP RECORD', ...migrationFields(actor) }); await auditService.record('customer_created', 'Customer', record.uuid, `Created customer ${record.customer_number}`, actor); return record; },
8 | async update(id, uuid, data, actor) { const { id: _id, created_date, updated_date, created_by_id, ...editable } = data; const record = await adapters.database.update('Customer', id, { ...editable, updated_by_name: actor }); await auditService.record('customer_edited', 'Customer', uuid, 'Customer details updated', actor); return record; },
9 | archive: async (record, actor) => { await adapters.database.archive('Customer', record.id); return auditService.record('customer_archived', 'Customer', record.uuid, 'Customer archived', actor); },
10 | restore: async (record, actor) => { await adapters.database.restore('Customer', record.id); return auditService.record('customer_restored', 'Customer', record.uuid, 'Customer restored', actor); },
11 | async createLimb(data, actor) { const record = await adapters.database.create('LimbProfile', { ...data, uuid: newUuid('limb'), ...migrationFields(actor) }); await auditService.record('limb_profile_created', 'LimbProfile', record.uuid, `Created ${record.profile_name}`, actor); return record; },
12 | async createProsthesis(data, actor) { const record = await adapters.database.create('Prosthesis', { ...data, uuid: newUuid('pros'), status: 'active', ...migrationFields(actor) }); await auditService.record('prosthesis_created', 'Prosthesis', record.uuid, `Created ${record.name}`, actor); return record; },
13 | };
IMPORT: @/services/projectService
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/services/projectService.js
54 | await adapters.database.filter(
55 | 'CoverProject',
56 | { uuid },
>> 57 | '-updated_date',
58 | 1
59 | )
60 | )[0],
61 |
62 | filter: query =>
65 | query
66 | ),
67 |
>> 68 | async create(data, actor) {
69 | const {
70 | id,
71 | created_date,
72 | updated_date,
73 | created_by_id,
74 | ...portable
75 | } = data;
76 |
77 | const uuid = newUuid('proj');
78 |
79 | const record =
>> 80 | await adapters.database.create(
81 | 'CoverProject',
82 | {
83 | ...portable,
84 | uuid,
85 | project_id:
109 | );
110 |
111 | if (
>> 112 | data.measurement_method?.includes('Manual')
113 | || data.measurement_method?.includes(
114 | 'Combination'
115 | )
116 | ) {
117 | const limb =
118 | (
121 | {
122 | uuid: data.limb_profile_uuid,
123 | },
>> 124 | '-updated_date',
125 | 1
126 | )
127 | )[0];
128 |
129 | const workflow = workflowFor(
131 | limb
132 | );
133 |
>> 134 | await adapters.database.create(
135 | 'MeasurementSession',
136 | {
137 | uuid: newUuid('session'),
138 | project_uuid: uuid,
139 | customer_uuid: data.customer_uuid,
140 | limb_profile_uuid:
141 | data.limb_profile_uuid,
142 | workflow,
143 | status: 'Not started',
>> 144 | measured_by: actor,
145 | measured_date:
146 | new Date()
147 | .toISOString()
148 | .slice(0, 10),
149 | progress_percent: 0,
150 | ...migrationFields(actor),
153 | }
154 |
155 | await auditService.record(
>> 156 | 'project_created',
157 | 'CoverProject',
158 | uuid,
159 | `Created cover project ${record.project_id}`,
160 | actor,
161 | data.components_pending
162 | ? 'Non-load-bearing cosmetic cover only. Component details remain unverified and manufacturing release is blocked.'
163 | : 'Non-load-bearing cosmetic prosthetic cover only. Manual review required.'
164 | );
167 | },
168 |
169 | duplicate: async (project, actor) =>
>> 170 | projectService.create(
171 | {
172 | ...project,
173 | id: undefined,
174 | project_id: undefined,
175 | revision: project.revision + 1,
>> 176 | status: 'Awaiting measurements',
177 | reason_for_cover:
178 | `Revision of ${project.project_id}`,
179 | },
180 | actor
181 | ),
IMPORT: @/services/measurementService
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/services/measurementService.js
1 | import { adapters, migrationFields, newUuid } from '@/services/serviceConfig';
2 | import { auditService } from '@/services/auditService';
>> 3 | export const measurementService = {
4 | sessions: () => adapters.database.list('MeasurementSession'),
5 | session: async (uuid) => (await adapters.database.filter('MeasurementSession', { uuid }, '-updated_date', 1))[0],
6 | definitions: (workflow) => adapters.database.filter('MeasurementDefinition', { workflow, active: true }, 'sequence'),
7 | values: (session_uuid) => adapters.database.filter('MeasurementValue', { session_uuid }),
8 | async save(session, definition, data, actor) { const existing = (await adapters.database.filter('MeasurementValue', { session_uuid: session.uuid, definition_uuid: definition.uuid }, '-updated_date', 1))[0]; const previous = (await adapters.database.filter('MeasurementValue', { definition_uuid: definition.uuid }, '-measured_date', 20)).find(v => v.session_uuid !== session.uuid && v.value_mm); const value_mm = data.entered_unit === 'cm' ? Number(data.value) * 10 : Number(data.value); const warnings = []; if (definition.unit === 'mm' && (value_mm < definition.minimum_value || value_mm > definition.maximum_value)) warnings.push(`Outside expected range ${definition.minimum_value}–${definition.maximum_value} mm.`); if (data.entered_unit === 'mm' && value_mm < definition.minimum_value && value_mm * 10 <= definition.maximum_value) warnings.push('Likely wrong unit: this value may have been entered in centimetres.'); if (previous && Math.abs(value_mm - previous.value_mm) / previous.value_mm > 0.2) warnings.push(`More than 20% different from a previous ${definition.name} record.`); const warning = warnings.length ? `${warnings.join(' ')} Manual review required.` : ''; const payload = { value_mm, entered_unit: data.entered_unit, confidence: data.confidence, measured_by: actor, measured_date: new Date().toISOString().slice(0,10), notes: data.notes, photo_url: data.photo_url || '', storage_destination: data.storage_destination || '', warning, updated_by_name: actor }; const saved = existing ? await adapters.database.update('MeasurementValue', existing.id, payload) : await adapters.database.create('MeasurementValue', { ...payload, uuid: newUuid('measure'), session_uuid: session.uuid, definition_uuid: definition.uuid, project_uuid: session.project_uuid, ...migrationFields(actor) }); await auditService.record('measurement_changed', 'MeasurementValue', saved.uuid, `Saved ${definition.name}`, actor, warning); return saved; },
9 | async skip(session, definition, reason, actor) { const saved = await adapters.database.create('MeasurementValue', { uuid: newUuid('measure'), session_uuid: session.uuid, definition_uuid: definition.uuid, project_uuid: session.project_uuid, entered_unit: 'text', skip_reason: reason, measured_by: actor, measured_date: new Date().toISOString().slice(0,10), ...migrationFields(actor) }); await auditService.record('measurement_skipped', 'MeasurementValue', saved.uuid, `Skipped ${definition.name}: ${reason}`, actor); return saved; },
10 | progress: (session, complete, total) => adapters.database.update('MeasurementSession', session.id, { progress_percent: Math.round((complete / total) * 100), status: complete >= total ? 'Complete' : 'In progress' }),
11 | pause: (session) => adapters.database.update('MeasurementSession', session.id, { status: 'Paused' }),
12 | };
IMPORT: @/services/serviceConfig
RESOLVED: /home/shire3d/ARMOR/agents/apps/limbforge/src/services/serviceConfig.js
45 |
46 | export const newUuid = prefix =>
47 | `${prefix}-${secureUuidV4()}`;
>> 48 | export const migrationFields = (actor = 'Workshop Owner') => ({ created_by_name: actor, updated_by_name: actor, revision: 1, record_state: 'active', migration_version: 1 });
------------------------------------------------------------
7. CURRENT LIVE SESSION AND DEFINITIONS
------------------------------------------------------------
Sessions: 1
Definitions: 12
Values: 0
CoverProjects: 1
Session UUID: session-02915477-3775-4868-8c89-fad351ca6ab8
Status: Not started
Workflow: Osseointegration
Project UUID: proj-3581fe0a-cfd5-4341-af75-eaab8186137b
Component UUID: None
Definitions: 12
Saved values: 0
------------------------------------------------------------
8. LIVE BUNDLE FEATURE STRINGS
------------------------------------------------------------
Live JavaScript: /home/shire3d/ARMOR/agents/apps/limbforge/dist/shire-module/assets/shire-index-C4QDmV6J.js
Bundle SHA: b6fd6ae0e2302711563f6baea448939853a18ce769aa3c499fee8b414d135b97
PRESENT: Begin measurements
ABSENT: 3D model
ABSENT: Model size
PRESENT: Measurement Studio
PRESENT: Save & continue
ABSENT: Save & Continue
PRESENT: MeasurementSession
PRESENT: MeasurementValue
------------------------------------------------------------
9. RECENT REQUESTS FROM THE LIVE PAGE
------------------------------------------------------------
Aug 05 10:33:22 Work python3[2433881]: 05/Aug/2026 10:33:22 - "GET /api/v1/collections/MeasurementDefinition?sort=sequence&limit=500 HTTP/1.1" 200 -
Aug 05 10:33:22 Work python3[2462810]: limbforge 05/Aug/2026 10:33:22 "GET /api/v1/collections/MeasurementDefinition?sort=sequence&limit=500 HTTP/1.1" 200 -
Aug 05 11:14:37 Work python3[2433881]: 05/Aug/2026 11:14:37 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 11:14:37 Work python3[2462810]: limbforge 05/Aug/2026 11:14:37 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 11:14:37 Work python3[2433881]: 05/Aug/2026 11:14:37 - "GET /api/v1/collections/MeasurementDefinition?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 11:14:37 Work python3[2462810]: limbforge 05/Aug/2026 11:14:37 "GET /api/v1/collections/MeasurementDefinition?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 11:14:37 Work python3[2433881]: 05/Aug/2026 11:14:37 - "GET /api/v1/collections/MeasurementValue?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 11:14:37 Work python3[2462810]: limbforge 05/Aug/2026 11:14:37 "GET /api/v1/collections/MeasurementValue?sort=-updated_date&limit=500 HTTP/1.1" 200 -
============================================================
TAKE-MEASUREMENTS AUDIT COMPLETE — NOTHING CHANGED
============================================================
Evidence: /SHiREVault/Backup/OSBackups/LIMBFORGE-TAKE-MEASUREMENTS-WIRING-AUDIT-20260805T031436Z
No source, build, database or service was modified.
No build was run.
No service was restarted.
Sentinel was not modified or restarted.