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/concern-groups.ts
import { boundedText, object } from './ai-contract.js';
import type { Concern } from './case.js';

export interface ConcernGroup { id: string; name: string; explanation: string; concernIds: number[] }
export const MAX_GROUPS = 20;
export const GROUP_NAME_LIMIT = 80;
export const GROUP_EXPLANATION_LIMIT = 2000;
export const GROUP_MEANING = 'Patient-suspected relationships, not established causes or clinician conclusions. Grouped concerns may be unrelated; ungrouped concerns are not necessarily unrelated.';

export function parseGroups(value: unknown, concerns: Pick<Concern, 'id'>[]): ConcernGroup[] {
  if (!Array.isArray(value) || value.length > MAX_GROUPS) throw new Error('Invalid observation groups.');
  const seen = new Set<string>(), validIds = new Set(concerns.map(c => c.id));
  return value.map(item => {
    const g = object(item), id = boundedText(g.id, 64);
    if (!/^[a-zA-Z0-9-]+$/.test(id) || seen.has(id)) throw new Error('Invalid group identity.');
    seen.add(id);
    if (!Array.isArray(g.concernIds) || g.concernIds.length > 50 || new Set(g.concernIds).size !== g.concernIds.length || g.concernIds.some(id => typeof id !== 'number' || !validIds.has(id))) throw new Error('Invalid group membership.');
    return { id, name: boundedText(g.name, GROUP_NAME_LIMIT, true), explanation: boundedText(g.explanation, GROUP_EXPLANATION_LIMIT, true), concernIds: [...g.concernIds] as number[] };
  });
}

/** Deleting a concern only removes its links; groups and other observations stay. */
export function unlinkConcern(groups: ConcernGroup[], concernId: number): void {
  for (const group of groups) group.concernIds = group.concernIds.filter(id => id !== concernId);
}

export function groupName(group: ConcernGroup): string { return group.name.trim() || 'Unnamed group'; }