Back to Body Journal
SOURCE / PINNED RELEASE

Made of little things.

Body Journal

Release
e8049f8da4e2…
Author-recorded commit
61c04bf0e8aa…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6c4554844c7

Archive hash verified: 4369d778da046a8b…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/photo-record.ts
import { boundedText, object } from './ai-contract.js';
import type { BodyImage } from './vision-contract.js';

export const MAX_PHOTOS = 4;
export const MAX_PHOTO_CHARS = 65_000;
export interface CasePhoto {
  id: string; name: string; caption: string; takenOn: string; concernId: number | null;
  includeInAi: boolean; dataUrl: string; width: number; height: number; sha256: string;
}
export function parsePhotos(value: unknown, concernIds: number[]): CasePhoto[] {
  if (value === undefined) return [];
  if (!Array.isArray(value) || value.length > MAX_PHOTOS) throw new Error('Too many photos.');
  const seen = new Set<string>();
  return value.map(item => {
    const p = object(item), id = boundedText(p.id, 64);
    if (!/^photo-[a-f0-9]{24}$/.test(id) || seen.has(id)) throw new Error('Invalid photo identity.');
    seen.add(id);
    if (typeof p.includeInAi !== 'boolean' || (p.concernId !== null && !concernIds.includes(Number(p.concernId)))) throw new Error('Invalid photo link.');
    if (p.concernId !== null && (!Number.isInteger(p.concernId) || Number(p.concernId) < 1)) throw new Error('Invalid photo link.');
    const dataUrl = boundedText(p.dataUrl, MAX_PHOTO_CHARS);
    if (!/^data:image\/jpeg;base64,\/9j\/[A-Za-z0-9+/]+={0,2}$/.test(dataUrl) || !/^[a-f0-9]{64}$/.test(String(p.sha256))) throw new Error('Invalid saved photo.');
    for (const n of [p.width, p.height]) if (!Number.isInteger(n) || Number(n) < 1 || Number(n) > 1280) throw new Error('Invalid photo dimensions.');
    const takenOn = boundedText(p.takenOn, 10, true);
    if (takenOn && (!/^\d{4}-\d{2}-\d{2}$/.test(takenOn) || !Number.isFinite(Date.parse(takenOn)) || new Date(takenOn).toISOString().slice(0, 10) !== takenOn)) throw new Error('Invalid photo date.');
    return { id, name: boundedText(p.name, 120), caption: boundedText(p.caption, 600, true), takenOn, concernId: p.concernId as number | null, includeInAi: p.includeInAi, dataUrl, width: Number(p.width), height: Number(p.height), sha256: String(p.sha256) };
  });
}
export function photoMetadata(photos: CasePhoto[]) {
  return photos.filter(p => p.includeInAi).map(p => ({ id: p.id, concernId: p.concernId, patientCaption: p.caption || null, patientReportedDateTaken: p.takenOn || null, width: p.width, height: p.height, sha256: p.sha256, source: 'User-selected photo, resized and JPEG-compressed. Metadata alone is not visual evidence; pixels are available only if attached to this request.' }));
}
export function photoEvidence(photos: CasePhoto[]): BodyImage[] {
  return photos.filter(p => p.includeInAi).map(p => ({ kind: 'patient-photo', attachmentId: p.id, concernId: p.concernId, dataUrl: p.dataUrl, caption: `${p.id}; ${p.concernId === null ? 'whole case' : `concern ${p.concernId}`}; resized copy ${p.width}×${p.height}. Patient-reported date: ${p.takenOn || 'unknown'}. Patient caption: ${p.caption || 'none'}` }));
}