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.

bridge/report-format.mjs
import { parseReport } from '../src/ai-contract.ts';

const text = maxLength => ({ type: 'string', minLength: 1, maxLength });
const array = (items, maxItems, minItems = 0) => ({ type: 'array', items, minItems, maxItems });
const list = (max, min = 0) => array(text(1600), max, min);
const record = properties => ({ type: 'object', properties, required: Object.keys(properties), additionalProperties: false });

// Provider-side grammar constraints complement the app's semantic validation.
export const REPORT_SCHEMA = record({
  version: { type: 'integer', enum: [2] }, summary: text(4000), history: text(4000),
  evidence: record({ reported: list(12), visual: list(12), missing: list(12) }),
  assessments: array(record({ concernId: { type: 'integer', minimum: 1 }, assessment: text(2000) }), 50, 1),
  urgency: record({ level: { type: 'string', enum: ['unclear', 'routine', 'soon', 'urgent', 'emergency'] }, reason: text(2000) }),
  possibilities: array(record({ name: text(200), rationale: text(1600), against: text(1600), uncertainty: text(1600) }), 6),
  evaluation: array(record({ item: text(300), rationale: text(1600) }), 8),
  nextSteps: list(8, 1), questions: list(10), clinician: record({ type: text(200), reason: text(1600) }),
  safetyNet: list(6, 1), plainLanguage: record({ gist: text(3000), nextSteps: list(6, 1), seekHelp: list(6, 1) }),
  limitations: list(6, 1),
});

export function reportSchema(hasPatientPhotos) {
  const schema = structuredClone(REPORT_SCHEMA);
  if (!hasPatientPhotos) schema.properties.evidence.properties.visual.maxItems = 0;
  return schema;
}

// Narrow source-absence grammar, not a classifier for medical claims. Unknown
// wording fails closed. In particular, a "no photos" prefix alone is insufficient.
export function isNoPhotoDisclaimer(value) {
  const parts = value.trim().replace(/\.$/, '').split(';').map(part => part.trim());
  const absent = /^no patient[- ](?:photos?|photographs?|images?)(?: were| was| are| is| have been| has been)? (?:attached|provided|supplied|sent|available|reviewed)(?: (?:or|and) (?:attached|provided|supplied|sent|available|reviewed))*$/i;
  if (!absent.test(parts[0])) return false;
  if (parts.length === 1) return true;
  if (parts.length !== 2) return false;
  const bodyMap = /^only (?:schematic )?body-map annotations(?: \(([^()]*)\))? were supplied, which indicate patient-marked location, not examination findings$/i.exec(parts[1]);
  if (!bodyMap) return false;
  const annotation = /^(?:(?:(?:central|left|right|upper|lower|front|back|head|neck|chest|abdomen|pelvis|arm|hand|finger|leg|knee|foot)[ -]){1,6}mark|(?:skin|muscle|muscles|skeleton|organs|mouth) layer|(?:standing|sitting|lying|walking|jogging|custom) pose)$/i;
  return !bodyMap[1] || bodyMap[1].split(',').every(label => annotation.test(label.trim()));
}

function scan(text) {
  const stack = [], candidates = [];
  let previous = '', previousAt = -1;
  for (let i = 0; i < text.length; i++) {
    const c = text[i];
    if (/\s/.test(c)) continue;
    if (c === '"') {
      const start = i++;
      while (i < text.length && text[i] !== '"') { if (text[i] === '\\') i++; i++; }
      if (i >= text.length) return null;
      let after = i + 1; while (/\s/.test(text[after] || '') && after < text.length) after++;
      if (text[after] === ':') {
        let key; try { key = JSON.parse(text.slice(start, i + 1)); } catch { return null; }
        const parent = stack.at(-1);
        if (parent?.type !== '{' || parent.keys.has(key)) return null;
        parent.keys.add(key);
        // Only close an immediate child object before a known root-level field.
        if (stack.length === 2 && previous === ',' && Object.hasOwn(REPORT_SCHEMA.properties, key)) candidates.push({ at: previousAt, key });
      }
    } else if (c === '{' || c === '[') stack.push({ type: c, keys: new Set() });
    else if (c === '}' || c === ']') {
      if (stack.pop()?.type !== (c === '}' ? '{' : '[')) return null;
    }
    previous = c; previousAt = i;
  }
  return { stack, candidates };
}

function unchanged(value, validated) {
  if (value === validated) return true;
  if (!value || !validated || typeof value !== 'object' || typeof validated !== 'object' || Array.isArray(value) !== Array.isArray(validated)) return false;
  const keys = Object.keys(value);
  return keys.length === Object.keys(validated).length && keys.every(key => Object.hasOwn(validated, key) && unchanged(value[key], validated[key]));
}

export function parseReportOutput(content) {
  try { return { value: JSON.parse(content) }; }
  catch (syntaxError) {
    const scanned = scan(content);
    // A missing final root brace can mean truncation. Never append data or repair
    // strings, values, duplicate keys, multiple delimiters or ambiguous structures.
    if (!scanned || scanned.stack.length !== 1 || scanned.stack[0].type !== '{' || scanned.candidates.length > 50) throw syntaxError;
    const matches = [];
    for (const candidate of scanned.candidates) {
      const repaired = content.slice(0, candidate.at) + '}' + content.slice(candidate.at);
      const checked = scan(repaired);
      if (!checked || checked.stack.length) continue;
      try {
        const value = JSON.parse(repaired), report = parseReport(value, true);
        if (unchanged(value, report)) matches.push({ value, formatRecovery: `Inserted one missing closing brace before "${candidate.key}". All report fields validated; no text or values were changed.` });
      } catch { /* This insertion does not produce a complete valid report. */ }
    }
    if (matches.length === 1) return matches[0];
    throw syntaxError;
  }
}