[codex] Extract light device session engine#459
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR extracts the repeated dose-math logic (mode resolution, body-area fraction, distance scaling, spectrum synthesis, and SAD-lux fallback) from
Confidence Score: 4/5Safe to merge — the refactoring faithfully preserves existing dose math across all three session lifecycle functions, and the new engine module is covered by targeted unit tests. The dose formulas, fallback logic, and mode-resolution paths are all identical to what was inline before. The one notable oddity is a now-redundant mode pre-normalization block in js/light-devices.js — the redundant pre-normalization block at lines 358–360 in updateDeviceSession is worth cleaning up in a follow-on commit. Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller as Caller (UI / timer)
participant LD as light-devices.js
participant Engine as light-device-session-engine.js
participant Win as window.* (synthesize / compute)
Note over Caller,Win: logDeviceSession
Caller->>LD: "logDeviceSession({deviceId, durationMin, ...})"
LD->>Engine: "computeDeviceSessionDoses({device, durationMin, ...})"
Engine->>Engine: resolveDeviceMode(device, mode)
Engine->>Engine: bodyFractionForDeviceSession(...)
Engine->>Engine: deviceDistanceFactor(device, distanceCm)
Engine->>Win: synthesizeDeviceSpectrum(effectiveDevice)
Win-->>Engine: baseSpec
Engine->>Win: "computeChannelDoses({spectrum, ...})"
Win-->>Engine: doses
Engine-->>LD: "{doses, mode, bodyExposureFraction, ...}"
LD-->>Caller: session record
Note over Caller,Win: stopDeviceSession
Caller->>LD: stopDeviceSession(id)
LD->>Engine: "computeDeviceSessionDoses({device, durationMin, ...})"
Engine->>Win: synthesizeDeviceSpectrum / computeChannelDoses
Win-->>Engine: doses
Engine-->>LD: "{doses}"
LD-->>Caller: finalized session
Note over Caller,Win: updateDeviceSession (patch triggers recompute)
Caller->>LD: updateDeviceSession(id, patch)
LD->>Engine: resolveDeviceMode(device, patch.mode)
Engine-->>LD: resolvedMode
LD->>Engine: "computeDeviceSessionDoses({device, ...sess params})"
Engine->>Win: synthesizeDeviceSpectrum / computeChannelDoses
Win-->>Engine: doses
Engine-->>LD: "{doses, mode}"
LD-->>Caller: updated session
Reviews (1): Last reviewed commit: "Extract light device session engine" | Re-trigger Greptile |
| if ((sess.mode === undefined || sess.mode === null) && Array.isArray(device.modes) && device.modes.length > 0) { | ||
| const defaultMode = device.modes.find(m => m.default) || device.modes[0]; | ||
| sess.mode = defaultMode.id; | ||
| } | ||
| const seconds = sess.durationMin * 60; | ||
| const fracByKey = Object.fromEntries((BODY_REGIONS || []).map(r => [r.key, r.fraction])); | ||
| const AREA_FRACTIONS = { face: 0.04, arms: 0.10, torso: 0.13, legs: 0.30, 'whole-body': 0.92, targeted: 0.05 }; | ||
| let area; | ||
| if (Array.isArray(sess.bodyAreas) && sess.bodyAreas.length > 0) { | ||
| area = sess.bodyAreas.reduce((s, k) => s + (fracByKey[k] || 0), 0) || 0.05; | ||
| } else { | ||
| area = AREA_FRACTIONS[sess.bodyArea] ?? 0.10; | ||
| } | ||
| const baseRangeCm = device.recommendedDistanceCm || 15; | ||
| const distFactor = Math.min((baseRangeCm / Math.max(sess.distanceCm || 15, 5)) ** 2, 3.0); | ||
| const eyeMode = sess.eyesProtected ? 'closed-eyes' : 'direct'; | ||
| const synthesizeDeviceSpectrum = window.synthesizeDeviceSpectrum; | ||
| const computeChannelDoses = window.computeChannelDoses; | ||
| const hasPeaks = Array.isArray(device.peakWavelengths) && device.peakWavelengths.length > 0; | ||
| const hasIrradiance = (device.mwPerCm2At15cm || 0) > 0; | ||
| let doses = {}; | ||
| if (synthesizeDeviceSpectrum && computeChannelDoses && hasPeaks && hasIrradiance) { | ||
| const effectiveDevice = window.effectiveDeviceForMode | ||
| ? window.effectiveDeviceForMode(device, sess.mode) | ||
| : device; | ||
| const baseSpec = synthesizeDeviceSpectrum(effectiveDevice); | ||
| const spectrum = { | ||
| wavelengths: baseSpec.wavelengths, | ||
| irradiance: baseSpec.irradiance.map(v => v * distFactor), | ||
| }; | ||
| doses = computeChannelDoses({ | ||
| spectrum, durationMin: sess.durationMin, bodyExposureFraction: area, | ||
| eyeExposure: { mode: eyeMode, durationSec: seconds }, | ||
| }); | ||
| } else { | ||
| const lux = device.lux || 0; | ||
| if (!sess.eyesProtected && lux > 0) doses.circadian = lux * distFactor * seconds / 100; | ||
| sess.mode = resolveDeviceMode(device, sess.mode); | ||
| } |
There was a problem hiding this comment.
Redundant pre-normalization before engine call
The if ((sess.mode === undefined || sess.mode === null)...) block mutates sess.mode to defaultMode.id, but the very next line passes mode: sess.mode to computeDeviceSessionDoses, which calls resolveDeviceMode(device, mode) internally and returns the same defaultMode.id as resolvedMode. sess.mode = resolvedMode then overwrites it again with the identical value. The pre-normalization block is a no-op and could be removed — relying on the engine's returned resolvedMode alone is sufficient (and already handles the legacy null-mode case).
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
js/light-device-session-engine.jslight-devices.js, while centralizing mode resolution, body fraction, distance scaling, spectrum dose, and SAD lux fallback logicAPP_VERSIONto1.8.287Validation
node --check js/light-device-session-engine.jsnode --check js/light-devices.jsnode --check tests/test-light-devices.jsnode tests/test-light-devices.jsnode tests/test-correctness-phase2.jsnode tests/test-light-device-ai-analysis.jsnode tests/test-light-tools.jsgit diff --check./run-tests.sh