============================================================ LIMBFORGE — LIVE TAKE MEASUREMENTS WIRING AUDIT ============================================================ UTC: 2026-08-05T03:14:36+00:00 ------------------------------------------------------------ 1. VERIFY THE LIVE ROUTE OWNER ------------------------------------------------------------ 24:import MeasurementSession from '@/pages/MeasurementSession'; 36:import TakeMeasurements from '@/pages/TakeMeasurements'; 52: } /> 55: path="/measurements/:uuid" 56: element={} Page: /home/shire3d/ARMOR/agents/apps/limbforge/src/pages/TakeMeasurements.jsx Page SHA: 0084ad8138682107e0824ab8feabfaafd8ab859ec03cfb5cae42d06bae0419f7 Live index: /home/shire3d/ARMOR/agents/apps/limbforge/dist/shire-module/shire-index.html Live SHA: 6a02ce7b9a88dab19b762032cf89d56d0eb8c62ec8ddb2119dcb28ddf60e457b ------------------------------------------------------------ 2. TAKE-MEASUREMENTS IMPORTS AND STATE ------------------------------------------------------------ >> 1 | import { 2 | useEffect, 3 | useMemo, 4 | useState, 5 | } from 'react'; 6 | 7 | import { 8 | ChevronLeft, 9 | ChevronRight, 10 | FolderKanban, 11 | Ruler, 12 | UserRound, 13 | } from 'lucide-react'; 14 | >> 15 | import { 16 | useNavigate, 17 | } from 'react-router-dom'; 18 | 19 | import BoundaryNotice from '@/components/workshop/BoundaryNotice'; 20 | import LoadingPanel from '@/components/workshop/LoadingPanel'; 21 | import StatusPill from '@/components/workshop/StatusPill'; 22 | 23 | import { 24 | customerService, 25 | } from '@/services/customerService'; 26 | 27 | import { 28 | projectService, 29 | } from '@/services/projectService'; 30 | 31 | import { 32 | measurementService, 33 | } from '@/services/measurementService'; 34 | 35 | import { 36 | adapters, 37 | } from '@/services/serviceConfig'; 38 | 39 | 40 | const activeRecord = record => 41 | record?.record_state !== 'archived'; 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 | ); 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 | ), 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 | 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 |
{ 319 | setCustomerUuid(customer.uuid); 320 | setLimbUuid(''); 321 | setProjectUuid(''); >> 322 | setSessionUuid(''); 323 | setStep(1); 324 | }} 325 | /> 326 | ))} 327 |
328 | 358 | } 359 | onClick={() => { 360 | setLimbUuid(item.uuid); 361 | setProjectUuid(''); >> 362 | setSessionUuid(''); 363 | setStep(2); 364 | }} 365 | /> 366 | ))} 367 |
368 | 396 | onClick={() => { 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); 419 | 420 | {step === 3 && ( 421 | <> 422 |

>> 423 | Confirm measurement session 424 |

425 | 426 |
427 |

428 | {patient?.full_name} 429 |

439 |

440 |
441 | 442 |
>> 443 | {relatedSessions.map(item => ( 444 | > 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 | } 463 | /> 464 | ))} 465 |
466 | 467 | )} 485 | type="button" 486 | className="btn-primary" 487 | disabled={ 488 | step !== 3 >> 489 | || !sessionUuid 490 | } 491 | onClick={() => 492 | navigate( 493 | `/measurements/${sessionUuid}` 494 | ) 495 | } 496 | > 497 | Begin measurements 498 | 499 | 500 |
501 | 502 | 503 | ------------------------------------------------------------ 3. BUTTONS, INPUTS AND EVENT HANDLERS ------------------------------------------------------------ --- context around line 32 --- 27 | import { 28 | projectService, 29 | } from '@/services/projectService'; 30 | 31 | import { >> 32 | measurementService, 33 | } from '@/services/measurementService'; 34 | 35 | import { 36 | adapters, 37 | } from '@/services/serviceConfig'; 38 | 39 | 40 | const activeRecord = record => --- context around line 33 --- 28 | projectService, 29 | } from '@/services/projectService'; 30 | 31 | import { 32 | measurementService, >> 33 | } from '@/services/measurementService'; 34 | 35 | import { 36 | adapters, 37 | } from '@/services/serviceConfig'; 38 | 39 | 40 | const activeRecord = record => 41 | record?.record_state !== 'archived'; --- context around line 53 --- 48 | detail, 49 | selected, 50 | onClick, 51 | }) { 52 | return ( >> 53 | 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([]); --- context around line 121 --- 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; --- context around line 154 --- 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 | }; --- context around line 227 --- 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', --- context around line 245 --- 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 --- context around line 254 --- 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 |
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 | --- context around line 484 --- 479 | > 480 | 481 | Back 482 | 483 | >> 484 | 483 | 484 | --- context around line 493 --- 488 | step !== 3 489 | || !sessionUuid 490 | } 491 | onClick={() => 492 | navigate( >> 493 | `/measurements/${sessionUuid}` 494 | ) 495 | } 496 | > 497 | Begin measurements 498 | 499 | 500 |
501 |
--- context around line 497 --- 492 | navigate( 493 | `/measurements/${sessionUuid}` 494 | ) 495 | } 496 | > >> 497 | Begin measurements 498 | 499 | 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 | 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 | 483 | 484 | 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.