Skip to content

26 refactor health record schema to support unified structure with parent child relationship#47

Draft
sttadic wants to merge 17 commits into
mainfrom
26-refactor-health-record-schema-to-support-unified-structure-with-parent-child-relationship
Draft

26 refactor health record schema to support unified structure with parent child relationship#47
sttadic wants to merge 17 commits into
mainfrom
26-refactor-health-record-schema-to-support-unified-structure-with-parent-child-relationship

Conversation

@sttadic
Copy link
Copy Markdown
Member

@sttadic sttadic commented Oct 18, 2025

Summary

  • Schema Unification: Merged health record and update schemas into a single unified schema
  • Hierarchical Structure: Introduced optional rootId field (null for top-level records) and updates array to track child records
  • Data Integrity: Implemented validation and pre-save hooks to prevent child records from having their own updates array
  • New PATCH Route: Added /updates/:healthRecordId for true partial updates (form edits and conversational corrections)
  • Route Restructuring: Converted update route from PUT to POST (/updates/:parentId) for creating chronological health record snapshots
  • Enhanced Validation: Created Z_HealthRecordPatch schema and extracted date pre-processing logic into reusable preprocessDates() helper
  • Code Quality: Refactored custom validators and route handlers for better consistency and maintainability

Checklist

  • My code is up-to-date with the main branch.
  • My code has no merge conflicts.
  • I have performed a self-review of my code.

…perator and configured .findByIdAndUpdate operation to run validators
…ake it optional and adjust validation checks for symptoms, treatments tried, and medical consultations in customValidators
…e route and clean up validation in customValidators
…ry so crucial instrunctions for outputing json object are preserved
…e helper function for date processing. Remove unused type from healthRecordValidation
@sttadic sttadic self-assigned this Oct 18, 2025
@sttadic sttadic marked this pull request as ready for review October 21, 2025 11:58
Copy link
Copy Markdown
Contributor

@AladinJmila AladinJmila left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice progress, we're moving in the right direction 👌

default: "me",
//required: true,
},
rootId: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we rename this to parentRecordId? rootId could mean anything and can be ambiguous to people that are not familiar with the code or the consumers of our API.

}
next();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we need this hook, since our logic should already prevent children from having an updates array. The schema validator is already safeguarding against this, and we should also have additional checks in our endpoints.

In my opinion, the hook is not a good solution for two reasons:

  1. If, for whatever reason, data does end up in the updates array, it will be silently cleared and lost without us noticing.
  2. This silent data removal could hide a bug in our code that we actually need to fix. The validator is better because it throws an error and makes the issue visible.

.optional()
.default([]),
updates: z.array(Z_HealthRecordUpdate).optional().default([]),
updates: z.array(z.string().regex(/^[0-9a-fA-F]{24}$/, "Invalid ObjectId format")).optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a built-in validator for object ids in mongoose since this is a common check. This can look like this:

updates: z.array(Z_ObjectId).optional(),

Where Z_ObjectId is:

const Z_ObjectId = z.string().refine((val) => Types.ObjectId.isValid(val), {
  message: "Invalid ObjectId format",
});

You then reuse to validate the parentRecordId as well

parentRecordId: Z_ObjectId.optional().nullable(),

rootId: true, // rootId is immutable
updates: true, // updates array is managed by the system
})
.partial();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seemed to be off to me. According to AI, this should be the right implementation, unless you had other reasons for it:

// For HTTP PATCH: Any field can be updated, all are optional, but when provided must be valid
export const Z_HealthRecordPatch = Z_HealthRecord.omit({
  rootId: true,    // Immutable
  updates: true,   // System-managed
}).partial();

symptoms: true,
status: true,
treatmentsTried: true,
medicalConsultations: true,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why we need this any longer. Can't we just use the new Z_HealthRecordPatch instead for all our update operations?

export type HealthRecordType = z.infer<typeof Z_HealthRecord> & {
parentId?: Types.ObjectId | null;
updates?: Types.ObjectId[];
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be, the below, since we need to remove the old entries and replace them with the new ones:

export type HealthRecordType = Omit<z.infer<typeof Z_HealthRecord>, 'parentRecordId' | 'updates'> & {
  parentRecordId?: Types.ObjectId | null; 
  updates?: Types.ObjectId[];         
};

Comment thread src/utils/helpers.ts
if (!conversation) return;

conversation.history = conversation.history.filter((prompt) => prompt.role === "user");
conversation.history = conversation.history.filter((prompt) => prompt.role === "user" || prompt.role === "system");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add system here as well? I was focusing only user input because they are more concise and should have the raw data which is captured by the AI. system prompts are lengthy and would cost us a lot of tokens, we also should not care about old system prompts, we only need to worry about the current one.

@sttadic sttadic marked this pull request as draft November 19, 2025 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor Health Record Schema to Support Unified Structure with Parent-Child Relationship

2 participants