@johnhenry/graceholder 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +492 -2
  3. package/STABILITY.md +24 -0
  4. package/package.json +126 -4
  5. package/src/adapters/apollo.d.ts +13 -0
  6. package/src/adapters/apollo.js +48 -0
  7. package/src/adapters/fetch.d.ts +15 -0
  8. package/src/adapters/fetch.js +150 -0
  9. package/src/adapters/shared.d.ts +14 -0
  10. package/src/adapters/shared.js +65 -0
  11. package/src/adapters/urql.d.ts +8 -0
  12. package/src/adapters/urql.js +53 -0
  13. package/src/anchor.js +13 -0
  14. package/src/args.js +41 -0
  15. package/src/bulk/csv.js +118 -0
  16. package/src/bulk/ndjson.js +10 -0
  17. package/src/bulk/plan.js +195 -0
  18. package/src/bulk/shape.js +67 -0
  19. package/src/bulk/source.js +24 -0
  20. package/src/bulk/sql.js +366 -0
  21. package/src/bulk/stream.js +55 -0
  22. package/src/cursor.js +25 -0
  23. package/src/dbg.d.ts +720 -0
  24. package/src/detmath.js +74 -0
  25. package/src/directives.js +113 -0
  26. package/src/dist.js +90 -0
  27. package/src/errors.js +17 -0
  28. package/src/execute.js +577 -0
  29. package/src/explain.js +473 -0
  30. package/src/fast-check.d.ts +31 -0
  31. package/src/fast-check.js +58 -0
  32. package/src/generate.js +84 -0
  33. package/src/generator-types.d.ts +90 -0
  34. package/src/generators.js +361 -0
  35. package/src/image.js +55 -0
  36. package/src/index.d.ts +507 -0
  37. package/src/index.js +62 -0
  38. package/src/infer.js +888 -0
  39. package/src/key.js +30 -0
  40. package/src/learn.js +397 -0
  41. package/src/naming.js +74 -0
  42. package/src/pack-registry.js +59 -0
  43. package/src/packs/PROVENANCE.md +26 -0
  44. package/src/packs/commerce.d.ts +4 -0
  45. package/src/packs/commerce.js +83 -0
  46. package/src/packs/finance.d.ts +4 -0
  47. package/src/packs/finance.js +180 -0
  48. package/src/packs/geo.d.ts +4 -0
  49. package/src/packs/geo.js +227 -0
  50. package/src/packs/health.d.ts +4 -0
  51. package/src/packs/health.js +76 -0
  52. package/src/packs/index.d.ts +7 -0
  53. package/src/packs/index.js +9 -0
  54. package/src/rng.js +89 -0
  55. package/src/rules.js +228 -0
  56. package/src/scenario.js +120 -0
  57. package/src/schema.js +148 -0
  58. package/src/spec.js +235 -0
  59. package/src/suggest.js +138 -0
  60. package/src/typed.d.ts +723 -0
  61. package/src/vocab/PROVENANCE.md +12 -0
  62. package/src/vocab/en.js +106 -0
  63. package/src/world.js +461 -0
  64. package/src/xxh64.js +87 -0
@@ -0,0 +1,83 @@
1
+ // Commerce pack: products, categories and brands that agree with each other. One persona slot
2
+ // ("category") picks a category; the product noun, the SKU prefix and the price band all come from
3
+ // it, so "Trail Running Shoes" is in Footwear at a footwear price. Brands are invented; any
4
+ // resemblance to a real trademark is coincidental. FROZEN at 1.0.0.
5
+
6
+ const freeze = (o) => {
7
+ if (o !== null && typeof o === "object") for (const v of Object.values(o)) freeze(v);
8
+ return Object.freeze(o);
9
+ };
10
+
11
+ // [category, SKU prefix, min price, max price, nouns]
12
+ const commerceCategories = [
13
+ ["Home & Kitchen", "HOM", 6, 180, ["Kettle", "Cutting Board", "Casserole Dish", "Tea Towel Set", "Storage Jar", "Mixing Bowl", "Table Lamp", "Throw Blanket"]],
14
+ ["Outdoors", "OUT", 15, 320, ["Daypack", "Camp Stove", "Sleeping Bag", "Headlamp", "Trekking Poles", "Water Bottle", "Hammock", "Rain Shell"]],
15
+ ["Electronics", "ELE", 12, 650, ["Bluetooth Speaker", "Desk Fan", "Power Bank", "Webcam", "Mechanical Keyboard", "Wireless Mouse", "Phone Stand", "USB Hub"]],
16
+ ["Apparel", "APP", 10, 140, ["Cotton T-Shirt", "Wool Sweater", "Canvas Jacket", "Denim Shorts", "Linen Shirt", "Knit Scarf", "Fleece Vest", "Rain Poncho"]],
17
+ ["Footwear", "FTW", 25, 220, ["Trail Running Shoes", "Leather Boots", "Canvas Sneakers", "Slip-On Loafers", "Hiking Sandals", "Wool Slippers", "Rain Boots", "Court Shoes"]],
18
+ ["Toys & Games", "TOY", 5, 90, ["Wooden Puzzle", "Card Game", "Building Blocks", "Board Game", "Kite", "Plush Bear", "Marble Run", "Dice Set"]],
19
+ ["Books & Stationery", "BKS", 3, 45, ["Hardcover Notebook", "Fountain Pen", "Sketchbook", "Desk Calendar", "Sticky Notes", "Pencil Set", "Atlas", "Cookbook"]],
20
+ ["Beauty & Personal Care", "BEA", 4, 85, ["Hand Cream", "Bath Salts", "Shaving Brush", "Lip Balm", "Face Mask", "Hair Brush", "Body Lotion", "Soap Bar"]],
21
+ ["Pet Supplies", "PET", 4, 110, ["Dog Leash", "Cat Tree", "Chew Toy", "Pet Bowl", "Travel Carrier", "Grooming Brush", "Bird Feeder", "Fish Tank Filter"]],
22
+ ["Garden", "GAR", 5, 260, ["Watering Can", "Pruning Shears", "Raised Planter", "Seed Starter Kit", "Garden Gloves", "Compost Bin", "Trellis", "Hose Reel"]],
23
+ ["Sports & Fitness", "SPT", 8, 300, ["Yoga Mat", "Resistance Bands", "Jump Rope", "Foam Roller", "Dumbbell Pair", "Tennis Racket", "Water Flask", "Gym Bag"]],
24
+ ["Office", "OFF", 6, 420, ["Standing Desk", "Desk Organizer", "Monitor Arm", "Task Chair", "Label Maker", "Paper Shredder", "Whiteboard", "Cable Tray"]],
25
+ ];
26
+
27
+ const commerceBrands = [
28
+ "Quillon", "Brambleton", "Tidewell", "Ferngrove", "Oakhaven", "Kestrelle", "Marrowby", "Lumenfield", "Harrowgate", "Pennywick",
29
+ "Alderbrook", "Stonecroft", "Wrenfield", "Cinderlane", "Moorhaven", "Fallowmere", "Birchwell", "Thistledown", "Larkspur & Vale", "Copperfield Co",
30
+ "Northmere", "Ashgrove", "Tamsin & Rowe", "Halcyon Street", "Briarwood Supply", "Gullwing", "Saltmarsh", "Redfern Goods", "Ember & Oak", "Kingfisher Row",
31
+ "Windermoor", "Hollowell", "Sparrowhill", "Cloverdale Works", "Ironbridge Trading", "Maplecrest", "Dunmore & Finch", "Rushlight", "Elmstead", "Foxtail Provisions",
32
+ ];
33
+
34
+ const commerceAdjectives = [
35
+ "Classic", "Everyday", "Compact", "Heritage", "Travel", "Studio", "Field", "Weekend", "Signature", "Essential",
36
+ "Artisan", "Lightweight", "Heavy-Duty", "Pocket", "Family-Size", "Limited", "Natural", "Recycled", "Foldable", "Insulated",
37
+ ];
38
+
39
+ const ENDING = [[0, 3], [1, 4], [2, 2], [3, 3]];
40
+
41
+ export const commerce = freeze({
42
+ name: "commerce",
43
+ version: 1,
44
+ vocab: { commerceCategories, commerceBrands, commerceAdjectives },
45
+ types: { "commerce.price": "Float" },
46
+ rules({ tokens, first, last, kind }) {
47
+ if (last === "category" || last === "department") return { sdl: "String", gen: "commerce.category" };
48
+ if (last === "brand" || last === "manufacturer") return { sdl: "String", gen: "commerce.brand" };
49
+ if (last === "sku") return { sdl: "String", gen: "commerce.sku" };
50
+ if (last === "price" && tokens.length === 1) return { sdl: "Float", gen: "commerce.price" };
51
+ if (kind === "product" && (last === "name" || last === "title") && tokens.length === 1) return { sdl: "String", gen: "commerce.product" };
52
+ if (tokens.join(" ") === "product name") return { sdl: "String", gen: "commerce.product" };
53
+ return null;
54
+ },
55
+ generators(V) {
56
+ const rows = V.commerceCategories.map(([name, sku, min, max, nouns]) => ({ name, sku, min, max, nouns }));
57
+ const row = (ctx) => ctx.slot("category").pick(rows);
58
+ return {
59
+ "commerce.category": (ctx) => row(ctx).name,
60
+ "commerce.brand": (ctx) => ctx.slot("brand").pick(V.commerceBrands),
61
+ "commerce.product": (ctx) => {
62
+ const r = row(ctx);
63
+ return `${ctx.slot("brand").pick(V.commerceBrands)} ${ctx.slot("adj").pick(V.commerceAdjectives)} ${ctx.slot("noun").pick(r.nouns)}`;
64
+ },
65
+ "commerce.sku": (ctx) => {
66
+ const s = ctx.slot("skuNum");
67
+ return `${row(ctx).sku}-${String(s.int(0, 99999)).padStart(5, "0")}`;
68
+ },
69
+ // whole cents inside the category's price band, with the same weighted endings as `money`
70
+ "commerce.price": ({ stream: s, args, slot }) => {
71
+ const r = slot("category").pick(rows);
72
+ const min = args.min ?? r.min;
73
+ const max = args.max ?? r.max;
74
+ const dollars = s.int(Math.ceil(min), Math.floor(max));
75
+ const e = s.weighted(ENDING);
76
+ const cents = e === 0 ? 0 : e === 1 ? 99 : e === 2 ? 49 : s.int(0, 99);
77
+ return Math.min(max * 100, Math.max(min * 100, dollars * 100 + cents)) / 100;
78
+ },
79
+ };
80
+ },
81
+ });
82
+
83
+ export default commerce;
@@ -0,0 +1,4 @@
1
+ import type { Pack } from "../index.js";
2
+ /** The finance pack. Import from "@johnhenry/graceholder/packs/finance". */
3
+ export declare const finance: Pack;
4
+ export default finance;
@@ -0,0 +1,180 @@
1
+ // Finance pack: account and transaction vocabulary, currencies, and identifiers whose check
2
+ // digits are valid: IBAN (ISO 13616 mod-97), payment card numbers (Luhn) and US ABA routing
3
+ // numbers (3-7-1 weights). The digits themselves are random: the IBAN's national bank code and
4
+ // national check part are NOT validated by the country's own scheme (FR RIB key, IT CIN, ES DC), and a
5
+ // card or account number may coincide with a real one by chance. Never use these to test payments
6
+ // against real systems. FROZEN at 1.0.0.
7
+
8
+ const freeze = (o) => {
9
+ if (o !== null && typeof o === "object") for (const v of Object.values(o)) freeze(v);
10
+ return Object.freeze(o);
11
+ };
12
+
13
+ // [code, name, symbol, minor units]
14
+ const financeCurrencies = [
15
+ ["USD", "US Dollar", "$", 2], ["EUR", "Euro", "€", 2], ["GBP", "Pound Sterling", "£", 2], ["CHF", "Swiss Franc", "CHF", 2],
16
+ ["SEK", "Swedish Krona", "kr", 2], ["NOK", "Norwegian Krone", "kr", 2], ["PLN", "Polish Zloty", "zł", 2], ["JPY", "Japanese Yen", "¥", 0],
17
+ ["CAD", "Canadian Dollar", "$", 2], ["AUD", "Australian Dollar", "$", 2], ["NZD", "New Zealand Dollar", "$", 2], ["BRL", "Brazilian Real", "R$", 2],
18
+ ["MXN", "Mexican Peso", "$", 2], ["INR", "Indian Rupee", "\u20b9", 2], ["NGN", "Nigerian Naira", "\u20a6", 2], ["ZAR", "South African Rand", "R", 2],
19
+ ];
20
+
21
+ // currency by country code, for the countries of the geo pack (used when the geo pack is also enabled)
22
+ const financeCountryCurrency = [
23
+ ["US", "USD"], ["CA", "CAD"], ["GB", "GBP"], ["DE", "EUR"], ["FR", "EUR"], ["ES", "EUR"], ["IT", "EUR"], ["NL", "EUR"], ["SE", "SEK"],
24
+ ["CH", "CHF"], ["AU", "AUD"], ["NZ", "NZD"], ["JP", "JPY"], ["BR", "BRL"], ["MX", "MXN"], ["IN", "INR"], ["NG", "NGN"], ["ZA", "ZAR"],
25
+ ];
26
+
27
+ // IBAN countries: [country code, currency, BBAN layout]; layout "8n,10n" = 8 digits then 10 digits,
28
+ // "4a" = 4 letters, "11c" = 11 letters or digits. Lengths follow ISO 13616 / the SWIFT registry.
29
+ const financeIbanCountries = [
30
+ ["DE", "EUR", "8n,10n"], ["GB", "GBP", "4a,6n,8n"], ["FR", "EUR", "5n,5n,11c,2n"], ["ES", "EUR", "4n,4n,2n,10n"],
31
+ ["IT", "EUR", "1a,5n,5n,12c"], ["NL", "EUR", "4a,10n"], ["BE", "EUR", "3n,7n,2n"], ["CH", "CHF", "5n,12c"],
32
+ ["SE", "SEK", "20n"], ["AT", "EUR", "5n,11n"], ["IE", "EUR", "4a,6n,8n"], ["PL", "PLN", "8n,16n"],
33
+ ["PT", "EUR", "4n,4n,11n,2n"], ["NO", "NOK", "4n,6n,1n"],
34
+ ];
35
+
36
+ const financeAccountTypes = ["Checking", "Savings", "Money Market", "Credit Card", "Line of Credit", "Certificate of Deposit", "Business Checking", "Joint Account"];
37
+
38
+ // [transaction type, direction] where direction is -1 for money out and 1 for money in
39
+ const financeTransactionTypes = [
40
+ ["Card Purchase", -1], ["ATM Withdrawal", -1], ["Direct Deposit", 1], ["Wire Transfer", -1], ["Wire Transfer Received", 1], ["Standing Order", -1],
41
+ ["Direct Debit", -1], ["Refund", 1], ["Interest Payment", 1], ["Account Fee", -1], ["Cheque Deposit", 1], ["Peer Transfer", -1],
42
+ ];
43
+
44
+ const financeMerchants = [
45
+ "Marlow Grocers", "Pinecrest Fuel", "Lanternside Cafe", "Harborline Transit", "Westgate Pharmacy", "Brightwater Utilities", "Corner Bakehouse",
46
+ "Summit Hardware", "Riverbend Books", "Oakmont Cinema", "Greenleaf Market", "Foxglove Florist", "Ironclad Gym", "Seabright Laundry",
47
+ "Millstone Pizza", "Lakeview Parking", "Tallow & Wick", "Dovetail Furniture", "Crescent Cleaners", "Pennant Sports",
48
+ "Meadowbank Dental", "Larder & Lime", "Kestrel Telecom", "Bluepine Insurance", "Anchor Hardware", "Birchline Optics",
49
+ "Copperkettle Diner", "Sundial Travel", "Halfmoon Records", "Wickerwood Gifts",
50
+ ];
51
+
52
+ const LOWER = "abcdefghijklmnopqrstuvwxyz";
53
+ const UPPER = LOWER.toUpperCase();
54
+ const ALNUM = UPPER + "0123456789";
55
+
56
+ /** Random characters for a layout like "5n,5n,11c,2n". */
57
+ function bban(layout, s) {
58
+ let out = "";
59
+ for (const part of layout.split(",")) {
60
+ const n = Number.parseInt(part, 10);
61
+ const kind = part[part.length - 1];
62
+ for (let i = 0; i < n; i++) out += kind === "n" ? s.int(0, 9) : kind === "a" ? UPPER[s.int(0, 25)] : ALNUM[s.int(0, ALNUM.length - 1)];
63
+ }
64
+ return out;
65
+ }
66
+
67
+ /** ISO 7064 mod 97-10 over a string of digits and letters (A=10 ... Z=35), without big integers. */
68
+ export function mod97(text) {
69
+ let rem = 0;
70
+ for (const ch of text) {
71
+ const v = ch >= "0" && ch <= "9" ? ch.charCodeAt(0) - 48 : ch.charCodeAt(0) - 55;
72
+ rem = (v > 9 ? rem * 100 + v : rem * 10 + v) % 97;
73
+ }
74
+ return rem;
75
+ }
76
+
77
+ /** A structurally valid IBAN: country, two check digits so that mod97(rearranged) is 1, then the BBAN. */
78
+ export function makeIban(country, body) {
79
+ const check = 98 - mod97(`${body}${country}00`);
80
+ return `${country}${String(check).padStart(2, "0")}${body}`;
81
+ }
82
+
83
+ /** Luhn check digit for a string of digits. */
84
+ export function luhnDigit(partial) {
85
+ let sum = 0;
86
+ for (let i = partial.length - 1, double = true; i >= 0; i--, double = !double) {
87
+ let d = partial.charCodeAt(i) - 48;
88
+ if (double) {
89
+ d *= 2;
90
+ if (d > 9) d -= 9;
91
+ }
92
+ sum += d;
93
+ }
94
+ return (10 - (sum % 10)) % 10;
95
+ }
96
+
97
+ // [network, prefixes, length]
98
+ const CARDS = [["visa", ["4"], 16], ["mastercard", ["51", "52", "53", "54", "55"], 16], ["amex", ["34", "37"], 15]];
99
+
100
+ export const finance = freeze({
101
+ name: "finance",
102
+ version: 1,
103
+ vocab: { financeCountryCurrency, financeCurrencies, financeIbanCountries, financeAccountTypes, financeTransactionTypes, financeMerchants },
104
+ rules({ tokens, last }) {
105
+ const key = tokens.join(" ");
106
+ if (last === "iban") return { sdl: "String", gen: "finance.iban" };
107
+ if (last === "bic" || last === "swift") return { sdl: "String", gen: "finance.bic" };
108
+ if (key === "card number" || key === "credit card" || key === "credit card number" || key === "cc number" || key === "pan") return { sdl: "String", gen: "finance.cardNumber" };
109
+ if (key === "routing number" || key === "aba" || key === "routing") return { sdl: "String", gen: "finance.routingNumber" };
110
+ if (key === "account number" || key === "account no") return { sdl: "String", gen: "finance.accountNumber" };
111
+ if (key === "account type") return { sdl: "String", gen: "finance.accountType" };
112
+ if (key === "transaction type" || key === "txn type") return { sdl: "String", gen: "finance.transactionType" };
113
+ if (last === "merchant" || key === "merchant name" || key === "payee") return { sdl: "String", gen: "finance.merchant" };
114
+ if (key === "currency" || key === "currency code") return { sdl: "String", gen: "finance.currency" };
115
+ if (key === "currency name") return { sdl: "String", gen: "finance.currencyName" };
116
+ if (key === "currency symbol") return { sdl: "String", gen: "finance.currencySymbol" };
117
+ return null;
118
+ },
119
+ generators(V, base) {
120
+ const ccy = new Map(V.financeCurrencies.map((c) => [c[0], c]));
121
+ const byCountry = new Map(V.financeCountryCurrency);
122
+ // With the geo pack enabled, the entity's country is its place's country (a persona slot shared
123
+ // with every place field), so its IBAN country and currency agree with its address. Otherwise one
124
+ // "fcountry" slot per entity keeps the IBAN country and the currency consistent with each other.
125
+ const geoCountry = (ctx) => base["geo.countryCode"]?.(ctx);
126
+ const home = (ctx) => {
127
+ const cc = geoCountry(ctx);
128
+ return (cc && V.financeIbanCountries.find((r) => r[0] === cc)) || ctx.slot("fcountry").pick(V.financeIbanCountries);
129
+ };
130
+ const currencyCode = (ctx) => {
131
+ const cc = geoCountry(ctx);
132
+ return (cc && byCountry.get(cc)) || home(ctx)[1];
133
+ };
134
+ return {
135
+ "finance.iban": (ctx) => {
136
+ const [cc, , layout] = home(ctx);
137
+ return makeIban(cc, bban(layout, ctx.slot("ibanDigits")));
138
+ },
139
+ "finance.bic": (ctx) => {
140
+ const s = ctx.slot("bic");
141
+ const [cc] = home(ctx);
142
+ let bank = "";
143
+ for (let i = 0; i < 4; i++) bank += UPPER[s.int(0, 25)];
144
+ return `${bank}${cc}${ALNUM[s.int(0, 35)]}${ALNUM[s.int(0, 35)]}`;
145
+ },
146
+ "finance.cardNumber": (ctx) => {
147
+ const s = ctx.slot("card");
148
+ const [, prefixes, len] = s.pick(CARDS);
149
+ let digits = s.pick(prefixes);
150
+ while (digits.length < len - 1) digits += s.int(0, 9);
151
+ return digits + luhnDigit(digits);
152
+ },
153
+ "finance.routingNumber": (ctx) => {
154
+ const s = ctx.slot("routing");
155
+ // first two digits: Federal Reserve routing symbols 01-12 (the range of ordinary banks)
156
+ let d = String(s.int(1, 12)).padStart(2, "0");
157
+ while (d.length < 8) d += s.int(0, 9);
158
+ const w = [3, 7, 1, 3, 7, 1, 3, 7];
159
+ let sum = 0;
160
+ for (let i = 0; i < 8; i++) sum += (d.charCodeAt(i) - 48) * w[i];
161
+ return d + ((10 - (sum % 10)) % 10);
162
+ },
163
+ "finance.accountNumber": (ctx) => {
164
+ const s = ctx.slot("acct");
165
+ let out = "";
166
+ const n = s.int(8, 12);
167
+ for (let i = 0; i < n; i++) out += s.int(0, 9);
168
+ return out;
169
+ },
170
+ "finance.accountType": (ctx) => ctx.slot("accountType").pick(V.financeAccountTypes),
171
+ "finance.transactionType": (ctx) => ctx.slot("txnType").pick(V.financeTransactionTypes)[0],
172
+ "finance.merchant": (ctx) => ctx.slot("merchant").pick(V.financeMerchants),
173
+ "finance.currency": (ctx) => currencyCode(ctx),
174
+ "finance.currencyName": (ctx) => ccy.get(currencyCode(ctx))[1],
175
+ "finance.currencySymbol": (ctx) => ccy.get(currencyCode(ctx))[2],
176
+ };
177
+ },
178
+ });
179
+
180
+ export default finance;
@@ -0,0 +1,4 @@
1
+ import type { Pack } from "../index.js";
2
+ /** The geo pack. Import from "@johnhenry/graceholder/packs/geo". */
3
+ export declare const geo: Pack;
4
+ export default geo;
@@ -0,0 +1,227 @@
1
+ // Geo pack: coherent place data. One persona slot ("place") selects a real city; the city's
2
+ // region, country, coordinates, time zone, postal prefix and area code all come from that row, so
3
+ // every field of an entity agrees. Hand-entered facts (see src/packs/PROVENANCE.md). FROZEN at 1.0.0.
4
+ //
5
+ // Coordinates are approximate city centres (about 0.01 degree); they are placeholders, not for navigation.
6
+ // Phone numbers use the right country code and shape. Reserved fictional ranges are used for the
7
+ // US and Canada (555-01xx), the United Kingdom (Ofcom drama ranges) and Australia (5550 xxxx); for
8
+ // every other country the numbers are well-formed but NOT guaranteed to be unassigned.
9
+
10
+ const freeze = (o) => {
11
+ if (o !== null && typeof o === "object") for (const v of Object.values(o)) freeze(v);
12
+ return Object.freeze(o);
13
+ };
14
+
15
+ // [code, name, calling code, postal suffix pattern, phone template ({ac} = the city's area code), address style]
16
+ // postal = the city's prefix + the suffix pattern ("#" digit, "A" letter, anything else literal).
17
+ // address style "us": street, city, REGION postal. "eu": street, postal city.
18
+ const geoCountries = [
19
+ ["US", "United States", "1", "##", "+1 {ac}-555-01##", "us"],
20
+ ["CA", "Canada", "1", " #A#", "+1 {ac}-555-01##", "us"],
21
+ ["GB", "United Kingdom", "44", " #AA", "+44 20 7946 0###", "eu"],
22
+ ["DE", "Germany", "49", "###", "+49 {ac} ######", "eu"],
23
+ ["FR", "France", "33", "##", "+33 {ac} ## ## ## ##", "eu"],
24
+ ["ES", "Spain", "34", "###", "+34 {ac}# ### ###", "eu"],
25
+ ["IT", "Italy", "39", "###", "+39 {ac} ### ####", "eu"],
26
+ ["NL", "Netherlands", "31", "## AA", "+31 {ac} ### ####", "eu"],
27
+ ["SE", "Sweden", "46", "# ##", "+46 {ac} ### ## ##", "eu"],
28
+ ["CH", "Switzerland", "41", "##", "+41 {ac} ### ## ##", "eu"],
29
+ ["AU", "Australia", "61", "##", "+61 {ac} 5550 ####", "us"],
30
+ ["NZ", "New Zealand", "64", "##", "+64 {ac} ### ####", "us"],
31
+ ["JP", "Japan", "81", "-####", "+81 {ac}-####-####", "eu"],
32
+ ["BR", "Brazil", "55", "###-###", "+55 {ac} ####-####", "eu"],
33
+ ["MX", "Mexico", "52", "###", "+52 {ac} #### ####", "eu"],
34
+ ["IN", "India", "91", "###", "+91 {ac} #### ####", "eu"],
35
+ ["NG", "Nigeria", "234", "###", "+234 {ac} ### ####", "eu"],
36
+ ["ZA", "South Africa", "27", "##", "+27 {ac} ### ####", "eu"],
37
+ ];
38
+
39
+ // [city, region, region code, country code, latitude, longitude, IANA time zone, postal prefix, area code, phone template override?]
40
+ const geoCities = [
41
+ ["New York", "New York", "NY", "US", 40.71, -74.01, "America/New_York", "100", "212"],
42
+ ["Los Angeles", "California", "CA", "US", 34.05, -118.24, "America/Los_Angeles", "900", "213"],
43
+ ["Chicago", "Illinois", "IL", "US", 41.88, -87.63, "America/Chicago", "606", "312"],
44
+ ["Houston", "Texas", "TX", "US", 29.76, -95.37, "America/Chicago", "770", "713"],
45
+ ["Phoenix", "Arizona", "AZ", "US", 33.45, -112.07, "America/Phoenix", "850", "602"],
46
+ ["Philadelphia", "Pennsylvania", "PA", "US", 39.95, -75.17, "America/New_York", "191", "215"],
47
+ ["San Antonio", "Texas", "TX", "US", 29.42, -98.49, "America/Chicago", "782", "210"],
48
+ ["San Diego", "California", "CA", "US", 32.72, -117.16, "America/Los_Angeles", "921", "619"],
49
+ ["Dallas", "Texas", "TX", "US", 32.78, -96.8, "America/Chicago", "752", "214"],
50
+ ["San Jose", "California", "CA", "US", 37.34, -121.89, "America/Los_Angeles", "951", "408"],
51
+ ["Austin", "Texas", "TX", "US", 30.27, -97.74, "America/Chicago", "787", "512"],
52
+ ["Seattle", "Washington", "WA", "US", 47.61, -122.33, "America/Los_Angeles", "981", "206"],
53
+ ["Denver", "Colorado", "CO", "US", 39.74, -104.99, "America/Denver", "802", "303"],
54
+ ["Boston", "Massachusetts", "MA", "US", 42.36, -71.06, "America/New_York", "021", "617"],
55
+ ["Atlanta", "Georgia", "GA", "US", 33.75, -84.39, "America/New_York", "303", "404"],
56
+ ["Miami", "Florida", "FL", "US", 25.76, -80.19, "America/New_York", "331", "305"],
57
+ ["Portland", "Oregon", "OR", "US", 45.52, -122.68, "America/Los_Angeles", "972", "503"],
58
+ ["Minneapolis", "Minnesota", "MN", "US", 44.98, -93.27, "America/Chicago", "554", "612"],
59
+ ["Nashville", "Tennessee", "TN", "US", 36.16, -86.78, "America/Chicago", "372", "615"],
60
+ ["Anchorage", "Alaska", "AK", "US", 61.22, -149.9, "America/Anchorage", "995", "907"],
61
+ ["Honolulu", "Hawaii", "HI", "US", 21.31, -157.86, "Pacific/Honolulu", "968", "808"],
62
+ ["Salt Lake City", "Utah", "UT", "US", 40.76, -111.89, "America/Denver", "841", "801"],
63
+ ["Detroit", "Michigan", "MI", "US", 42.33, -83.05, "America/Detroit", "482", "313"],
64
+ ["New Orleans", "Louisiana", "LA", "US", 29.95, -90.07, "America/Chicago", "701", "504"],
65
+ ["Toronto", "Ontario", "ON", "CA", 43.65, -79.38, "America/Toronto", "M5V", "416"],
66
+ ["Vancouver", "British Columbia", "BC", "CA", 49.28, -123.12, "America/Vancouver", "V6B", "604"],
67
+ ["Montreal", "Quebec", "QC", "CA", 45.5, -73.57, "America/Toronto", "H2Y", "514"],
68
+ ["Calgary", "Alberta", "AB", "CA", 51.05, -114.07, "America/Edmonton", "T2P", "403"],
69
+ ["Ottawa", "Ontario", "ON", "CA", 45.42, -75.7, "America/Toronto", "K1P", "613"],
70
+ ["Winnipeg", "Manitoba", "MB", "CA", 49.9, -97.14, "America/Winnipeg", "R3C", "204"],
71
+ ["Halifax", "Nova Scotia", "NS", "CA", 44.65, -63.58, "America/Halifax", "B3J", "902"],
72
+ ["London", "England", "ENG", "GB", 51.51, -0.13, "Europe/London", "EC1A", "20"],
73
+ ["Manchester", "England", "ENG", "GB", 53.48, -2.24, "Europe/London", "M1", "161", "+44 161 496 0###"],
74
+ ["Birmingham", "England", "ENG", "GB", 52.49, -1.89, "Europe/London", "B1", "121", "+44 121 496 0###"],
75
+ ["Leeds", "England", "ENG", "GB", 53.8, -1.55, "Europe/London", "LS1", "113", "+44 113 496 0###"],
76
+ ["Liverpool", "England", "ENG", "GB", 53.41, -2.98, "Europe/London", "L1", "151", "+44 151 496 0###"],
77
+ ["Bristol", "England", "ENG", "GB", 51.45, -2.59, "Europe/London", "BS1", "117", "+44 117 496 0###"],
78
+ ["Edinburgh", "Scotland", "SCT", "GB", 55.95, -3.19, "Europe/London", "EH1", "131", "+44 131 496 0###"],
79
+ ["Glasgow", "Scotland", "SCT", "GB", 55.86, -4.25, "Europe/London", "G1", "141", "+44 141 496 0###"],
80
+ ["Cardiff", "Wales", "WLS", "GB", 51.48, -3.18, "Europe/London", "CF10", "29", "+44 29 2018 0###"],
81
+ ["Belfast", "Northern Ireland", "NIR", "GB", 54.6, -5.93, "Europe/London", "BT1", "28", "+44 28 9649 6###"],
82
+ ["Berlin", "Berlin", "BE", "DE", 52.52, 13.41, "Europe/Berlin", "10", "30"],
83
+ ["Hamburg", "Hamburg", "HH", "DE", 53.55, 9.99, "Europe/Berlin", "20", "40"],
84
+ ["Munich", "Bavaria", "BY", "DE", 48.14, 11.58, "Europe/Berlin", "80", "89"],
85
+ ["Cologne", "North Rhine-Westphalia", "NW", "DE", 50.94, 6.96, "Europe/Berlin", "50", "221"],
86
+ ["Frankfurt", "Hesse", "HE", "DE", 50.11, 8.68, "Europe/Berlin", "60", "69"],
87
+ ["Stuttgart", "Baden-Württemberg", "BW", "DE", 48.78, 9.18, "Europe/Berlin", "70", "711"],
88
+ ["Dresden", "Saxony", "SN", "DE", 51.05, 13.74, "Europe/Berlin", "01", "351"],
89
+ ["Leipzig", "Saxony", "SN", "DE", 51.34, 12.37, "Europe/Berlin", "04", "341"],
90
+ ["Paris", "Île-de-France", "IDF", "FR", 48.86, 2.35, "Europe/Paris", "750", "1", "+33 1 99 00 ## ##"],
91
+ ["Lyon", "Auvergne-Rhône-Alpes", "ARA", "FR", 45.76, 4.84, "Europe/Paris", "690", "4"],
92
+ ["Marseille", "Provence-Alpes-Côte d'Azur", "PAC", "FR", 43.3, 5.37, "Europe/Paris", "130", "4"],
93
+ ["Toulouse", "Occitanie", "OCC", "FR", 43.6, 1.44, "Europe/Paris", "310", "5"],
94
+ ["Nice", "Provence-Alpes-Côte d'Azur", "PAC", "FR", 43.7, 7.27, "Europe/Paris", "060", "4"],
95
+ ["Bordeaux", "Nouvelle-Aquitaine", "NAQ", "FR", 44.84, -0.58, "Europe/Paris", "330", "5"],
96
+ ["Nantes", "Pays de la Loire", "PDL", "FR", 47.22, -1.55, "Europe/Paris", "440", "2"],
97
+ ["Strasbourg", "Grand Est", "GES", "FR", 48.57, 7.75, "Europe/Paris", "670", "3"],
98
+ ["Madrid", "Community of Madrid", "MD", "ES", 40.42, -3.7, "Europe/Madrid", "28", "91"],
99
+ ["Barcelona", "Catalonia", "CT", "ES", 41.39, 2.17, "Europe/Madrid", "08", "93"],
100
+ ["Valencia", "Valencian Community", "VC", "ES", 39.47, -0.38, "Europe/Madrid", "46", "96"],
101
+ ["Seville", "Andalusia", "AN", "ES", 37.39, -5.98, "Europe/Madrid", "41", "95"],
102
+ ["Bilbao", "Basque Country", "PV", "ES", 43.26, -2.93, "Europe/Madrid", "48", "94"],
103
+ ["Zaragoza", "Aragon", "AR", "ES", 41.65, -0.89, "Europe/Madrid", "50", "97"],
104
+ ["Rome", "Lazio", "62", "IT", 41.9, 12.5, "Europe/Rome", "00", "06"],
105
+ ["Milan", "Lombardy", "25", "IT", 45.46, 9.19, "Europe/Rome", "20", "02"],
106
+ ["Naples", "Campania", "72", "IT", 40.85, 14.27, "Europe/Rome", "80", "081"],
107
+ ["Turin", "Piedmont", "21", "IT", 45.07, 7.69, "Europe/Rome", "10", "011"],
108
+ ["Florence", "Tuscany", "52", "IT", 43.77, 11.26, "Europe/Rome", "50", "055"],
109
+ ["Palermo", "Sicily", "82", "IT", 38.12, 13.36, "Europe/Rome", "90", "091"],
110
+ ["Amsterdam", "North Holland", "NH", "NL", 52.37, 4.9, "Europe/Amsterdam", "10", "20"],
111
+ ["Rotterdam", "South Holland", "ZH", "NL", 51.92, 4.48, "Europe/Amsterdam", "30", "10"],
112
+ ["Utrecht", "Utrecht", "UT", "NL", 52.09, 5.12, "Europe/Amsterdam", "35", "30"],
113
+ ["Eindhoven", "North Brabant", "NB", "NL", 51.44, 5.48, "Europe/Amsterdam", "56", "40"],
114
+ ["Stockholm", "Stockholm County", "AB", "SE", 59.33, 18.07, "Europe/Stockholm", "11", "8"],
115
+ ["Gothenburg", "Västra Götaland", "O", "SE", 57.71, 11.97, "Europe/Stockholm", "41", "31"],
116
+ ["Malmö", "Skåne", "M", "SE", 55.6, 13.0, "Europe/Stockholm", "21", "40"],
117
+ ["Zurich", "Zurich", "ZH", "CH", 47.38, 8.54, "Europe/Zurich", "80", "44"],
118
+ ["Geneva", "Geneva", "GE", "CH", 46.2, 6.14, "Europe/Zurich", "12", "22"],
119
+ ["Bern", "Bern", "BE", "CH", 46.95, 7.45, "Europe/Zurich", "30", "31"],
120
+ ["Sydney", "New South Wales", "NSW", "AU", -33.87, 151.21, "Australia/Sydney", "20", "2"],
121
+ ["Melbourne", "Victoria", "VIC", "AU", -37.81, 144.96, "Australia/Melbourne", "30", "3"],
122
+ ["Brisbane", "Queensland", "QLD", "AU", -27.47, 153.03, "Australia/Brisbane", "40", "7"],
123
+ ["Perth", "Western Australia", "WA", "AU", -31.95, 115.86, "Australia/Perth", "60", "8"],
124
+ ["Adelaide", "South Australia", "SA", "AU", -34.93, 138.6, "Australia/Adelaide", "50", "8"],
125
+ ["Auckland", "Auckland", "AUK", "NZ", -36.85, 174.76, "Pacific/Auckland", "10", "9"],
126
+ ["Wellington", "Wellington", "WGN", "NZ", -41.29, 174.78, "Pacific/Auckland", "61", "4"],
127
+ ["Christchurch", "Canterbury", "CAN", "NZ", -43.53, 172.64, "Pacific/Auckland", "80", "3"],
128
+ ["Tokyo", "Tokyo", "13", "JP", 35.68, 139.69, "Asia/Tokyo", "100", "3"],
129
+ ["Osaka", "Osaka", "27", "JP", 34.69, 135.5, "Asia/Tokyo", "530", "6"],
130
+ ["Kyoto", "Kyoto", "26", "JP", 35.01, 135.77, "Asia/Tokyo", "600", "75"],
131
+ ["Sapporo", "Hokkaido", "01", "JP", 43.06, 141.35, "Asia/Tokyo", "060", "11"],
132
+ ["Fukuoka", "Fukuoka", "40", "JP", 33.59, 130.4, "Asia/Tokyo", "810", "92"],
133
+ ["Nagoya", "Aichi", "23", "JP", 35.18, 136.91, "Asia/Tokyo", "460", "52"],
134
+ ["São Paulo", "São Paulo", "SP", "BR", -23.55, -46.63, "America/Sao_Paulo", "01", "11"],
135
+ ["Rio de Janeiro", "Rio de Janeiro", "RJ", "BR", -22.91, -43.17, "America/Sao_Paulo", "20", "21"],
136
+ ["Brasília", "Federal District", "DF", "BR", -15.79, -47.88, "America/Sao_Paulo", "70", "61"],
137
+ ["Salvador", "Bahia", "BA", "BR", -12.97, -38.5, "America/Bahia", "40", "71"],
138
+ ["Manaus", "Amazonas", "AM", "BR", -3.12, -60.02, "America/Manaus", "69", "92"],
139
+ ["Mexico City", "Mexico City", "CMX", "MX", 19.43, -99.13, "America/Mexico_City", "06", "55"],
140
+ ["Guadalajara", "Jalisco", "JAL", "MX", 20.67, -103.35, "America/Mexico_City", "44", "33"],
141
+ ["Monterrey", "Nuevo León", "NLE", "MX", 25.69, -100.32, "America/Monterrey", "64", "81"],
142
+ ["Mérida", "Yucatán", "YUC", "MX", 20.97, -89.62, "America/Merida", "97", "999"],
143
+ ["Mumbai", "Maharashtra", "MH", "IN", 19.08, 72.88, "Asia/Kolkata", "400", "22"],
144
+ ["Delhi", "Delhi", "DL", "IN", 28.61, 77.21, "Asia/Kolkata", "110", "11"],
145
+ ["Bengaluru", "Karnataka", "KA", "IN", 12.97, 77.59, "Asia/Kolkata", "560", "80"],
146
+ ["Chennai", "Tamil Nadu", "TN", "IN", 13.08, 80.27, "Asia/Kolkata", "600", "44"],
147
+ ["Kolkata", "West Bengal", "WB", "IN", 22.57, 88.36, "Asia/Kolkata", "700", "33"],
148
+ ["Hyderabad", "Telangana", "TS", "IN", 17.39, 78.49, "Asia/Kolkata", "500", "40"],
149
+ ["Lagos", "Lagos", "LA", "NG", 6.52, 3.38, "Africa/Lagos", "100", "1"],
150
+ ["Abuja", "Federal Capital Territory", "FC", "NG", 9.08, 7.4, "Africa/Lagos", "900", "9"],
151
+ ["Kano", "Kano", "KN", "NG", 12.0, 8.52, "Africa/Lagos", "700", "64"],
152
+ ["Johannesburg", "Gauteng", "GP", "ZA", -26.2, 28.05, "Africa/Johannesburg", "20", "11"],
153
+ ["Cape Town", "Western Cape", "WC", "ZA", -33.92, 18.42, "Africa/Johannesburg", "80", "21"],
154
+ ["Durban", "KwaZulu-Natal", "KZN", "ZA", -29.86, 31.02, "Africa/Johannesburg", "40", "31"],
155
+ ];
156
+
157
+ const LOWER = "abcdefghijklmnopqrstuvwxyz";
158
+ const UPPER = LOWER.toUpperCase();
159
+ const round4 = (x) => Math.round(x * 10000) / 10000;
160
+
161
+ /** Fill a pattern: "#" digit, "A" letter, anything else literal. */
162
+ function fill(pattern, s) {
163
+ let out = "";
164
+ for (const c of pattern) out += c === "#" ? s.int(0, 9) : c === "A" ? UPPER[s.int(0, 25)] : c;
165
+ return out;
166
+ }
167
+
168
+ const INHERIT = new Set(["address", "location", "coordinates", "coords", "geo", "geolocation", "position", "place", "residence"]);
169
+
170
+ export const geo = freeze({
171
+ name: "geo",
172
+ version: 1,
173
+ vocab: { geoCountries, geoCities },
174
+ types: { "place.lat": "Float", "place.lng": "Float" },
175
+ // a type called `address`, `location`, ... shares the persona of the entity that holds it
176
+ inherit: (typeName) => INHERIT.has(typeName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).pop().toLowerCase()),
177
+ rules({ tokens, last }) {
178
+ const key = tokens.join(" ");
179
+ if (last === "timezone" || last === "tz" || key.endsWith("time zone")) return { sdl: "String", gen: "geo.timezone" };
180
+ if (last === "latitude" || last === "lat") return { sdl: "Float", gen: "place.lat" };
181
+ if (last === "longitude" || last === "lng" || last === "lon" || last === "long") return { sdl: "Float", gen: "place.lng" };
182
+ if (last === "region" || last === "province") return { sdl: "String", gen: "geo.region" };
183
+ if (key.endsWith("country code")) return { sdl: "String", gen: "geo.countryCode" };
184
+ if (key.endsWith("region code") || key.endsWith("state code") || key.endsWith("province code")) return { sdl: "String", gen: "geo.regionCode" };
185
+ if (key.endsWith("calling code") || key.endsWith("dial code")) return { sdl: "String", gen: "geo.callingCode" };
186
+ if (last === "postcode") return { sdl: "String", gen: "place.zip" };
187
+ return null;
188
+ },
189
+ generators(V) {
190
+ const countries = new Map(V.geoCountries.map((c) => [c[0], { code: c[0], name: c[1], dial: c[2], pf: c[3], ph: c[4], style: c[5] }]));
191
+ const cities = V.geoCities.map((c) => ({
192
+ city: c[0], region: c[1], regionCode: c[2], country: countries.get(c[3]), lat: c[4], lng: c[5], tz: c[6], pc: c[7], ac: c[8], ph: c[9],
193
+ }));
194
+ const place = (ctx) => ctx.slot("place").pick(cities);
195
+ const postal = (ctx, row) => row.pc + fill(row.country.pf, ctx.slot("zip"));
196
+ const street = (ctx) => {
197
+ const s = ctx.slot("street");
198
+ return `${s.int(1, 999)} ${s.pick(V.streets)} ${s.pick(V.streetSuffixes)}`;
199
+ };
200
+ return {
201
+ "place.city": (ctx) => place(ctx).city,
202
+ "place.state": (ctx) => place(ctx).region,
203
+ "place.country": (ctx) => place(ctx).country.name,
204
+ "place.zip": (ctx) => postal(ctx, place(ctx)),
205
+ "place.street": (ctx) => street(ctx),
206
+ "place.address": (ctx) => {
207
+ const row = place(ctx);
208
+ const p = postal(ctx, row);
209
+ return row.country.style === "us" ? `${street(ctx)}, ${row.city}, ${row.regionCode} ${p}` : `${street(ctx)}, ${p} ${row.city}`;
210
+ },
211
+ // within about 0.1 degree (roughly 10 km) of the city centre
212
+ "place.lat": (ctx) => round4(place(ctx).lat + (ctx.slot("latJitter").float() - 0.5) * 0.2),
213
+ "place.lng": (ctx) => round4(place(ctx).lng + (ctx.slot("lngJitter").float() - 0.5) * 0.2),
214
+ "person.phone": (ctx) => {
215
+ const row = place(ctx);
216
+ return fill((row.ph ?? row.country.ph).replace("{ac}", row.ac), ctx.slot("phoneLine"));
217
+ },
218
+ "geo.region": (ctx) => place(ctx).region,
219
+ "geo.regionCode": (ctx) => place(ctx).regionCode,
220
+ "geo.countryCode": (ctx) => place(ctx).country.code,
221
+ "geo.callingCode": (ctx) => `+${place(ctx).country.dial}`,
222
+ "geo.timezone": (ctx) => place(ctx).tz,
223
+ };
224
+ },
225
+ });
226
+
227
+ export default geo;
@@ -0,0 +1,4 @@
1
+ import type { Pack } from "../index.js";
2
+ /** The health pack. Import from "@johnhenry/graceholder/packs/health". */
3
+ export declare const health: Pack;
4
+ export default health;
@@ -0,0 +1,76 @@
1
+ // Health pack: CLEARLY FICTIONAL conditions and medications for demos and tests. Every condition and
2
+ // medication is an invented, whimsical name that is not a real diagnosis or drug, and by default the
3
+ // generators append "(fictional)" so a value can never be mistaken for medical data. Medical
4
+ // specialties are ordinary category names. Nothing here is medical information or advice.
5
+ // FROZEN at 1.0.0.
6
+
7
+ const freeze = (o) => {
8
+ if (o !== null && typeof o === "object") for (const v of Object.values(o)) freeze(v);
9
+ return Object.freeze(o);
10
+ };
11
+
12
+ // [condition, specialty, medication A, medication B]
13
+ const healthConditions = [
14
+ ["Wibblefern Syndrome", "Neurology", "Glimmerol", "Wobblex"],
15
+ ["Grumblecough", "Pulmonology", "Snoozeprax", "Puffadine"],
16
+ ["Snorkelitis", "Otolaryngology", "Bubblenase", "Flumpazine"],
17
+ ["Quibblepox", "Dermatology", "Dabbacream", "Spotless Plink"],
18
+ ["Mumbleknee", "Orthopedics", "Creakaway", "Jointibop"],
19
+ ["Flutterhiccup Disorder", "Gastroenterology", "Hiccuban", "Glugstop"],
20
+ ["Marmalade Fever", "Infectious Disease", "Jamzolve", "Sticktoast"],
21
+ ["Gigglepox", "Pediatrics", "Chuckleron", "Tickleflux"],
22
+ ["Sillybone Disorder", "Rheumatology", "Funnymax", "Boneroo"],
23
+ ["Twiddlethumb Palsy", "Neurology", "Fidgetol", "Thumbwell"],
24
+ ["Noodleback", "Physical Medicine", "Stiffnot", "Wigglorin"],
25
+ ["Dizzlewhirl", "Otolaryngology", "Spinabate", "Steadyfloop"],
26
+ ["Pumpernickel Rash", "Dermatology", "Crumbicide", "Smoothaloo"],
27
+ ["Hullabaloo Headache", "Neurology", "Quietane", "Hushabye Forte"],
28
+ ["Gloomcloud Fatigue", "Psychiatry", "Sunbeamol", "Brightify"],
29
+ ["Tumbleweed Cough", "Pulmonology", "Rollaway", "Breezeomin"],
30
+ ["Fidgetwing Syndrome", "Cardiology", "Calmwing", "Steadybeat"],
31
+ ["Plinkerton Allergy", "Allergy", "Sneezeless", "Pollenpuff"],
32
+ ["Blinkblot Strain", "Ophthalmology", "Clearglance", "Eyebrite Mist"],
33
+ ["Crumplekidney Ache", "Nephrology", "Flushwell", "Filterfun"],
34
+ ["Hiccup-in-the-Heart", "Cardiology", "Thumpaway", "Rhythmoo"],
35
+ ["Mossback Stiffness", "Rheumatology", "Limberol", "Unkinkazine"],
36
+ ["Pillowcase Insomnia", "Sleep Medicine", "Drowsabell", "Dreamlux"],
37
+ ["Teacup Tremor", "Neurology", "Steadysip", "Shakeaway"],
38
+ ];
39
+
40
+ const healthDoses = ["5 mg", "10 mg", "20 mg", "25 mg", "50 mg", "100 mg", "250 mg", "500 mg", "2 mL", "5 mL"];
41
+ const healthSeverities = ["mild", "moderate", "severe"];
42
+
43
+ export const health = freeze({
44
+ name: "health",
45
+ version: 1,
46
+ vocab: { healthConditions, healthDoses, healthSeverities },
47
+ rules({ tokens, last, kind }) {
48
+ const key = tokens.join(" ");
49
+ if (last === "diagnosis" || last === "illness" || last === "disease" || (last === "condition" && kind !== "product")) return { sdl: "String", gen: "health.condition" };
50
+ if (last === "medication" || last === "drug" || last === "medicine" || last === "prescription") return { sdl: "String", gen: "health.medication" };
51
+ if (last === "dosage" || last === "dose") return { sdl: "String", gen: "health.dosage" };
52
+ if (last === "specialty" || last === "speciality") return { sdl: "String", gen: "health.specialty" };
53
+ if (key === "severity" || key === "condition severity") return { sdl: "String", gen: "health.severity" };
54
+ if (key === "mrn" || key === "record number" || key === "medical record number") return { sdl: "String", gen: "health.recordNumber" };
55
+ return null;
56
+ },
57
+ generators(V) {
58
+ // one "condition" slot per entity: condition, medication and specialty agree
59
+ const row = (ctx) => ctx.slot("condition").pick(V.healthConditions);
60
+ const tag = (text, args) => (args.tag === false ? text : `${text} (fictional)`);
61
+ return {
62
+ "health.condition": (ctx) => tag(row(ctx)[0], ctx.args),
63
+ "health.medication": (ctx) => {
64
+ const r = row(ctx);
65
+ return tag(r[2 + ctx.slot("medChoice").int(0, 1)], ctx.args);
66
+ },
67
+ "health.dosage": (ctx) => ctx.slot("dose").pick(V.healthDoses),
68
+ "health.specialty": (ctx) => row(ctx)[1],
69
+ "health.severity": (ctx) => ctx.slot("severity").pick(V.healthSeverities),
70
+ // not a real identifier format: the FX prefix marks it as fictional
71
+ "health.recordNumber": (ctx) => `FX-${String(ctx.slot("mrn").int(0, 9999999)).padStart(7, "0")}`,
72
+ };
73
+ },
74
+ });
75
+
76
+ export default health;
@@ -0,0 +1,7 @@
1
+ import type { Pack } from "../index.js";
2
+ export { geo } from "./geo.js";
3
+ export { commerce } from "./commerce.js";
4
+ export { finance } from "./finance.js";
5
+ export { health } from "./health.js";
6
+ /** Every pack, in registration order. */
7
+ export declare const allPacks: readonly Pack[];
@@ -0,0 +1,9 @@
1
+ // All packs. Import this entry to get every pack; import `@johnhenry/graceholder/packs/<name>` to ship one.
2
+ import { geo } from "./geo.js";
3
+ import { commerce } from "./commerce.js";
4
+ import { finance } from "./finance.js";
5
+ import { health } from "./health.js";
6
+
7
+ export { geo, commerce, finance, health };
8
+ /** Every pack, in registration order. */
9
+ export const allPacks = Object.freeze([geo, commerce, finance, health]);