feat(medical): add a medical module - #3949
Conversation
Adds `faker.medical` with plausible, non-clinical healthcare data for tests, demos, and fixtures: specialty, department, condition, symptom, procedure, allergen, bloodType, and a fictitious drugName. Scoped to Faker's plausible-not-real policy per the discussion in faker-js#2309: real diagnosis codes (ICD-10), real medicine names, and correlated patient records are deliberately excluded. drugName() assembles invented brand-style names from morphemes and avoids real WHO INN stems, so it can never resemble a real drug. English locale data; tests, snapshots, and JSDoc included. Ref faker-js#2309
✅ Deploy Preview for fakerjs ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## next #3949 +/- ##
=======================================
Coverage 98.98% 98.98%
=======================================
Files 926 928 +2
Lines 3246 3264 +18
Branches 582 569 -13
=======================================
+ Hits 3213 3231 +18
Misses 29 29
Partials 4 4
🚀 New features to boost your workflow:
|
- add generated docs/.vitepress/api-pages.ts and regenerate the typed en/medical locale index (fixes the Check Code Generation job) - symptom: drop non-symptom trigger/modifier entries (Exercise, Laughing, Lifting, Relief with Movement, Worsening at Night, Tolerance), exact plural duplicates (Headaches, Red Patches, Sleep Disturbances), and entries duplicating the condition list (Anemia, Hyperlipidemia); clarify Frequency/Urgency as Urinary Frequency/Urinary Urgency - specialty: use Allergy and Immunology; add Anesthesiology, Emergency Medicine, Family Medicine, Neurosurgery, Pathology, Plastic Surgery, Radiology - procedure: replace the trademarked Botox Injection with Botulinum Toxin Injection - condition: use a literal ö in Sjögren's Syndrome - drug morphemes: swap pex/zia/se/ga for plex/vex/so/go so no generated name can contain an unfortunate substring; the full 31,500-name space is verified free of real brand names and WHO INN generics
|
Are you familiar with the medical sector? |
Looks like he is the maintainer of https://github.com/rodrigobnogueira/faker-healthcare-provider 👀 |
…tic list Per review feedback: drugName() now draws from a pre-generated drug_name locale array (250 names built once from the morphemes and screened against real brand names, WHO INN generic names, and INN class stems), instead of assembling names at runtime with a retry loop. The four morpheme definition keys are gone and the module reads like every other array-backed method. Also trims the class-level overview to not enumerate every method.
|
Software engineer, not a clinician 😄 — no clinical authority claimed. This module was deliberately scoped so nothing in it depends on clinical judgment — curated vocabulary only, no codes, no real medicines. I was starting a repo to port the python |
The fictitious brand generator could form names containing offensive substrings (256 of the 31,500 possible names): infix na + suffix zia and prefixes ending ra + suffix pex among others. Swap suffixes pex->plex and zia->vex, infixes se->so and ga->go, mirroring the fix already shipped in the TypeScript port (faker-js/faker#3949). Re-verified across the full 31,500-name space: zero offensive substrings (28-term list), zero collisions with shipped INN generics or well-known real brands, forbidden-ending retry trigger unchanged. zh_CN inherits the base generator, so all six locales are covered. Bump 2.3.0 -> 2.3.1.
ST-DDT
left a comment
There was a problem hiding this comment.
Looks good implementation wise.
I have no knowledge in the medical domain.
There was a problem hiding this comment.
Pull request overview
Adds a new faker.medical module to generate plausible, non-clinical healthcare terms (specialties, departments, conditions, symptoms, procedures, allergens, blood types, and fictitious drug names), with English locale data and API docs integration.
Changes:
- Introduces
MedicalModulewith eight methods wired intoFaker. - Adds English locale definitions for the new
medicalcategory plus type definitions and locale generation wiring. - Adds module tests/snapshots and updates API docs navigation/completeness snapshots.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/scripts/apidocs/snapshots/verify-jsdoc-tags.spec.ts.snap | Registers medical module/methods in JSDoc completeness snapshot. |
| test/modules/medical.spec.ts | Adds module-level tests for faker.medical.*. |
| test/modules/snapshots/medical.spec.ts.snap | Adds seeded snapshot outputs for medical. |
| src/modules/medical/module.ts | Implements MedicalModule methods + JSDoc. |
| src/modules/medical/index.ts | Barrel export for the module. |
| src/locales/en/medical/allergen.ts | Adds English allergen vocabulary. |
| src/locales/en/medical/blood_type.ts | Adds blood type vocabulary. |
| src/locales/en/medical/condition.ts | Adds condition vocabulary. |
| src/locales/en/medical/department.ts | Adds department vocabulary. |
| src/locales/en/medical/drug_name.ts | Adds fictitious drug name vocabulary. |
| src/locales/en/medical/procedure.ts | Adds procedure vocabulary. |
| src/locales/en/medical/specialty.ts | Adds specialty vocabulary. |
| src/locales/en/medical/symptom.ts | Adds symptom vocabulary. |
| src/locales/en/medical/index.ts | Aggregates medical locale definitions for en. |
| src/locales/en/index.ts | Wires medical into the en locale definition. |
| src/index.ts | Exports MedicalModule type from the public entrypoint. |
| src/faker.ts | Instantiates/exposes faker.medical. |
| src/definitions/medical.ts | Adds MedicalDefinition type. |
| src/definitions/index.ts | Re-exports MedicalDefinition. |
| src/definitions/definitions.ts | Adds optional medical to LocaleDefinition. |
| scripts/generate-locales.ts | Registers medical for locale generation typing. |
| docs/.vitepress/api-pages.ts | Adds Medical to the API docs sidebar. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
A rescreen of all 250 drug names found four that a reader could look up. The
original screen covered human brand names, WHO INN generics and INN stems,
which is why it missed them:
- Revalor and Orbax are FDA-approved veterinary products, and a name is a
collision whether the real product treats people or animals. Orbaex differs
from Orbax by one inserted letter, which is close enough to remove.
- Lumemox is a moxifloxacin eye drop marketed in India and Kenya, invisible to
a screen that only queries the FDA and EMA registries.
- Nuvizen is a medication-device trademark rather than a medicine, removed
because a reader cannot tell that distinction from the generated value.
The four are now a committed denylist that fails the suite if one returns, and
a second test rejects any name ending in an INN stem, so the constraint the
docs promise is checked rather than asserted.
Also drops Lactose, Fructose and MSG from allergen(). NIAID's food-allergy
guidelines state that lactose intolerance is non-immune and lactose is not an
allergen; fructose malabsorption is metabolic; and the FDA treats MSG as GRAS
with no reproducible immune mechanism. The remaining entries stay real on
purpose, which the method's documentation now explains rather than leaving to
the reader.
MedicalDefinition is exported from the package root, so
`import type { MedicalDefinition } from '@faker-js/faker'` resolves instead of
raising TS2305, and the module overview no longer claims real medicine names
are out of scope while allergen() deliberately returns them.
|
Rescreened all 250 names in d9648c4: four shadowed real products (
|
Sonadex is a real brand: paracetamol/caffeine/aspirin tablets from Sydler Remedies, item 304 of their catalogue, sold at retail in Kenya under ATC N02BG. It is absent from DailyMed, openFDA, the FDA Green Book and EMA, which is the same reason Lumemox survived the first screen — those registries cannot clear a name that is manufactured in India and marketed in East Africa. I diffed the whole of that catalogue against the list; Sonadex was the only hit. The denylist listed Orbax, the real product, but not Orbaex, the name actually removed for shadowing it, so re-adding the generated spelling passed both tests. Both spellings are now listed. The stem list omitted the antacid stem, which the WHO stem book writes as `-ox/-alox`. It is added as `alox` rather than `ox`: WHO assigns no INN on the bare ending, and 20 of the 38 INNs that end in those letters carry an unrelated stem, so matching `ox` would flag eleven names that do not carry it. Twenty further stems are added at the same time — none of them matches any current name, so the documented guarantee is now checked rather than asserted. Also corrects Restless Leg Syndrome to Restless Legs Syndrome, the name NINDS and MedlinePlus use.
brand_drug() built a name from morphemes on every call: 31,500 reachable
names in the base provider, 30,752 more in the zh_CN Chinese path. It
screened each attempt against 16 WHO INN class stems, retried up to 12
times, and then returned the last attempt anyway if all 12 failed, so a
name ending in an INN stem was reachable in principle ('Zolol' is the one
such name in the space).
The retry loop is not the real problem. Nothing in it screened for real
product names, and 31,500 names cannot be screened for them. When the
same morphemes were ported to faker-js (faker-js/faker#3949), a human
screen of a ~250-name sample found five names shadowing real products --
Revalor, Orbaex, Lumemox, Nuvizen, Sonadex, two of them FDA veterinary
drugs -- and all five were reachable from these pools too. Meanwhile the
README promised the names were "not real trademarks" and that "any
resemblance to a real product is coincidental".
You cannot screen 31,500 names. You can screen 250.
scripts/generate_brand_names.py enumerates the morpheme space with no
RNG, rejects any name that ends in a BRAND_FORBIDDEN_ENDINGS stem, is in
REAL_PRODUCT_DENYLIST, contains a term from OFFENSIVE_SUBSTRINGS (the
check behind the 2.3.1 morpheme fix, which had lived in a throwaway
script), or collides with a drug in any of the six catalogues -- and
writes the reviewed survivors to a generated module, sorted, idempotently.
brand_drug() is now random_element(self.brand_drug_names); the retry loop
and its return-the-last-attempt path are gone.
zh_CN gets the same treatment with a weaker, stated guarantee. Its
Chinese half now draws from ZH_BRAND_NAMES, 64 two-character names
screened against a denylist and against every Chinese term the package
ships. Compiling that denylist by inspection turned up real trademarks in
the reachable set -- Novartis, Tylenol, Taikang, Ping An -- and ordinary
words including the first half of "euthanasia", but it took five passes
and each one found more, so the module carries a TODO(review) saying
plainly that no fluent Chinese speaker has read it.
The guarantee is now executable. tests iterate the whole shipped tuple
rather than sampling the generator: every name passes every screen, the
five known collisions are absent by name, the list is non-empty, sorted
and deduplicated, 1000 draws are all members of it, and re-running the
script reproduces both generated files byte for byte.
The morpheme tuples and BRAND_FORBIDDEN_ENDINGS stay: they are the
script's input and part of the public surface.
README, AGENTS.md and CONTRIBUTING.md drop the unfalsifiable claims for
what is actually true -- curated fictional names, screened against a
documented corpus on a stated date, not a trademark search -- and invite
collision reports. AGENTS.md and CONTRIBUTING.md also gain the general
rule this is an instance of: a user-visible generated identifier must
come from a screened, enumerable, committed set, never from a space too
large to screen, and its safety property must be asserted by iterating
the shipped set.
|
FYI: This is planned for early in v11.0 / after the dependency PRs. It may take a few days though. |
|
@rodrigobnogueira This PR was hidden/unreachable yesterday. Do you know what happened? |
Yes, for some reason I got my user suspended from Github yesterday. I opened a ticket and it was restored today. |
Shinigami92
left a comment
There was a problem hiding this comment.
Nice work, the wiring and the generated locale files all look correct. A few things before this can go in.
Drug names
The JSDoc claims the screening covers veterinary and non-US/EU products, but it only catches exact matches. Orbaex was removed for shadowing Orbax while Orbamox stayed, same with Lumemox vs Lumein. These are one letter off a real product, please drop them:
- Solvadex / Nolvadex
- Zolvia / Zolvix (veterinary) and Zovia
- Cetrasen / Cetraben
- Lumein / Lutein
Then either catch near misses in the screening, or reword the JSDoc to claim only what we check. The INN stem part holds by the way, I ran all 245 names against a larger stem list and found no violations.
Allergens
Gluten needs to go. Celiac is autoimmune and NCGS isn't immune mediated, so it's the same case as the lactose and fructose we already exclude, and Wheat is in the list already as the actual allergy.
Aspirin, Ibuprofen, Codeine, Morphine, Contrast Dye and Sulfites aren't immune mediated either. I'd keep those since every EHR records them, but then the JSDoc sentence about excluding non-immune reactions no longer matches the data.
Duplicate values across methods
specialty()anddepartment()both return "Oncology", "Pediatrics" and "Radiology". The department list qualifies everything else ("Cardiology Unit"), so those three should too.condition()andsymptom()overlap on "Hearing Loss" and "Depression".procedure()has "Gallbladder Removal" next to "Cholecystectomy", and "Dialysis" next to "Hemodialysis".
Symptoms
"Negative Symptoms" and "Withdrawal Symptoms" are category labels, so the output reads Symptom: Negative Symptoms. "Hypertension" is asymptomatic by definition, and "Hypoalbuminemia" and "Proteinuria" are lab values no patient reports. Also "Color Fading" and "Fading Colors" are both in there.
Overview
The ### Overview block only links drugName() and allergen(). The other modules link all of their methods (book, music, animal, food), which is what builds the method index on the API page. Could you add the remaining six?
|
Overview doesn't necessarily have to mention every method. It should guide you to the common commonly used/important ones. |
Nine more drug names read as a real product a letter or two away, which an exact-match screen cannot see: Solvadex/Nolvadex, Zolnodex/Zoladex, Zolvia/Zolvix, Cetrasen/Cetraben, Fendaol/Fendall, Lumein/Lutein, Sonaex/Sonadex, Uvelmox/Velamox, and Orbamox, which shadows Orbax and reads as an amoxicillin product besides. Sonaex is the sharpest of them: one deletion from Sonadex, a product this branch had already removed. The spec now denylists the real products and the shadowing spellings separately, because listing only one side lets the other back in. Sixteen further pairs sit within a single edit of each other -- Advaen and Advazen, Zentacor and Zentavor, and so on. That is a property of recombining morphemes rather than a collision with anything real, so they stay. The JSDoc described a screen the suite does not run. It now says the screen is a manual step and that the tests lock in what it caught, which is what they do. allergen() drops Gluten: a record files it as an intolerance, and its IgE counterpart is already listed as Wheat. The mechanism sentence goes with it -- Aspirin, Codeine and Sulfites are pharmacological rather than immune and are kept on purpose, so a rule about immune mediation was never true of this list. No value is returned by two different methods any more. Hearing Loss leaves symptom.ts, where Difficulty Hearing already carries it; Depression stays as the symptom and condition.ts takes Major Depressive Disorder, matching its own Anxiety Disorder and Bipolar Disorder. Raynaud's Phenomenon moves to condition.ts. Oncology, Pediatrics and Radiology become Oncology Unit, Pediatric Ward and Radiology Department, the idiom the rest of the file uses. procedure() drops Gallbladder Removal for Cholecystectomy, and bare Dialysis, which sat beside both Hemodialysis and Peritoneal Dialysis. symptom() loses 32 entries: umbrella labels that read as categories (Gastrointestinal Issues, Vision Problems, Negative Symptoms), lab values no patient reports (Proteinuria, Hypoalbuminemia, Bone Loss), Hypertension and High Blood Pressure, asymptomatic and already in condition.ts as Essential Hypertension, and pairs saying the same thing twice (Rapid Heartbeat kept over Rapid Heart Rate and Rapid Pulse, Rash over Skin Rash). The module overview points at the methods a reader starts with rather than naming every category in turn.
Description
Adds a new
faker.medicalmodule — the request in #2309. It generates plausible, non-clinical healthcare data for tests, demos, and fixtures:specialty()'Cardiology'department()'Emergency Department'condition()'Type 2 Diabetes'(name only, no code)symptom()'Shortness of Breath'procedure()'Appendectomy'allergen()'Penicillin'bloodType()'O+'drugName()'Zolpraxen'(invented, screened)Scoped to Faker's "plausible, not real" policy
Following the discussion in #2309 (thanks @ST-DDT / @matthewmayer), I deliberately excluded:
drugName()draws from a static list of 245 invented names, pre-generated from neutral morphemes and screened against WHO INN generics and INN class stems (e.g.-statin,-pril), human brand names including ones marketed only outside the US and EU, and veterinary products (per @matthewmayer's suggestion to pre-generate rather than retry at runtime). Five names were removed after a second screen —RevalorandOrbaexshadowed FDA veterinary products,Lumemoxa moxifloxacin eye drop sold in India and Kenya,Nuvizena medication-device trademark, andSonadexa further collision caught in the 11 August rescreen — and all five are now a denylist in the spec, alongside a test rejecting any name that ends in an INN stem, so the guarantee is enforced rather than described.allergen()— the allergen vocabulary intentionally names real substances (foods, environmental triggers, and medicines such as penicillin), drawn from commonly available public lists of frequent allergens: an allergen field describes what a person reacts to, so real names make the data more meaningful there. The fictitious rule applies to generated medication names, not to this descriptive vocabulary.Lactose,FructoseandMSGhave been dropped, since the reactions to those are not immune-mediated and NIAID's guidelines say so explicitly for lactose.Notes
@example,@since 10.6.0) included;ts-check,lint,test, andbuildare green locally.Happy to adjust scope or naming (
medicalvshealth) — feedback welcome.