equilibria-mcp-server 1.0.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 (164) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/LICENSE +21 -0
  3. package/README.md +240 -0
  4. package/dist/builders/YAMLBuilder.d.ts +77 -0
  5. package/dist/builders/YAMLBuilder.d.ts.map +1 -0
  6. package/dist/builders/YAMLBuilder.js +251 -0
  7. package/dist/builders/YAMLBuilder.js.map +1 -0
  8. package/dist/economics/constants.d.ts +39 -0
  9. package/dist/economics/constants.d.ts.map +1 -0
  10. package/dist/economics/constants.js +46 -0
  11. package/dist/economics/constants.js.map +1 -0
  12. package/dist/economics/formulas.d.ts +19 -0
  13. package/dist/economics/formulas.d.ts.map +1 -0
  14. package/dist/economics/formulas.js +260 -0
  15. package/dist/economics/formulas.js.map +1 -0
  16. package/dist/economics/index.d.ts +9 -0
  17. package/dist/economics/index.d.ts.map +1 -0
  18. package/dist/economics/index.js +9 -0
  19. package/dist/economics/index.js.map +1 -0
  20. package/dist/economics/validators.d.ts +18 -0
  21. package/dist/economics/validators.d.ts.map +1 -0
  22. package/dist/economics/validators.js +111 -0
  23. package/dist/economics/validators.js.map +1 -0
  24. package/dist/errors/index.d.ts +172 -0
  25. package/dist/errors/index.d.ts.map +1 -0
  26. package/dist/errors/index.js +313 -0
  27. package/dist/errors/index.js.map +1 -0
  28. package/dist/formatters/index.d.ts +8 -0
  29. package/dist/formatters/index.d.ts.map +1 -0
  30. package/dist/formatters/index.js +8 -0
  31. package/dist/formatters/index.js.map +1 -0
  32. package/dist/formatters/redux.d.ts +15 -0
  33. package/dist/formatters/redux.d.ts.map +1 -0
  34. package/dist/formatters/redux.js +35 -0
  35. package/dist/formatters/redux.js.map +1 -0
  36. package/dist/formatters/yaml.d.ts +18 -0
  37. package/dist/formatters/yaml.d.ts.map +1 -0
  38. package/dist/formatters/yaml.js +40 -0
  39. package/dist/formatters/yaml.js.map +1 -0
  40. package/dist/index.d.ts +11 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +19 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/server.d.ts +14 -0
  45. package/dist/server.d.ts.map +1 -0
  46. package/dist/server.js +86 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/templates/advancedMicro.d.ts +8 -0
  49. package/dist/templates/advancedMicro.d.ts.map +1 -0
  50. package/dist/templates/advancedMicro.js +834 -0
  51. package/dist/templates/advancedMicro.js.map +1 -0
  52. package/dist/templates/consumer.d.ts +12 -0
  53. package/dist/templates/consumer.d.ts.map +1 -0
  54. package/dist/templates/consumer.js +1978 -0
  55. package/dist/templates/consumer.js.map +1 -0
  56. package/dist/templates/elasticity.d.ts +8 -0
  57. package/dist/templates/elasticity.d.ts.map +1 -0
  58. package/dist/templates/elasticity.js +500 -0
  59. package/dist/templates/elasticity.js.map +1 -0
  60. package/dist/templates/externalities.d.ts +11 -0
  61. package/dist/templates/externalities.d.ts.map +1 -0
  62. package/dist/templates/externalities.js +997 -0
  63. package/dist/templates/externalities.js.map +1 -0
  64. package/dist/templates/financeBehavioral.d.ts +8 -0
  65. package/dist/templates/financeBehavioral.d.ts.map +1 -0
  66. package/dist/templates/financeBehavioral.js +860 -0
  67. package/dist/templates/financeBehavioral.js.map +1 -0
  68. package/dist/templates/growth.d.ts +8 -0
  69. package/dist/templates/growth.d.ts.map +1 -0
  70. package/dist/templates/growth.js +740 -0
  71. package/dist/templates/growth.js.map +1 -0
  72. package/dist/templates/index.d.ts +31 -0
  73. package/dist/templates/index.d.ts.map +1 -0
  74. package/dist/templates/index.js +91 -0
  75. package/dist/templates/index.js.map +1 -0
  76. package/dist/templates/inequality.d.ts +8 -0
  77. package/dist/templates/inequality.d.ts.map +1 -0
  78. package/dist/templates/inequality.js +562 -0
  79. package/dist/templates/inequality.js.map +1 -0
  80. package/dist/templates/intertemporalMacro.d.ts +8 -0
  81. package/dist/templates/intertemporalMacro.d.ts.map +1 -0
  82. package/dist/templates/intertemporalMacro.js +550 -0
  83. package/dist/templates/intertemporalMacro.js.map +1 -0
  84. package/dist/templates/isLM.d.ts +8 -0
  85. package/dist/templates/isLM.d.ts.map +1 -0
  86. package/dist/templates/isLM.js +747 -0
  87. package/dist/templates/isLM.js.map +1 -0
  88. package/dist/templates/macro.d.ts +8 -0
  89. package/dist/templates/macro.d.ts.map +1 -0
  90. package/dist/templates/macro.js +600 -0
  91. package/dist/templates/macro.js.map +1 -0
  92. package/dist/templates/marketStructures.d.ts +11 -0
  93. package/dist/templates/marketStructures.d.ts.map +1 -0
  94. package/dist/templates/marketStructures.js +1135 -0
  95. package/dist/templates/marketStructures.js.map +1 -0
  96. package/dist/templates/newKeynesian.d.ts +8 -0
  97. package/dist/templates/newKeynesian.d.ts.map +1 -0
  98. package/dist/templates/newKeynesian.js +633 -0
  99. package/dist/templates/newKeynesian.js.map +1 -0
  100. package/dist/templates/oligopoly.d.ts +11 -0
  101. package/dist/templates/oligopoly.d.ts.map +1 -0
  102. package/dist/templates/oligopoly.js +1113 -0
  103. package/dist/templates/oligopoly.js.map +1 -0
  104. package/dist/templates/ppf.d.ts +8 -0
  105. package/dist/templates/ppf.d.ts.map +1 -0
  106. package/dist/templates/ppf.js +439 -0
  107. package/dist/templates/ppf.js.map +1 -0
  108. package/dist/templates/producer.d.ts +11 -0
  109. package/dist/templates/producer.d.ts.map +1 -0
  110. package/dist/templates/producer.js +979 -0
  111. package/dist/templates/producer.js.map +1 -0
  112. package/dist/templates/production.d.ts +8 -0
  113. package/dist/templates/production.d.ts.map +1 -0
  114. package/dist/templates/production.js +574 -0
  115. package/dist/templates/production.js.map +1 -0
  116. package/dist/templates/supplyDemand.d.ts +8 -0
  117. package/dist/templates/supplyDemand.d.ts.map +1 -0
  118. package/dist/templates/supplyDemand.js +1282 -0
  119. package/dist/templates/supplyDemand.js.map +1 -0
  120. package/dist/templates/tradeGrowth.d.ts +8 -0
  121. package/dist/templates/tradeGrowth.d.ts.map +1 -0
  122. package/dist/templates/tradeGrowth.js +637 -0
  123. package/dist/templates/tradeGrowth.js.map +1 -0
  124. package/dist/tools/index.d.ts +25 -0
  125. package/dist/tools/index.d.ts.map +1 -0
  126. package/dist/tools/index.js +54 -0
  127. package/dist/tools/index.js.map +1 -0
  128. package/dist/tools/models.d.ts +8 -0
  129. package/dist/tools/models.d.ts.map +1 -0
  130. package/dist/tools/models.js +828 -0
  131. package/dist/tools/models.js.map +1 -0
  132. package/dist/tools/output.d.ts +8 -0
  133. package/dist/tools/output.d.ts.map +1 -0
  134. package/dist/tools/output.js +236 -0
  135. package/dist/tools/output.js.map +1 -0
  136. package/dist/tools/templates.d.ts +8 -0
  137. package/dist/tools/templates.d.ts.map +1 -0
  138. package/dist/tools/templates.js +247 -0
  139. package/dist/tools/templates.js.map +1 -0
  140. package/dist/tools/validation.d.ts +8 -0
  141. package/dist/tools/validation.d.ts.map +1 -0
  142. package/dist/tools/validation.js +181 -0
  143. package/dist/tools/validation.js.map +1 -0
  144. package/dist/types/index.d.ts +187 -0
  145. package/dist/types/index.d.ts.map +1 -0
  146. package/dist/types/index.js +7 -0
  147. package/dist/types/index.js.map +1 -0
  148. package/dist/utils/cache.d.ts +99 -0
  149. package/dist/utils/cache.d.ts.map +1 -0
  150. package/dist/utils/cache.js +192 -0
  151. package/dist/utils/cache.js.map +1 -0
  152. package/dist/utils/index.d.ts +8 -0
  153. package/dist/utils/index.d.ts.map +1 -0
  154. package/dist/utils/index.js +8 -0
  155. package/dist/utils/index.js.map +1 -0
  156. package/dist/utils/logger.d.ts +128 -0
  157. package/dist/utils/logger.d.ts.map +1 -0
  158. package/dist/utils/logger.js +251 -0
  159. package/dist/utils/logger.js.map +1 -0
  160. package/dist/validation/index.d.ts +42 -0
  161. package/dist/validation/index.d.ts.map +1 -0
  162. package/dist/validation/index.js +282 -0
  163. package/dist/validation/index.js.map +1 -0
  164. package/package.json +73 -0
@@ -0,0 +1,1978 @@
1
+ /**
2
+ * Consumer Theory Templates
3
+ *
4
+ * Templates for consumer theory models including budget constraints,
5
+ * indifference curves, and optimal choice analysis.
6
+ *
7
+ * Phase 3.1: Budget Constraints
8
+ * Phase 3.2: Preferences (indifference curves, optimal choice)
9
+ */
10
+ // ============================================================================
11
+ // PHASE 3.1: BUDGET CONSTRAINTS
12
+ // ============================================================================
13
+ /**
14
+ * Basic Budget Constraint
15
+ *
16
+ * Two-good budget line showing the trade-off between goods X and Y
17
+ * given income (M) and prices (Px, Py).
18
+ *
19
+ * Budget constraint: Px * X + Py * Y = M
20
+ * Rearranged: Y = M/Py - (Px/Py) * X
21
+ */
22
+ const budgetConstraintBasic = {
23
+ id: 'budget_constraint_basic',
24
+ name: 'Basic Budget Constraint',
25
+ description: 'Two-good budget line showing the affordable combinations of goods X and Y given income and prices. The slope represents the relative price ratio.',
26
+ category: 'consumer_theory',
27
+ level: 'undergraduate',
28
+ tags: ['budget constraint', 'consumer theory', 'affordability', 'price ratio'],
29
+ parameters: {
30
+ income: {
31
+ type: 'number',
32
+ default: 100,
33
+ min: 10,
34
+ max: 500,
35
+ description: 'Consumer income (M)',
36
+ },
37
+ priceX: {
38
+ type: 'number',
39
+ default: 10,
40
+ min: 1,
41
+ max: 50,
42
+ description: 'Price of good X (Px)',
43
+ },
44
+ priceY: {
45
+ type: 'number',
46
+ default: 5,
47
+ min: 1,
48
+ max: 50,
49
+ description: 'Price of good Y (Py)',
50
+ },
51
+ },
52
+ generate: (params) => {
53
+ const M = params.income ?? 100;
54
+ const Px = params.priceX ?? 10;
55
+ const Py = params.priceY ?? 5;
56
+ return {
57
+ metadata: {
58
+ specVersion: '1.3',
59
+ title: 'Budget Constraint',
60
+ description: 'Two-good budget constraint showing affordable combinations',
61
+ },
62
+ parameters: {
63
+ M: { value: M, label: 'Income', min: 10, max: 500, step: 10 },
64
+ Px: { value: Px, label: 'Price of X', min: 1, max: 50, step: 1 },
65
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 50, step: 1 },
66
+ // Intercepts
67
+ maxX: { expression: 'M / Px', label: 'Max X', readonly: true },
68
+ maxY: { expression: 'M / Py', label: 'Max Y', readonly: true },
69
+ // Slope (opportunity cost of X in terms of Y)
70
+ slope: { expression: '-Px / Py', label: 'Slope (-Px/Py)', readonly: true },
71
+ // Example affordable bundle
72
+ bundleX: { value: M / (2 * Px), label: 'Bundle X', min: 0, max: 'maxX' },
73
+ bundleY: {
74
+ expression: '(M - Px * bundleX) / Py',
75
+ label: 'Bundle Y',
76
+ readonly: true,
77
+ },
78
+ },
79
+ charts: [
80
+ {
81
+ id: 'main',
82
+ title: 'Budget Constraint: Px·X + Py·Y = M',
83
+ xAxis: { label: 'Good X', min: 0, max: 'maxX * 1.2' },
84
+ yAxis: { label: 'Good Y', min: 0, max: 'maxY * 1.2' },
85
+ elements: [
86
+ // Affordable region
87
+ {
88
+ id: 'affordable-region',
89
+ type: 'area',
90
+ topBoundary: 'budget-line',
91
+ bottomBoundary: '0',
92
+ leftBoundary: '0',
93
+ rightBoundary: 'maxX',
94
+ color: '#22C55E',
95
+ opacity: 0.2,
96
+ label: 'Affordable Region',
97
+ },
98
+ // Budget line
99
+ {
100
+ id: 'budget-line',
101
+ type: 'line',
102
+ equation: 'M / Py - (Px / Py) * x',
103
+ color: '#2563EB',
104
+ strokeWidth: 3,
105
+ label: 'Budget Line',
106
+ domain: { min: 0, max: 'maxX' },
107
+ },
108
+ // X-intercept
109
+ {
110
+ id: 'x-intercept',
111
+ type: 'point',
112
+ x: 'maxX',
113
+ y: 0,
114
+ color: '#7C3AED',
115
+ label: 'M/Px',
116
+ },
117
+ // Y-intercept
118
+ {
119
+ id: 'y-intercept',
120
+ type: 'point',
121
+ x: 0,
122
+ y: 'maxY',
123
+ color: '#7C3AED',
124
+ label: 'M/Py',
125
+ },
126
+ // Current bundle
127
+ {
128
+ id: 'bundle',
129
+ type: 'point',
130
+ x: 'bundleX',
131
+ y: 'bundleY',
132
+ color: '#16A34A',
133
+ label: 'Bundle',
134
+ droplines: { x: true, y: true },
135
+ draggable: true,
136
+ dragConfig: {
137
+ mode: 'expression',
138
+ parameters: { expressions: { bundleX: 'x' } },
139
+ constraints: { bounds: { minX: 0, maxX: 'maxX' } },
140
+ },
141
+ },
142
+ ],
143
+ annotations: [
144
+ {
145
+ id: 'slope-label',
146
+ text: 'Slope = -Px/Py = ${slope:.2f}',
147
+ x: 'maxX * 0.5',
148
+ y: 'maxY * 0.7',
149
+ color: '#2563EB',
150
+ },
151
+ {
152
+ id: 'bundle-label',
153
+ text: '(${bundleX:.1f}, ${bundleY:.1f})',
154
+ x: 'bundleX + maxX * 0.05',
155
+ y: 'bundleY + maxY * 0.05',
156
+ color: '#16A34A',
157
+ },
158
+ ],
159
+ },
160
+ ],
161
+ };
162
+ },
163
+ };
164
+ /**
165
+ * Budget Constraint with Income Change
166
+ *
167
+ * Shows how the budget constraint shifts when income changes,
168
+ * demonstrating parallel shifts in the budget line.
169
+ */
170
+ const budgetConstraintIncomeChange = {
171
+ id: 'budget_constraint_income_change',
172
+ name: 'Budget Constraint - Income Change',
173
+ description: 'Demonstrates how budget constraint shifts with income changes. Increase in income causes parallel outward shift; decrease causes inward shift.',
174
+ category: 'consumer_theory',
175
+ level: 'undergraduate',
176
+ tags: ['budget constraint', 'income effect', 'parallel shift', 'consumer theory'],
177
+ parameters: {
178
+ initialIncome: {
179
+ type: 'number',
180
+ default: 100,
181
+ min: 50,
182
+ max: 300,
183
+ description: 'Initial income level',
184
+ },
185
+ newIncome: {
186
+ type: 'number',
187
+ default: 150,
188
+ min: 50,
189
+ max: 300,
190
+ description: 'New income level',
191
+ },
192
+ priceX: {
193
+ type: 'number',
194
+ default: 10,
195
+ min: 1,
196
+ max: 50,
197
+ description: 'Price of good X',
198
+ },
199
+ priceY: {
200
+ type: 'number',
201
+ default: 5,
202
+ min: 1,
203
+ max: 50,
204
+ description: 'Price of good Y',
205
+ },
206
+ },
207
+ generate: (params) => {
208
+ const M0 = params.initialIncome ?? 100;
209
+ const M1 = params.newIncome ?? 150;
210
+ const Px = params.priceX ?? 10;
211
+ const Py = params.priceY ?? 5;
212
+ return {
213
+ metadata: {
214
+ specVersion: '1.3',
215
+ title: 'Budget Constraint - Income Change',
216
+ description: 'Effect of income change on the budget constraint',
217
+ },
218
+ parameters: {
219
+ M0: { value: M0, label: 'Initial Income', min: 50, max: 300 },
220
+ M1: { value: M1, label: 'New Income', min: 50, max: 300 },
221
+ Px: { value: Px, label: 'Price of X', min: 1, max: 50 },
222
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 50 },
223
+ // Initial intercepts
224
+ maxX0: { expression: 'M0 / Px', hidden: true },
225
+ maxY0: { expression: 'M0 / Py', hidden: true },
226
+ // New intercepts
227
+ maxX1: { expression: 'M1 / Px', hidden: true },
228
+ maxY1: { expression: 'M1 / Py', hidden: true },
229
+ // Max for chart bounds
230
+ chartMaxX: { expression: 'max(maxX0, maxX1) * 1.2', hidden: true },
231
+ chartMaxY: { expression: 'max(maxY0, maxY1) * 1.2', hidden: true },
232
+ // Income change
233
+ deltaM: { expression: 'M1 - M0', label: 'ΔIncome', readonly: true },
234
+ pctChange: {
235
+ expression: '(M1 - M0) / M0 * 100',
236
+ label: '% Change',
237
+ readonly: true,
238
+ },
239
+ },
240
+ charts: [
241
+ {
242
+ id: 'main',
243
+ title: 'Income Effect on Budget Constraint',
244
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
245
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
246
+ elements: [
247
+ // Original budget line
248
+ {
249
+ id: 'budget-original',
250
+ type: 'line',
251
+ equation: 'M0 / Py - (Px / Py) * x',
252
+ color: '#6B7280',
253
+ strokeWidth: 2,
254
+ lineStyle: 'dashed',
255
+ label: 'Original (M₀)',
256
+ domain: { min: 0, max: 'maxX0' },
257
+ },
258
+ // New budget line
259
+ {
260
+ id: 'budget-new',
261
+ type: 'line',
262
+ equation: 'M1 / Py - (Px / Py) * x',
263
+ color: '#2563EB',
264
+ strokeWidth: 3,
265
+ label: 'New (M₁)',
266
+ domain: { min: 0, max: 'maxX1' },
267
+ },
268
+ // Shift arrow from origin line to new line
269
+ {
270
+ id: 'shift-arrow',
271
+ type: 'arrow',
272
+ x1: 'maxX0 / 2',
273
+ y1: 'M0 / Py - (Px / Py) * maxX0 / 2',
274
+ x2: 'maxX1 / 2',
275
+ y2: 'M1 / Py - (Px / Py) * maxX1 / 2',
276
+ color: '#16A34A',
277
+ label: 'Shift',
278
+ },
279
+ // Original intercepts
280
+ {
281
+ id: 'x-intercept-0',
282
+ type: 'point',
283
+ x: 'maxX0',
284
+ y: 0,
285
+ color: '#6B7280',
286
+ },
287
+ {
288
+ id: 'y-intercept-0',
289
+ type: 'point',
290
+ x: 0,
291
+ y: 'maxY0',
292
+ color: '#6B7280',
293
+ },
294
+ // New intercepts
295
+ {
296
+ id: 'x-intercept-1',
297
+ type: 'point',
298
+ x: 'maxX1',
299
+ y: 0,
300
+ color: '#2563EB',
301
+ },
302
+ {
303
+ id: 'y-intercept-1',
304
+ type: 'point',
305
+ x: 0,
306
+ y: 'maxY1',
307
+ color: '#2563EB',
308
+ },
309
+ ],
310
+ annotations: [
311
+ {
312
+ id: 'original-label',
313
+ text: 'M₀ = ${M0}',
314
+ x: 'maxX0 * 0.6',
315
+ y: 'M0 / Py - (Px / Py) * maxX0 * 0.6 + chartMaxY * 0.05',
316
+ color: '#6B7280',
317
+ },
318
+ {
319
+ id: 'new-label',
320
+ text: 'M₁ = ${M1}',
321
+ x: 'maxX1 * 0.6',
322
+ y: 'M1 / Py - (Px / Py) * maxX1 * 0.6 + chartMaxY * 0.05',
323
+ color: '#2563EB',
324
+ },
325
+ {
326
+ id: 'change-label',
327
+ text: 'ΔM = ${deltaM} (${pctChange:.1f}%)',
328
+ x: 'chartMaxX * 0.6',
329
+ y: 'chartMaxY * 0.9',
330
+ color: '#16A34A',
331
+ },
332
+ ],
333
+ },
334
+ ],
335
+ };
336
+ },
337
+ };
338
+ /**
339
+ * Budget Constraint with Price Change
340
+ *
341
+ * Shows how the budget constraint pivots when the price of one good changes,
342
+ * demonstrating the substitution effect graphically.
343
+ */
344
+ const budgetConstraintPriceChange = {
345
+ id: 'budget_constraint_price_change',
346
+ name: 'Budget Constraint - Price Change',
347
+ description: 'Demonstrates how budget constraint pivots when price of good X changes. Price increase pivots inward; decrease pivots outward from Y-intercept.',
348
+ category: 'consumer_theory',
349
+ level: 'undergraduate',
350
+ tags: ['budget constraint', 'price change', 'pivot', 'substitution effect'],
351
+ parameters: {
352
+ income: {
353
+ type: 'number',
354
+ default: 100,
355
+ description: 'Consumer income',
356
+ },
357
+ initialPriceX: {
358
+ type: 'number',
359
+ default: 10,
360
+ description: 'Initial price of good X',
361
+ },
362
+ newPriceX: {
363
+ type: 'number',
364
+ default: 5,
365
+ description: 'New price of good X (after change)',
366
+ },
367
+ priceY: {
368
+ type: 'number',
369
+ default: 5,
370
+ description: 'Price of good Y (unchanged)',
371
+ },
372
+ },
373
+ generate: (params) => {
374
+ const M = params.income ?? 100;
375
+ const Px0 = params.initialPriceX ?? 10;
376
+ const Px1 = params.newPriceX ?? 5;
377
+ const Py = params.priceY ?? 5;
378
+ return {
379
+ metadata: {
380
+ specVersion: '1.3',
381
+ title: 'Budget Constraint - Price Change',
382
+ description: 'Effect of price change on budget constraint (pivot)',
383
+ },
384
+ parameters: {
385
+ M: { value: M, label: 'Income', min: 50, max: 300 },
386
+ Px0: { value: Px0, label: 'Initial Px', min: 1, max: 50 },
387
+ Px1: { value: Px1, label: 'New Px', min: 1, max: 50 },
388
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 50 },
389
+ // Y-intercept (unchanged)
390
+ maxY: { expression: 'M / Py', hidden: true },
391
+ // X-intercepts
392
+ maxX0: { expression: 'M / Px0', hidden: true },
393
+ maxX1: { expression: 'M / Px1', hidden: true },
394
+ // Chart bounds
395
+ chartMaxX: { expression: 'max(maxX0, maxX1) * 1.2', hidden: true },
396
+ chartMaxY: { expression: 'maxY * 1.2', hidden: true },
397
+ // Slopes
398
+ slope0: { expression: '-Px0 / Py', label: 'Original Slope', readonly: true },
399
+ slope1: { expression: '-Px1 / Py', label: 'New Slope', readonly: true },
400
+ // Price change
401
+ deltaPx: { expression: 'Px1 - Px0', label: 'ΔPx', readonly: true },
402
+ pctPxChange: {
403
+ expression: '(Px1 - Px0) / Px0 * 100',
404
+ label: '% Px Change',
405
+ readonly: true,
406
+ },
407
+ },
408
+ charts: [
409
+ {
410
+ id: 'main',
411
+ title: 'Price Change Effect on Budget Constraint',
412
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
413
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
414
+ elements: [
415
+ // Original budget line
416
+ {
417
+ id: 'budget-original',
418
+ type: 'line',
419
+ equation: 'M / Py - (Px0 / Py) * x',
420
+ color: '#6B7280',
421
+ strokeWidth: 2,
422
+ lineStyle: 'dashed',
423
+ label: 'Original (Px₀)',
424
+ domain: { min: 0, max: 'maxX0' },
425
+ },
426
+ // New budget line
427
+ {
428
+ id: 'budget-new',
429
+ type: 'line',
430
+ equation: 'M / Py - (Px1 / Py) * x',
431
+ color: '#2563EB',
432
+ strokeWidth: 3,
433
+ label: 'New (Px₁)',
434
+ domain: { min: 0, max: 'maxX1' },
435
+ },
436
+ // Pivot point (Y-intercept)
437
+ {
438
+ id: 'pivot-point',
439
+ type: 'point',
440
+ x: 0,
441
+ y: 'maxY',
442
+ color: '#7C3AED',
443
+ label: 'Pivot Point',
444
+ },
445
+ // Original X-intercept
446
+ {
447
+ id: 'x-intercept-0',
448
+ type: 'point',
449
+ x: 'maxX0',
450
+ y: 0,
451
+ color: '#6B7280',
452
+ label: 'M/Px₀',
453
+ },
454
+ // New X-intercept
455
+ {
456
+ id: 'x-intercept-1',
457
+ type: 'point',
458
+ x: 'maxX1',
459
+ y: 0,
460
+ color: '#2563EB',
461
+ label: 'M/Px₁',
462
+ },
463
+ // Arc showing pivot
464
+ {
465
+ id: 'pivot-arc',
466
+ type: 'parametric',
467
+ x: 'maxX0 * cos(t)',
468
+ y: 'maxX0 * sin(t) * (Py / Px0)',
469
+ tMin: 0,
470
+ tMax: 'atan2(maxY, maxX1) - atan2(maxY, maxX0)',
471
+ color: '#16A34A',
472
+ lineStyle: 'dotted',
473
+ },
474
+ ],
475
+ annotations: [
476
+ {
477
+ id: 'slope-original',
478
+ text: 'Slope = ${slope0:.2f}',
479
+ x: 'maxX0 * 0.4',
480
+ y: 'M / Py - (Px0 / Py) * maxX0 * 0.4 + chartMaxY * 0.08',
481
+ color: '#6B7280',
482
+ },
483
+ {
484
+ id: 'slope-new',
485
+ text: 'Slope = ${slope1:.2f}',
486
+ x: 'maxX1 * 0.5',
487
+ y: 'M / Py - (Px1 / Py) * maxX1 * 0.5 - chartMaxY * 0.05',
488
+ color: '#2563EB',
489
+ },
490
+ {
491
+ id: 'price-change-label',
492
+ text: 'Px: ${Px0} → ${Px1} (${pctPxChange:.1f}%)',
493
+ x: 'chartMaxX * 0.6',
494
+ y: 'chartMaxY * 0.9',
495
+ color: '#DC2626',
496
+ },
497
+ ],
498
+ },
499
+ ],
500
+ };
501
+ },
502
+ };
503
+ /**
504
+ * Kinked Budget Constraint
505
+ *
506
+ * Shows budget constraint with kink due to rationing, quantity discounts,
507
+ * welfare programs, or in-kind transfers (vouchers).
508
+ */
509
+ const budgetConstraintKinked = {
510
+ id: 'budget_constraint_kinked',
511
+ name: 'Kinked Budget Constraint',
512
+ description: 'Budget constraint with a kink caused by rationing, quantity discounts, welfare programs, or in-kind transfers like food stamps.',
513
+ category: 'consumer_theory',
514
+ level: 'undergraduate',
515
+ tags: ['budget constraint', 'kinked', 'rationing', 'vouchers', 'welfare'],
516
+ parameters: {
517
+ income: {
518
+ type: 'number',
519
+ default: 100,
520
+ description: 'Consumer income',
521
+ },
522
+ priceX: {
523
+ type: 'number',
524
+ default: 10,
525
+ description: 'Price of good X',
526
+ },
527
+ priceY: {
528
+ type: 'number',
529
+ default: 5,
530
+ description: 'Price of good Y',
531
+ },
532
+ subsidizedUnits: {
533
+ type: 'number',
534
+ default: 5,
535
+ description: 'Units of X available at subsidized price',
536
+ },
537
+ subsidizedPrice: {
538
+ type: 'number',
539
+ default: 2,
540
+ description: 'Subsidized price for first units of X',
541
+ },
542
+ },
543
+ generate: (params) => {
544
+ const M = params.income ?? 100;
545
+ const Px = params.priceX ?? 10;
546
+ const Py = params.priceY ?? 5;
547
+ const Xsub = params.subsidizedUnits ?? 5;
548
+ const Pxsub = params.subsidizedPrice ?? 2;
549
+ return {
550
+ metadata: {
551
+ specVersion: '1.3',
552
+ title: 'Kinked Budget Constraint',
553
+ description: 'Budget constraint with subsidy/rationing kink',
554
+ },
555
+ parameters: {
556
+ M: { value: M, label: 'Income', min: 50, max: 300 },
557
+ Px: { value: Px, label: 'Market Price X', min: 1, max: 50 },
558
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 50 },
559
+ Xsub: { value: Xsub, label: 'Subsidized Units', min: 1, max: 20 },
560
+ Pxsub: { value: Pxsub, label: 'Subsidized Px', min: 0, max: 'Px - 1' },
561
+ // Y-intercept (all income on Y)
562
+ maxY: { expression: 'M / Py', hidden: true },
563
+ // Kink point calculations
564
+ kinkX: { expression: 'Xsub', hidden: true },
565
+ kinkY: { expression: '(M - Pxsub * Xsub) / Py', hidden: true },
566
+ // Money remaining after buying subsidized units
567
+ remainingM: { expression: 'M - Pxsub * Xsub', hidden: true },
568
+ // Max X if buying all subsidized then market price
569
+ maxX: { expression: 'Xsub + remainingM / Px', hidden: true },
570
+ // Chart bounds
571
+ chartMaxX: { expression: 'maxX * 1.2', hidden: true },
572
+ chartMaxY: { expression: 'maxY * 1.2', hidden: true },
573
+ // Subsidy value
574
+ subsidyValue: {
575
+ expression: '(Px - Pxsub) * Xsub',
576
+ label: 'Subsidy Value',
577
+ readonly: true,
578
+ },
579
+ },
580
+ charts: [
581
+ {
582
+ id: 'main',
583
+ title: 'Kinked Budget Constraint (Subsidy Program)',
584
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
585
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
586
+ elements: [
587
+ // Subsidized region (steeper because lower price)
588
+ {
589
+ id: 'budget-subsidized',
590
+ type: 'line',
591
+ equation: 'M / Py - (Pxsub / Py) * x',
592
+ color: '#22C55E',
593
+ strokeWidth: 3,
594
+ label: 'Subsidized (Px = ' + Pxsub + ')',
595
+ domain: { min: 0, max: 'kinkX' },
596
+ },
597
+ // Market price region (flatter because higher price)
598
+ {
599
+ id: 'budget-market',
600
+ type: 'line',
601
+ equation: 'kinkY - (Px / Py) * (x - kinkX)',
602
+ color: '#2563EB',
603
+ strokeWidth: 3,
604
+ label: 'Market Price (Px = ' + Px + ')',
605
+ domain: { min: 'kinkX', max: 'maxX' },
606
+ },
607
+ // Counterfactual: no subsidy budget line
608
+ {
609
+ id: 'budget-no-subsidy',
610
+ type: 'line',
611
+ equation: 'M / Py - (Px / Py) * x',
612
+ color: '#9CA3AF',
613
+ strokeWidth: 1,
614
+ lineStyle: 'dashed',
615
+ label: 'Without Subsidy',
616
+ domain: { min: 0, max: 'M / Px' },
617
+ },
618
+ // Kink point
619
+ {
620
+ id: 'kink-point',
621
+ type: 'point',
622
+ x: 'kinkX',
623
+ y: 'kinkY',
624
+ color: '#DC2626',
625
+ label: 'Kink',
626
+ },
627
+ // Y-intercept
628
+ {
629
+ id: 'y-intercept',
630
+ type: 'point',
631
+ x: 0,
632
+ y: 'maxY',
633
+ color: '#22C55E',
634
+ },
635
+ // X-intercept
636
+ {
637
+ id: 'x-intercept',
638
+ type: 'point',
639
+ x: 'maxX',
640
+ y: 0,
641
+ color: '#2563EB',
642
+ },
643
+ // Vertical line at kink
644
+ {
645
+ id: 'kink-vertical',
646
+ type: 'verticalLine',
647
+ x: 'kinkX',
648
+ color: '#DC2626',
649
+ lineStyle: 'dotted',
650
+ strokeWidth: 1,
651
+ },
652
+ ],
653
+ annotations: [
654
+ {
655
+ id: 'subsidized-label',
656
+ text: 'First ${Xsub} units at $${Pxsub}',
657
+ x: 'kinkX * 0.3',
658
+ y: 'chartMaxY * 0.85',
659
+ color: '#22C55E',
660
+ },
661
+ {
662
+ id: 'market-label',
663
+ text: 'Additional units at $${Px}',
664
+ x: 'kinkX + (maxX - kinkX) * 0.5',
665
+ y: 'kinkY * 0.4',
666
+ color: '#2563EB',
667
+ },
668
+ {
669
+ id: 'subsidy-value-label',
670
+ text: 'Subsidy value = $${subsidyValue:.0f}',
671
+ x: 'chartMaxX * 0.6',
672
+ y: 'chartMaxY * 0.95',
673
+ color: '#DC2626',
674
+ },
675
+ ],
676
+ },
677
+ ],
678
+ };
679
+ },
680
+ };
681
+ /**
682
+ * Budget Constraint with Endowment
683
+ *
684
+ * Shows budget constraint when consumer has an initial endowment of goods
685
+ * rather than just income. Common in general equilibrium analysis.
686
+ */
687
+ const budgetConstraintEndowment = {
688
+ id: 'budget_constraint_endowment',
689
+ name: 'Budget Constraint with Endowment',
690
+ description: 'Budget constraint when consumer has an initial endowment of goods. The budget line passes through the endowment point, allowing both buying and selling.',
691
+ category: 'consumer_theory',
692
+ level: 'undergraduate',
693
+ tags: ['budget constraint', 'endowment', 'general equilibrium', 'trading'],
694
+ parameters: {
695
+ endowmentX: {
696
+ type: 'number',
697
+ default: 6,
698
+ description: 'Initial endowment of good X',
699
+ },
700
+ endowmentY: {
701
+ type: 'number',
702
+ default: 8,
703
+ description: 'Initial endowment of good Y',
704
+ },
705
+ priceX: {
706
+ type: 'number',
707
+ default: 10,
708
+ description: 'Price of good X',
709
+ },
710
+ priceY: {
711
+ type: 'number',
712
+ default: 5,
713
+ description: 'Price of good Y',
714
+ },
715
+ },
716
+ generate: (params) => {
717
+ const eX = params.endowmentX ?? 6;
718
+ const eY = params.endowmentY ?? 8;
719
+ const Px = params.priceX ?? 10;
720
+ const Py = params.priceY ?? 5;
721
+ return {
722
+ metadata: {
723
+ specVersion: '1.3',
724
+ title: 'Budget Constraint with Endowment',
725
+ description: 'Budget constraint passing through initial endowment',
726
+ },
727
+ parameters: {
728
+ eX: { value: eX, label: 'Endowment X', min: 1, max: 20 },
729
+ eY: { value: eY, label: 'Endowment Y', min: 1, max: 20 },
730
+ Px: { value: Px, label: 'Price of X', min: 1, max: 50 },
731
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 50 },
732
+ // Wealth = value of endowment
733
+ W: { expression: 'Px * eX + Py * eY', label: 'Wealth', readonly: true },
734
+ // Intercepts
735
+ maxX: { expression: 'W / Px', hidden: true },
736
+ maxY: { expression: 'W / Py', hidden: true },
737
+ // Chart bounds
738
+ chartMaxX: { expression: 'maxX * 1.1', hidden: true },
739
+ chartMaxY: { expression: 'maxY * 1.1', hidden: true },
740
+ // Example chosen bundle
741
+ chosenX: { value: eX * 1.2, label: 'Chosen X', min: 0, max: 'maxX' },
742
+ chosenY: {
743
+ expression: '(W - Px * chosenX) / Py',
744
+ label: 'Chosen Y',
745
+ readonly: true,
746
+ },
747
+ // Trade amounts
748
+ tradeX: { expression: 'chosenX - eX', label: 'Trade X', readonly: true },
749
+ tradeY: { expression: 'chosenY - eY', label: 'Trade Y', readonly: true },
750
+ },
751
+ charts: [
752
+ {
753
+ id: 'main',
754
+ title: 'Budget Constraint with Endowment',
755
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
756
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
757
+ elements: [
758
+ // Budget line
759
+ {
760
+ id: 'budget-line',
761
+ type: 'line',
762
+ equation: 'W / Py - (Px / Py) * x',
763
+ color: '#2563EB',
764
+ strokeWidth: 3,
765
+ label: 'Budget Line',
766
+ domain: { min: 0, max: 'maxX' },
767
+ },
768
+ // Endowment point
769
+ {
770
+ id: 'endowment',
771
+ type: 'point',
772
+ x: 'eX',
773
+ y: 'eY',
774
+ color: '#DC2626',
775
+ size: 8,
776
+ label: 'Endowment (E)',
777
+ droplines: { x: true, y: true },
778
+ },
779
+ // Chosen bundle
780
+ {
781
+ id: 'chosen',
782
+ type: 'point',
783
+ x: 'chosenX',
784
+ y: 'chosenY',
785
+ color: '#16A34A',
786
+ label: 'Chosen Bundle',
787
+ droplines: { x: true, y: true },
788
+ draggable: true,
789
+ dragConfig: {
790
+ mode: 'expression',
791
+ parameters: { expressions: { chosenX: 'x' } },
792
+ constraints: { bounds: { minX: 0, maxX: 'maxX' } },
793
+ },
794
+ },
795
+ // Trade vector
796
+ {
797
+ id: 'trade-arrow',
798
+ type: 'arrow',
799
+ x1: 'eX',
800
+ y1: 'eY',
801
+ x2: 'chosenX',
802
+ y2: 'chosenY',
803
+ color: '#7C3AED',
804
+ label: 'Trade',
805
+ },
806
+ // Regions: seller of X (left of endowment), buyer of X (right)
807
+ {
808
+ id: 'sell-x-region',
809
+ type: 'area',
810
+ topBoundary: 'budget-line',
811
+ bottomBoundary: '0',
812
+ leftBoundary: '0',
813
+ rightBoundary: 'eX',
814
+ color: '#EF4444',
815
+ opacity: 0.1,
816
+ },
817
+ {
818
+ id: 'buy-x-region',
819
+ type: 'area',
820
+ topBoundary: 'budget-line',
821
+ bottomBoundary: '0',
822
+ leftBoundary: 'eX',
823
+ rightBoundary: 'maxX',
824
+ color: '#22C55E',
825
+ opacity: 0.1,
826
+ },
827
+ ],
828
+ annotations: [
829
+ {
830
+ id: 'wealth-label',
831
+ text: 'W = Px·eX + Py·eY = $${W:.0f}',
832
+ x: 'chartMaxX * 0.5',
833
+ y: 'chartMaxY * 0.95',
834
+ color: '#2563EB',
835
+ },
836
+ {
837
+ id: 'sell-label',
838
+ text: 'Sell X, Buy Y',
839
+ x: 'eX * 0.3',
840
+ y: 'chartMaxY * 0.7',
841
+ color: '#EF4444',
842
+ },
843
+ {
844
+ id: 'buy-label',
845
+ text: 'Buy X, Sell Y',
846
+ x: 'eX + (maxX - eX) * 0.5',
847
+ y: 'chartMaxY * 0.3',
848
+ color: '#22C55E',
849
+ },
850
+ {
851
+ id: 'trade-label',
852
+ text: 'ΔX=${tradeX:.1f}, ΔY=${tradeY:.1f}',
853
+ x: '(eX + chosenX) / 2',
854
+ y: '(eY + chosenY) / 2 + chartMaxY * 0.05',
855
+ color: '#7C3AED',
856
+ },
857
+ ],
858
+ },
859
+ ],
860
+ };
861
+ },
862
+ };
863
+ // ============================================================================
864
+ // PHASE 3.2: PREFERENCES AND INDIFFERENCE CURVES
865
+ // ============================================================================
866
+ /**
867
+ * Cobb-Douglas Indifference Curves
868
+ *
869
+ * Standard utility function: U(X,Y) = X^α * Y^(1-α)
870
+ * Indifference curves are convex hyperbolas.
871
+ */
872
+ const indifferenceCurvesCobbDouglas = {
873
+ id: 'indifference_curves_cobb_douglas',
874
+ name: 'Cobb-Douglas Indifference Curves',
875
+ description: 'Standard convex indifference curves from Cobb-Douglas utility function U = X^α · Y^(1-α). Shows diminishing marginal rate of substitution.',
876
+ category: 'consumer_theory',
877
+ level: 'undergraduate',
878
+ tags: ['indifference curves', 'utility', 'Cobb-Douglas', 'MRS', 'preferences'],
879
+ parameters: {
880
+ alpha: {
881
+ type: 'number',
882
+ default: 0.5,
883
+ min: 0.1,
884
+ max: 0.9,
885
+ description: 'Exponent on good X (preference weight)',
886
+ },
887
+ utilityLevel: {
888
+ type: 'number',
889
+ default: 10,
890
+ min: 1,
891
+ max: 50,
892
+ description: 'Utility level for main indifference curve',
893
+ },
894
+ },
895
+ generate: (params) => {
896
+ const alpha = params.alpha ?? 0.5;
897
+ const U = params.utilityLevel ?? 10;
898
+ return {
899
+ metadata: {
900
+ specVersion: '1.3',
901
+ title: 'Cobb-Douglas Indifference Curves',
902
+ description: 'U(X,Y) = X^α · Y^(1-α)',
903
+ },
904
+ parameters: {
905
+ alpha: { value: alpha, label: 'α (preference for X)', min: 0.1, max: 0.9, step: 0.05 },
906
+ U: { value: U, label: 'Utility Level', min: 1, max: 50 },
907
+ U_low: { expression: 'U * 0.5', hidden: true },
908
+ U_high: { expression: 'U * 1.5', hidden: true },
909
+ // Current point on IC
910
+ pointX: { value: U ** alpha, label: 'Point X', min: 0.5, max: 30 },
911
+ pointY: {
912
+ expression: '(U / (pointX ^ alpha)) ^ (1 / (1 - alpha))',
913
+ label: 'Point Y',
914
+ readonly: true,
915
+ },
916
+ // MRS at current point: MRS = (α/(1-α)) * (Y/X)
917
+ MRS: {
918
+ expression: '(alpha / (1 - alpha)) * (pointY / pointX)',
919
+ label: 'MRS (|slope|)',
920
+ readonly: true,
921
+ },
922
+ // Chart bounds
923
+ chartMax: { expression: 'max(pointX, pointY) * 2', hidden: true },
924
+ },
925
+ charts: [
926
+ {
927
+ id: 'main',
928
+ title: 'Cobb-Douglas Indifference Curves',
929
+ xAxis: { label: 'Good X', min: 0.1, max: 'chartMax' },
930
+ yAxis: { label: 'Good Y', min: 0.1, max: 'chartMax' },
931
+ elements: [
932
+ // Lower utility IC
933
+ {
934
+ id: 'ic-low',
935
+ type: 'line',
936
+ equation: '(U_low / (x ^ alpha)) ^ (1 / (1 - alpha))',
937
+ color: '#93C5FD',
938
+ strokeWidth: 2,
939
+ label: 'U = ${U_low:.1f}',
940
+ domain: { min: 0.5, max: 'chartMax * 0.9' },
941
+ },
942
+ // Main utility IC
943
+ {
944
+ id: 'ic-main',
945
+ type: 'line',
946
+ equation: '(U / (x ^ alpha)) ^ (1 / (1 - alpha))',
947
+ color: '#2563EB',
948
+ strokeWidth: 3,
949
+ label: 'U = ${U:.1f}',
950
+ domain: { min: 0.5, max: 'chartMax * 0.9' },
951
+ },
952
+ // Higher utility IC
953
+ {
954
+ id: 'ic-high',
955
+ type: 'line',
956
+ equation: '(U_high / (x ^ alpha)) ^ (1 / (1 - alpha))',
957
+ color: '#1D4ED8',
958
+ strokeWidth: 2,
959
+ label: 'U = ${U_high:.1f}',
960
+ domain: { min: 0.5, max: 'chartMax * 0.9' },
961
+ },
962
+ // Current point
963
+ {
964
+ id: 'current-point',
965
+ type: 'point',
966
+ x: 'pointX',
967
+ y: 'pointY',
968
+ color: '#16A34A',
969
+ label: 'Current',
970
+ droplines: { x: true, y: true },
971
+ draggable: true,
972
+ dragConfig: {
973
+ mode: 'expression',
974
+ parameters: { expressions: { pointX: 'x' } },
975
+ constraints: { bounds: { minX: 0.5, maxX: 'chartMax * 0.8' } },
976
+ },
977
+ },
978
+ // Tangent line showing MRS
979
+ {
980
+ id: 'tangent',
981
+ type: 'line',
982
+ equation: 'pointY - MRS * (x - pointX)',
983
+ color: '#DC2626',
984
+ strokeWidth: 1,
985
+ lineStyle: 'dashed',
986
+ domain: { min: 'max(0.1, pointX - 5)', max: 'pointX + 5' },
987
+ },
988
+ ],
989
+ annotations: [
990
+ {
991
+ id: 'mrs-label',
992
+ text: 'MRS = ${MRS:.2f}',
993
+ x: 'pointX + chartMax * 0.1',
994
+ y: 'pointY',
995
+ color: '#DC2626',
996
+ },
997
+ {
998
+ id: 'utility-formula',
999
+ text: 'U = X^${alpha:.2f} · Y^${(1-alpha):.2f}',
1000
+ x: 'chartMax * 0.6',
1001
+ y: 'chartMax * 0.9',
1002
+ color: '#2563EB',
1003
+ },
1004
+ {
1005
+ id: 'preference-direction',
1006
+ text: '← Higher Utility →',
1007
+ x: 'chartMax * 0.7',
1008
+ y: 'chartMax * 0.5',
1009
+ color: '#6B7280',
1010
+ },
1011
+ ],
1012
+ },
1013
+ ],
1014
+ };
1015
+ },
1016
+ };
1017
+ /**
1018
+ * Perfect Substitutes Indifference Curves
1019
+ *
1020
+ * Linear utility function: U(X,Y) = aX + bY
1021
+ * Indifference curves are straight lines.
1022
+ */
1023
+ const indifferenceCurvesPerfectSubstitutes = {
1024
+ id: 'indifference_curves_perfect_substitutes',
1025
+ name: 'Perfect Substitutes Indifference Curves',
1026
+ description: 'Linear indifference curves for perfect substitutes. U = aX + bY. Constant MRS regardless of bundle.',
1027
+ category: 'consumer_theory',
1028
+ level: 'undergraduate',
1029
+ tags: ['indifference curves', 'perfect substitutes', 'linear utility', 'constant MRS'],
1030
+ parameters: {
1031
+ marginalUtilityX: {
1032
+ type: 'number',
1033
+ default: 2,
1034
+ min: 1,
1035
+ max: 10,
1036
+ description: 'Marginal utility of X (a)',
1037
+ },
1038
+ marginalUtilityY: {
1039
+ type: 'number',
1040
+ default: 1,
1041
+ min: 1,
1042
+ max: 10,
1043
+ description: 'Marginal utility of Y (b)',
1044
+ },
1045
+ utilityLevel: {
1046
+ type: 'number',
1047
+ default: 20,
1048
+ min: 5,
1049
+ max: 100,
1050
+ description: 'Utility level',
1051
+ },
1052
+ },
1053
+ generate: (params) => {
1054
+ const a = params.marginalUtilityX ?? 2;
1055
+ const b = params.marginalUtilityY ?? 1;
1056
+ const U = params.utilityLevel ?? 20;
1057
+ return {
1058
+ metadata: {
1059
+ specVersion: '1.3',
1060
+ title: 'Perfect Substitutes',
1061
+ description: 'Linear utility: U = aX + bY',
1062
+ },
1063
+ parameters: {
1064
+ a: { value: a, label: 'MU of X (a)', min: 1, max: 10 },
1065
+ b: { value: b, label: 'MU of Y (b)', min: 1, max: 10 },
1066
+ U: { value: U, label: 'Utility Level', min: 5, max: 100 },
1067
+ U_low: { expression: 'U * 0.6', hidden: true },
1068
+ U_high: { expression: 'U * 1.4', hidden: true },
1069
+ // Intercepts for main IC
1070
+ maxX: { expression: 'U / a', hidden: true },
1071
+ maxY: { expression: 'U / b', hidden: true },
1072
+ // MRS = a/b (constant)
1073
+ MRS: { expression: 'a / b', label: 'MRS (constant)', readonly: true },
1074
+ // Chart bounds
1075
+ chartMaxX: { expression: 'U_high / a * 1.1', hidden: true },
1076
+ chartMaxY: { expression: 'U_high / b * 1.1', hidden: true },
1077
+ },
1078
+ charts: [
1079
+ {
1080
+ id: 'main',
1081
+ title: 'Perfect Substitutes: U = aX + bY',
1082
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
1083
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
1084
+ elements: [
1085
+ // Lower utility IC
1086
+ {
1087
+ id: 'ic-low',
1088
+ type: 'line',
1089
+ equation: '(U_low - a * x) / b',
1090
+ color: '#93C5FD',
1091
+ strokeWidth: 2,
1092
+ label: 'U = ${U_low:.0f}',
1093
+ domain: { min: 0, max: 'U_low / a' },
1094
+ },
1095
+ // Main utility IC
1096
+ {
1097
+ id: 'ic-main',
1098
+ type: 'line',
1099
+ equation: '(U - a * x) / b',
1100
+ color: '#2563EB',
1101
+ strokeWidth: 3,
1102
+ label: 'U = ${U:.0f}',
1103
+ domain: { min: 0, max: 'maxX' },
1104
+ },
1105
+ // Higher utility IC
1106
+ {
1107
+ id: 'ic-high',
1108
+ type: 'line',
1109
+ equation: '(U_high - a * x) / b',
1110
+ color: '#1D4ED8',
1111
+ strokeWidth: 2,
1112
+ label: 'U = ${U_high:.0f}',
1113
+ domain: { min: 0, max: 'U_high / a' },
1114
+ },
1115
+ // Intercept points for main IC
1116
+ {
1117
+ id: 'x-intercept',
1118
+ type: 'point',
1119
+ x: 'maxX',
1120
+ y: 0,
1121
+ color: '#7C3AED',
1122
+ label: 'U/a',
1123
+ },
1124
+ {
1125
+ id: 'y-intercept',
1126
+ type: 'point',
1127
+ x: 0,
1128
+ y: 'maxY',
1129
+ color: '#7C3AED',
1130
+ label: 'U/b',
1131
+ },
1132
+ ],
1133
+ annotations: [
1134
+ {
1135
+ id: 'mrs-label',
1136
+ text: 'MRS = a/b = ${MRS:.2f} (constant)',
1137
+ x: 'chartMaxX * 0.5',
1138
+ y: 'chartMaxY * 0.9',
1139
+ color: '#DC2626',
1140
+ },
1141
+ {
1142
+ id: 'substitution-label',
1143
+ text: '${MRS:.1f} units of Y = 1 unit of X',
1144
+ x: 'chartMaxX * 0.5',
1145
+ y: 'chartMaxY * 0.82',
1146
+ color: '#6B7280',
1147
+ },
1148
+ ],
1149
+ },
1150
+ ],
1151
+ };
1152
+ },
1153
+ };
1154
+ /**
1155
+ * Perfect Complements Indifference Curves
1156
+ *
1157
+ * Leontief utility function: U(X,Y) = min(aX, bY)
1158
+ * L-shaped indifference curves.
1159
+ */
1160
+ const indifferenceCurvesPerfectComplements = {
1161
+ id: 'indifference_curves_perfect_complements',
1162
+ name: 'Perfect Complements Indifference Curves',
1163
+ description: 'L-shaped indifference curves for perfect complements. U = min(aX, bY). Goods consumed in fixed proportions.',
1164
+ category: 'consumer_theory',
1165
+ level: 'undergraduate',
1166
+ tags: ['indifference curves', 'perfect complements', 'Leontief', 'L-shaped', 'fixed proportions'],
1167
+ parameters: {
1168
+ ratioX: {
1169
+ type: 'number',
1170
+ default: 1,
1171
+ min: 1,
1172
+ max: 5,
1173
+ description: 'Units of X per bundle (a)',
1174
+ },
1175
+ ratioY: {
1176
+ type: 'number',
1177
+ default: 2,
1178
+ min: 1,
1179
+ max: 5,
1180
+ description: 'Units of Y per bundle (b)',
1181
+ },
1182
+ utilityLevel: {
1183
+ type: 'number',
1184
+ default: 10,
1185
+ min: 1,
1186
+ max: 30,
1187
+ description: 'Utility level (number of bundles)',
1188
+ },
1189
+ },
1190
+ generate: (params) => {
1191
+ const a = params.ratioX ?? 1;
1192
+ const b = params.ratioY ?? 2;
1193
+ const U = params.utilityLevel ?? 10;
1194
+ return {
1195
+ metadata: {
1196
+ specVersion: '1.3',
1197
+ title: 'Perfect Complements',
1198
+ description: 'Leontief utility: U = min(X/a, Y/b)',
1199
+ },
1200
+ parameters: {
1201
+ a: { value: a, label: 'X per bundle', min: 1, max: 5 },
1202
+ b: { value: b, label: 'Y per bundle', min: 1, max: 5 },
1203
+ U: { value: U, label: 'Bundles', min: 1, max: 30 },
1204
+ U_low: { expression: 'U * 0.6', hidden: true },
1205
+ U_high: { expression: 'U * 1.4', hidden: true },
1206
+ // Kink points
1207
+ kinkX: { expression: 'U * a', hidden: true },
1208
+ kinkY: { expression: 'U * b', hidden: true },
1209
+ kinkX_low: { expression: 'U_low * a', hidden: true },
1210
+ kinkY_low: { expression: 'U_low * b', hidden: true },
1211
+ kinkX_high: { expression: 'U_high * a', hidden: true },
1212
+ kinkY_high: { expression: 'U_high * b', hidden: true },
1213
+ // Chart bounds
1214
+ chartMax: { expression: 'max(kinkX_high, kinkY_high) * 1.3', hidden: true },
1215
+ // Optimal ratio
1216
+ ratio: { expression: 'b / a', label: 'Optimal Y/X', readonly: true },
1217
+ },
1218
+ charts: [
1219
+ {
1220
+ id: 'main',
1221
+ title: 'Perfect Complements: U = min(X/a, Y/b)',
1222
+ xAxis: { label: 'Good X', min: 0, max: 'chartMax' },
1223
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMax' },
1224
+ elements: [
1225
+ // Optimal consumption ray
1226
+ {
1227
+ id: 'optimal-ray',
1228
+ type: 'line',
1229
+ equation: '(b / a) * x',
1230
+ color: '#16A34A',
1231
+ strokeWidth: 2,
1232
+ lineStyle: 'dashed',
1233
+ label: 'Optimal Ratio',
1234
+ domain: { min: 0, max: 'chartMax * 0.9' },
1235
+ },
1236
+ // Lower utility IC (L-shape)
1237
+ {
1238
+ id: 'ic-low-horizontal',
1239
+ type: 'horizontalLine',
1240
+ y: 'kinkY_low',
1241
+ color: '#93C5FD',
1242
+ strokeWidth: 2,
1243
+ domain: { min: 'kinkX_low', max: 'chartMax' },
1244
+ },
1245
+ {
1246
+ id: 'ic-low-vertical',
1247
+ type: 'verticalLine',
1248
+ x: 'kinkX_low',
1249
+ color: '#93C5FD',
1250
+ strokeWidth: 2,
1251
+ domain: { min: 'kinkY_low', max: 'chartMax' },
1252
+ },
1253
+ // Main utility IC (L-shape)
1254
+ {
1255
+ id: 'ic-main-horizontal',
1256
+ type: 'horizontalLine',
1257
+ y: 'kinkY',
1258
+ color: '#2563EB',
1259
+ strokeWidth: 3,
1260
+ domain: { min: 'kinkX', max: 'chartMax' },
1261
+ },
1262
+ {
1263
+ id: 'ic-main-vertical',
1264
+ type: 'verticalLine',
1265
+ x: 'kinkX',
1266
+ color: '#2563EB',
1267
+ strokeWidth: 3,
1268
+ domain: { min: 'kinkY', max: 'chartMax' },
1269
+ },
1270
+ // Higher utility IC (L-shape)
1271
+ {
1272
+ id: 'ic-high-horizontal',
1273
+ type: 'horizontalLine',
1274
+ y: 'kinkY_high',
1275
+ color: '#1D4ED8',
1276
+ strokeWidth: 2,
1277
+ domain: { min: 'kinkX_high', max: 'chartMax' },
1278
+ },
1279
+ {
1280
+ id: 'ic-high-vertical',
1281
+ type: 'verticalLine',
1282
+ x: 'kinkX_high',
1283
+ color: '#1D4ED8',
1284
+ strokeWidth: 2,
1285
+ domain: { min: 'kinkY_high', max: 'chartMax' },
1286
+ },
1287
+ // Kink points
1288
+ {
1289
+ id: 'kink-low',
1290
+ type: 'point',
1291
+ x: 'kinkX_low',
1292
+ y: 'kinkY_low',
1293
+ color: '#93C5FD',
1294
+ },
1295
+ {
1296
+ id: 'kink-main',
1297
+ type: 'point',
1298
+ x: 'kinkX',
1299
+ y: 'kinkY',
1300
+ color: '#2563EB',
1301
+ label: 'Optimal',
1302
+ },
1303
+ {
1304
+ id: 'kink-high',
1305
+ type: 'point',
1306
+ x: 'kinkX_high',
1307
+ y: 'kinkY_high',
1308
+ color: '#1D4ED8',
1309
+ },
1310
+ ],
1311
+ annotations: [
1312
+ {
1313
+ id: 'ratio-label',
1314
+ text: 'Consume ${a} X : ${b} Y',
1315
+ x: 'chartMax * 0.6',
1316
+ y: 'chartMax * 0.9',
1317
+ color: '#16A34A',
1318
+ },
1319
+ {
1320
+ id: 'utility-label',
1321
+ text: 'U = min(X/${a}, Y/${b})',
1322
+ x: 'chartMax * 0.6',
1323
+ y: 'chartMax * 0.82',
1324
+ color: '#2563EB',
1325
+ },
1326
+ {
1327
+ id: 'main-kink-label',
1328
+ text: '(${kinkX:.0f}, ${kinkY:.0f})',
1329
+ x: 'kinkX + chartMax * 0.05',
1330
+ y: 'kinkY + chartMax * 0.05',
1331
+ color: '#2563EB',
1332
+ },
1333
+ ],
1334
+ },
1335
+ ],
1336
+ };
1337
+ },
1338
+ };
1339
+ /**
1340
+ * Optimal Consumer Choice
1341
+ *
1342
+ * Shows tangency between budget constraint and indifference curve
1343
+ * at the utility-maximizing bundle.
1344
+ */
1345
+ const consumerChoiceOptimal = {
1346
+ id: 'consumer_choice_optimal',
1347
+ name: 'Optimal Consumer Choice',
1348
+ description: 'Utility maximization where indifference curve is tangent to budget constraint. MRS = Px/Py at optimum.',
1349
+ category: 'consumer_theory',
1350
+ level: 'undergraduate',
1351
+ tags: ['utility maximization', 'optimal choice', 'tangency', 'MRS equals price ratio'],
1352
+ parameters: {
1353
+ income: {
1354
+ type: 'number',
1355
+ default: 100,
1356
+ description: 'Consumer income',
1357
+ },
1358
+ priceX: {
1359
+ type: 'number',
1360
+ default: 5,
1361
+ description: 'Price of good X',
1362
+ },
1363
+ priceY: {
1364
+ type: 'number',
1365
+ default: 5,
1366
+ description: 'Price of good Y',
1367
+ },
1368
+ alpha: {
1369
+ type: 'number',
1370
+ default: 0.5,
1371
+ description: 'Preference parameter (Cobb-Douglas α)',
1372
+ },
1373
+ },
1374
+ generate: (params) => {
1375
+ const M = params.income ?? 100;
1376
+ const Px = params.priceX ?? 5;
1377
+ const Py = params.priceY ?? 5;
1378
+ const alpha = params.alpha ?? 0.5;
1379
+ return {
1380
+ metadata: {
1381
+ specVersion: '1.3',
1382
+ title: 'Optimal Consumer Choice',
1383
+ description: 'Utility maximization with budget constraint',
1384
+ },
1385
+ parameters: {
1386
+ M: { value: M, label: 'Income', min: 50, max: 200 },
1387
+ Px: { value: Px, label: 'Price of X', min: 1, max: 20 },
1388
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 20 },
1389
+ alpha: { value: alpha, label: 'α', min: 0.1, max: 0.9, step: 0.05 },
1390
+ // Budget constraint intercepts
1391
+ maxX: { expression: 'M / Px', hidden: true },
1392
+ maxY: { expression: 'M / Py', hidden: true },
1393
+ // Optimal bundle (Cobb-Douglas solution)
1394
+ optX: { expression: 'alpha * M / Px', label: 'Optimal X', readonly: true },
1395
+ optY: {
1396
+ expression: '(1 - alpha) * M / Py',
1397
+ label: 'Optimal Y',
1398
+ readonly: true,
1399
+ },
1400
+ // Utility at optimum
1401
+ U_opt: {
1402
+ expression: '(optX ^ alpha) * (optY ^ (1 - alpha))',
1403
+ label: 'Max Utility',
1404
+ readonly: true,
1405
+ },
1406
+ // MRS at optimum (should equal Px/Py)
1407
+ MRS: {
1408
+ expression: '(alpha / (1 - alpha)) * (optY / optX)',
1409
+ label: 'MRS at optimum',
1410
+ readonly: true,
1411
+ },
1412
+ priceRatio: { expression: 'Px / Py', label: 'Px/Py', readonly: true },
1413
+ // Lower utility ICs for comparison
1414
+ U_low: { expression: 'U_opt * 0.6', hidden: true },
1415
+ U_high: { expression: 'U_opt * 1.2', hidden: true },
1416
+ // Chart bounds
1417
+ chartMaxX: { expression: 'maxX * 1.1', hidden: true },
1418
+ chartMaxY: { expression: 'maxY * 1.1', hidden: true },
1419
+ },
1420
+ charts: [
1421
+ {
1422
+ id: 'main',
1423
+ title: 'Utility Maximization: MRS = Px/Py',
1424
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
1425
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
1426
+ elements: [
1427
+ // Budget constraint
1428
+ {
1429
+ id: 'budget',
1430
+ type: 'line',
1431
+ equation: 'M / Py - (Px / Py) * x',
1432
+ color: '#2563EB',
1433
+ strokeWidth: 3,
1434
+ label: 'Budget Constraint',
1435
+ domain: { min: 0, max: 'maxX' },
1436
+ },
1437
+ // Suboptimal IC (attainable but not optimal)
1438
+ {
1439
+ id: 'ic-low',
1440
+ type: 'line',
1441
+ equation: '(U_low / (x ^ alpha)) ^ (1 / (1 - alpha))',
1442
+ color: '#9CA3AF',
1443
+ strokeWidth: 1,
1444
+ lineStyle: 'dashed',
1445
+ domain: { min: 0.5, max: 'chartMaxX' },
1446
+ },
1447
+ // Optimal IC (tangent to budget)
1448
+ {
1449
+ id: 'ic-optimal',
1450
+ type: 'line',
1451
+ equation: '(U_opt / (x ^ alpha)) ^ (1 / (1 - alpha))',
1452
+ color: '#16A34A',
1453
+ strokeWidth: 3,
1454
+ label: 'Optimal IC',
1455
+ domain: { min: 0.5, max: 'chartMaxX' },
1456
+ },
1457
+ // Unattainable IC (above budget)
1458
+ {
1459
+ id: 'ic-high',
1460
+ type: 'line',
1461
+ equation: '(U_high / (x ^ alpha)) ^ (1 / (1 - alpha))',
1462
+ color: '#EF4444',
1463
+ strokeWidth: 1,
1464
+ lineStyle: 'dashed',
1465
+ domain: { min: 0.5, max: 'chartMaxX' },
1466
+ },
1467
+ // Optimal point
1468
+ {
1469
+ id: 'optimum',
1470
+ type: 'point',
1471
+ x: 'optX',
1472
+ y: 'optY',
1473
+ color: '#16A34A',
1474
+ size: 10,
1475
+ label: 'Optimum',
1476
+ droplines: { x: true, y: true },
1477
+ },
1478
+ // Tangent line at optimum (showing MRS = price ratio)
1479
+ {
1480
+ id: 'tangent',
1481
+ type: 'line',
1482
+ equation: 'optY - priceRatio * (x - optX)',
1483
+ color: '#7C3AED',
1484
+ strokeWidth: 1,
1485
+ lineStyle: 'dotted',
1486
+ domain: { min: 'max(0, optX - 8)', max: 'optX + 8' },
1487
+ },
1488
+ ],
1489
+ annotations: [
1490
+ {
1491
+ id: 'opt-label',
1492
+ text: 'X* = ${optX:.1f}, Y* = ${optY:.1f}',
1493
+ x: 'optX + chartMaxX * 0.05',
1494
+ y: 'optY + chartMaxY * 0.05',
1495
+ color: '#16A34A',
1496
+ },
1497
+ {
1498
+ id: 'tangency-label',
1499
+ text: 'MRS = Px/Py = ${priceRatio:.2f}',
1500
+ x: 'chartMaxX * 0.6',
1501
+ y: 'chartMaxY * 0.9',
1502
+ color: '#7C3AED',
1503
+ },
1504
+ {
1505
+ id: 'utility-label',
1506
+ text: 'U* = ${U_opt:.2f}',
1507
+ x: 'chartMaxX * 0.6',
1508
+ y: 'chartMaxY * 0.82',
1509
+ color: '#16A34A',
1510
+ },
1511
+ {
1512
+ id: 'unattainable-label',
1513
+ text: 'Unattainable',
1514
+ x: 'chartMaxX * 0.8',
1515
+ y: '(U_high / ((chartMaxX * 0.8) ^ alpha)) ^ (1 / (1 - alpha))',
1516
+ color: '#EF4444',
1517
+ },
1518
+ ],
1519
+ },
1520
+ ],
1521
+ };
1522
+ },
1523
+ };
1524
+ /**
1525
+ * Income Consumption Curve (ICC) and Engel Curve
1526
+ *
1527
+ * Shows how optimal consumption changes with income,
1528
+ * tracing out the ICC in good-space and the Engel curve.
1529
+ */
1530
+ const incomeConsumptionCurve = {
1531
+ id: 'income_consumption_curve',
1532
+ name: 'Income Consumption Curve',
1533
+ description: 'Traces optimal bundles as income changes, showing income expansion path. Engel curve shows quantity of X vs income.',
1534
+ category: 'consumer_theory',
1535
+ level: 'undergraduate',
1536
+ tags: ['ICC', 'Engel curve', 'income effect', 'normal good', 'inferior good'],
1537
+ parameters: {
1538
+ priceX: {
1539
+ type: 'number',
1540
+ default: 5,
1541
+ description: 'Price of good X',
1542
+ },
1543
+ priceY: {
1544
+ type: 'number',
1545
+ default: 5,
1546
+ description: 'Price of good Y',
1547
+ },
1548
+ alpha: {
1549
+ type: 'number',
1550
+ default: 0.4,
1551
+ description: 'Preference parameter α',
1552
+ },
1553
+ incomeBase: {
1554
+ type: 'number',
1555
+ default: 50,
1556
+ description: 'Base income level',
1557
+ },
1558
+ incomeCurrent: {
1559
+ type: 'number',
1560
+ default: 100,
1561
+ description: 'Current income level',
1562
+ },
1563
+ },
1564
+ generate: (params) => {
1565
+ const Px = params.priceX ?? 5;
1566
+ const Py = params.priceY ?? 5;
1567
+ const alpha = params.alpha ?? 0.4;
1568
+ const M_base = params.incomeBase ?? 50;
1569
+ const M = params.incomeCurrent ?? 100;
1570
+ return {
1571
+ metadata: {
1572
+ specVersion: '1.3',
1573
+ title: 'Income Consumption Curve',
1574
+ description: 'Optimal bundles as income varies',
1575
+ },
1576
+ parameters: {
1577
+ Px: { value: Px, label: 'Price of X', min: 1, max: 20 },
1578
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 20 },
1579
+ alpha: { value: alpha, label: 'α', min: 0.1, max: 0.9, step: 0.05 },
1580
+ M_base: { value: M_base, label: 'Base Income', min: 20, max: 100 },
1581
+ M: { value: M, label: 'Current Income', min: 50, max: 200 },
1582
+ M_high: { expression: 'M * 1.5', hidden: true },
1583
+ // Budget constraint bounds
1584
+ maxX: { expression: 'M / Px', hidden: true },
1585
+ maxY: { expression: 'M / Py', hidden: true },
1586
+ // Optimal bundles at different incomes
1587
+ optX_base: { expression: 'alpha * M_base / Px', hidden: true },
1588
+ optY_base: { expression: '(1 - alpha) * M_base / Py', hidden: true },
1589
+ optX: { expression: 'alpha * M / Px', label: 'X*', readonly: true },
1590
+ optY: { expression: '(1 - alpha) * M / Py', label: 'Y*', readonly: true },
1591
+ optX_high: { expression: 'alpha * M_high / Px', hidden: true },
1592
+ optY_high: { expression: '(1 - alpha) * M_high / Py', hidden: true },
1593
+ // Income elasticity (for Cobb-Douglas, it's 1)
1594
+ incomeElasticity: { value: 1, label: 'Income Elasticity', readonly: true },
1595
+ // Chart bounds
1596
+ chartMaxX: { expression: 'M_high / Px * 1.1', hidden: true },
1597
+ chartMaxY: { expression: 'M_high / Py * 1.1', hidden: true },
1598
+ },
1599
+ charts: [
1600
+ {
1601
+ id: 'icc',
1602
+ title: 'Income Consumption Curve (ICC)',
1603
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
1604
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
1605
+ elements: [
1606
+ // Income Consumption Curve (for Cobb-Douglas, it's a ray through origin)
1607
+ {
1608
+ id: 'icc-line',
1609
+ type: 'line',
1610
+ equation: '((1 - alpha) / alpha) * (Px / Py) * x',
1611
+ color: '#7C3AED',
1612
+ strokeWidth: 3,
1613
+ label: 'ICC',
1614
+ domain: { min: 0, max: 'chartMaxX' },
1615
+ },
1616
+ // Budget constraints at different incomes
1617
+ {
1618
+ id: 'budget-base',
1619
+ type: 'line',
1620
+ equation: 'M_base / Py - (Px / Py) * x',
1621
+ color: '#9CA3AF',
1622
+ strokeWidth: 1,
1623
+ lineStyle: 'dashed',
1624
+ domain: { min: 0, max: 'M_base / Px' },
1625
+ },
1626
+ {
1627
+ id: 'budget-current',
1628
+ type: 'line',
1629
+ equation: 'M / Py - (Px / Py) * x',
1630
+ color: '#2563EB',
1631
+ strokeWidth: 2,
1632
+ label: 'Budget (M)',
1633
+ domain: { min: 0, max: 'maxX' },
1634
+ },
1635
+ {
1636
+ id: 'budget-high',
1637
+ type: 'line',
1638
+ equation: 'M_high / Py - (Px / Py) * x',
1639
+ color: '#9CA3AF',
1640
+ strokeWidth: 1,
1641
+ lineStyle: 'dashed',
1642
+ domain: { min: 0, max: 'M_high / Px' },
1643
+ },
1644
+ // Optimal points
1645
+ {
1646
+ id: 'opt-base',
1647
+ type: 'point',
1648
+ x: 'optX_base',
1649
+ y: 'optY_base',
1650
+ color: '#6B7280',
1651
+ },
1652
+ {
1653
+ id: 'opt-current',
1654
+ type: 'point',
1655
+ x: 'optX',
1656
+ y: 'optY',
1657
+ color: '#2563EB',
1658
+ size: 8,
1659
+ label: 'Current',
1660
+ droplines: { x: true, y: true },
1661
+ },
1662
+ {
1663
+ id: 'opt-high',
1664
+ type: 'point',
1665
+ x: 'optX_high',
1666
+ y: 'optY_high',
1667
+ color: '#6B7280',
1668
+ },
1669
+ ],
1670
+ annotations: [
1671
+ {
1672
+ id: 'icc-label',
1673
+ text: 'Income Consumption Curve',
1674
+ x: 'chartMaxX * 0.7',
1675
+ y: '((1 - alpha) / alpha) * (Px / Py) * chartMaxX * 0.7 * 1.1',
1676
+ color: '#7C3AED',
1677
+ },
1678
+ {
1679
+ id: 'current-label',
1680
+ text: 'M = $${M}',
1681
+ x: 'optX + 2',
1682
+ y: 'optY + 2',
1683
+ color: '#2563EB',
1684
+ },
1685
+ ],
1686
+ },
1687
+ {
1688
+ id: 'engel',
1689
+ title: 'Engel Curve for Good X',
1690
+ xAxis: { label: 'Income (M)', min: 0, max: 'M_high * 1.1' },
1691
+ yAxis: { label: 'Quantity of X', min: 0, max: 'optX_high * 1.2' },
1692
+ elements: [
1693
+ // Engel curve: X = αM/Px
1694
+ {
1695
+ id: 'engel-curve',
1696
+ type: 'line',
1697
+ equation: 'alpha * x / Px',
1698
+ color: '#16A34A',
1699
+ strokeWidth: 3,
1700
+ label: 'Engel Curve',
1701
+ },
1702
+ // Current income point
1703
+ {
1704
+ id: 'current-engel',
1705
+ type: 'point',
1706
+ x: 'M',
1707
+ y: 'optX',
1708
+ color: '#2563EB',
1709
+ label: 'Current',
1710
+ droplines: { x: true, y: true },
1711
+ },
1712
+ ],
1713
+ annotations: [
1714
+ {
1715
+ id: 'engel-formula',
1716
+ text: 'X* = αM/Px',
1717
+ x: 'M_high * 0.6',
1718
+ y: 'optX_high * 1.1',
1719
+ color: '#16A34A',
1720
+ },
1721
+ {
1722
+ id: 'normal-label',
1723
+ text: 'Normal Good (upward sloping)',
1724
+ x: 'M_high * 0.5',
1725
+ y: 'optX_high * 0.3',
1726
+ color: '#6B7280',
1727
+ },
1728
+ ],
1729
+ },
1730
+ ],
1731
+ };
1732
+ },
1733
+ };
1734
+ /**
1735
+ * Price Consumption Curve (PCC) and Demand Curve Derivation
1736
+ *
1737
+ * Shows how optimal consumption changes as price varies,
1738
+ * deriving the individual demand curve.
1739
+ */
1740
+ const priceConsumptionCurve = {
1741
+ id: 'price_consumption_curve',
1742
+ name: 'Price Consumption Curve',
1743
+ description: 'Traces optimal bundles as price of X changes, deriving the individual demand curve for X.',
1744
+ category: 'consumer_theory',
1745
+ level: 'undergraduate',
1746
+ tags: ['PCC', 'demand derivation', 'price effect', 'substitution effect', 'income effect'],
1747
+ parameters: {
1748
+ income: {
1749
+ type: 'number',
1750
+ default: 100,
1751
+ description: 'Consumer income',
1752
+ },
1753
+ priceY: {
1754
+ type: 'number',
1755
+ default: 5,
1756
+ description: 'Price of good Y (fixed)',
1757
+ },
1758
+ priceXLow: {
1759
+ type: 'number',
1760
+ default: 2,
1761
+ description: 'Low price of X',
1762
+ },
1763
+ priceXMid: {
1764
+ type: 'number',
1765
+ default: 5,
1766
+ description: 'Medium price of X',
1767
+ },
1768
+ priceXHigh: {
1769
+ type: 'number',
1770
+ default: 10,
1771
+ description: 'High price of X',
1772
+ },
1773
+ alpha: {
1774
+ type: 'number',
1775
+ default: 0.5,
1776
+ description: 'Preference parameter α',
1777
+ },
1778
+ },
1779
+ generate: (params) => {
1780
+ const M = params.income ?? 100;
1781
+ const Py = params.priceY ?? 5;
1782
+ const Px_low = params.priceXLow ?? 2;
1783
+ const Px_mid = params.priceXMid ?? 5;
1784
+ const Px_high = params.priceXHigh ?? 10;
1785
+ const alpha = params.alpha ?? 0.5;
1786
+ return {
1787
+ metadata: {
1788
+ specVersion: '1.3',
1789
+ title: 'Price Consumption Curve & Demand Derivation',
1790
+ description: 'Deriving demand from utility maximization',
1791
+ },
1792
+ parameters: {
1793
+ M: { value: M, label: 'Income', min: 50, max: 200 },
1794
+ Py: { value: Py, label: 'Price of Y', min: 1, max: 20 },
1795
+ Px_low: { value: Px_low, label: 'Low Px', min: 1, max: 10 },
1796
+ Px_mid: { value: Px_mid, label: 'Mid Px', min: 2, max: 15 },
1797
+ Px_high: { value: Px_high, label: 'High Px', min: 5, max: 25 },
1798
+ alpha: { value: alpha, label: 'α', min: 0.1, max: 0.9, step: 0.05 },
1799
+ // Y-intercept (always M/Py)
1800
+ maxY: { expression: 'M / Py', hidden: true },
1801
+ // X-intercepts at each price
1802
+ maxX_low: { expression: 'M / Px_low', hidden: true },
1803
+ maxX_mid: { expression: 'M / Px_mid', hidden: true },
1804
+ maxX_high: { expression: 'M / Px_high', hidden: true },
1805
+ // Optimal bundles at each price
1806
+ optX_low: { expression: 'alpha * M / Px_low', hidden: true },
1807
+ optY_low: { expression: '(1 - alpha) * M / Py', hidden: true },
1808
+ optX_mid: { expression: 'alpha * M / Px_mid', label: 'X* (mid)', readonly: true },
1809
+ optY_mid: { expression: '(1 - alpha) * M / Py', label: 'Y* (mid)', readonly: true },
1810
+ optX_high: { expression: 'alpha * M / Px_high', hidden: true },
1811
+ optY_high: { expression: '(1 - alpha) * M / Py', hidden: true },
1812
+ // Chart bounds
1813
+ chartMaxX: { expression: 'maxX_low * 1.1', hidden: true },
1814
+ chartMaxY: { expression: 'maxY * 1.2', hidden: true },
1815
+ priceMax: { expression: 'Px_high * 1.2', hidden: true },
1816
+ },
1817
+ charts: [
1818
+ {
1819
+ id: 'pcc',
1820
+ title: 'Price Consumption Curve (PCC)',
1821
+ xAxis: { label: 'Good X', min: 0, max: 'chartMaxX' },
1822
+ yAxis: { label: 'Good Y', min: 0, max: 'chartMaxY' },
1823
+ elements: [
1824
+ // PCC (for Cobb-Douglas, it's horizontal since Y* = (1-α)M/Py is constant)
1825
+ {
1826
+ id: 'pcc-line',
1827
+ type: 'horizontalLine',
1828
+ y: 'optY_mid',
1829
+ color: '#7C3AED',
1830
+ strokeWidth: 3,
1831
+ label: 'PCC',
1832
+ },
1833
+ // Budget lines at different prices
1834
+ {
1835
+ id: 'budget-low',
1836
+ type: 'line',
1837
+ equation: 'maxY - (Px_low / Py) * x',
1838
+ color: '#22C55E',
1839
+ strokeWidth: 2,
1840
+ lineStyle: 'dashed',
1841
+ label: 'Px = $' + Px_low,
1842
+ domain: { min: 0, max: 'maxX_low' },
1843
+ },
1844
+ {
1845
+ id: 'budget-mid',
1846
+ type: 'line',
1847
+ equation: 'maxY - (Px_mid / Py) * x',
1848
+ color: '#2563EB',
1849
+ strokeWidth: 2,
1850
+ label: 'Px = $' + Px_mid,
1851
+ domain: { min: 0, max: 'maxX_mid' },
1852
+ },
1853
+ {
1854
+ id: 'budget-high',
1855
+ type: 'line',
1856
+ equation: 'maxY - (Px_high / Py) * x',
1857
+ color: '#DC2626',
1858
+ strokeWidth: 2,
1859
+ lineStyle: 'dashed',
1860
+ label: 'Px = $' + Px_high,
1861
+ domain: { min: 0, max: 'maxX_high' },
1862
+ },
1863
+ // Optimal points
1864
+ {
1865
+ id: 'opt-low',
1866
+ type: 'point',
1867
+ x: 'optX_low',
1868
+ y: 'optY_low',
1869
+ color: '#22C55E',
1870
+ label: 'A',
1871
+ },
1872
+ {
1873
+ id: 'opt-mid',
1874
+ type: 'point',
1875
+ x: 'optX_mid',
1876
+ y: 'optY_mid',
1877
+ color: '#2563EB',
1878
+ size: 8,
1879
+ label: 'B',
1880
+ },
1881
+ {
1882
+ id: 'opt-high',
1883
+ type: 'point',
1884
+ x: 'optX_high',
1885
+ y: 'optY_high',
1886
+ color: '#DC2626',
1887
+ label: 'C',
1888
+ },
1889
+ ],
1890
+ annotations: [
1891
+ {
1892
+ id: 'pcc-label',
1893
+ text: 'Price Consumption Curve',
1894
+ x: 'chartMaxX * 0.7',
1895
+ y: 'optY_mid + chartMaxY * 0.08',
1896
+ color: '#7C3AED',
1897
+ },
1898
+ ],
1899
+ },
1900
+ {
1901
+ id: 'demand',
1902
+ title: 'Individual Demand Curve',
1903
+ xAxis: { label: 'Quantity of X', min: 0, max: 'chartMaxX' },
1904
+ yAxis: { label: 'Price of X', min: 0, max: 'priceMax' },
1905
+ elements: [
1906
+ // Demand curve: Px = αM/X (inverse demand)
1907
+ {
1908
+ id: 'demand-curve',
1909
+ type: 'line',
1910
+ equation: 'alpha * M / x',
1911
+ color: '#2563EB',
1912
+ strokeWidth: 3,
1913
+ label: 'Demand',
1914
+ domain: { min: 2, max: 'chartMaxX * 0.9' },
1915
+ },
1916
+ // Points from PCC
1917
+ {
1918
+ id: 'demand-low',
1919
+ type: 'point',
1920
+ x: 'optX_low',
1921
+ y: 'Px_low',
1922
+ color: '#22C55E',
1923
+ label: 'A',
1924
+ droplines: { x: true, y: true },
1925
+ },
1926
+ {
1927
+ id: 'demand-mid',
1928
+ type: 'point',
1929
+ x: 'optX_mid',
1930
+ y: 'Px_mid',
1931
+ color: '#2563EB',
1932
+ size: 8,
1933
+ label: 'B',
1934
+ droplines: { x: true, y: true },
1935
+ },
1936
+ {
1937
+ id: 'demand-high',
1938
+ type: 'point',
1939
+ x: 'optX_high',
1940
+ y: 'Px_high',
1941
+ color: '#DC2626',
1942
+ label: 'C',
1943
+ droplines: { x: true, y: true },
1944
+ },
1945
+ ],
1946
+ annotations: [
1947
+ {
1948
+ id: 'demand-formula',
1949
+ text: 'X* = αM/Px',
1950
+ x: 'chartMaxX * 0.7',
1951
+ y: 'priceMax * 0.8',
1952
+ color: '#2563EB',
1953
+ },
1954
+ ],
1955
+ },
1956
+ ],
1957
+ };
1958
+ },
1959
+ };
1960
+ // ============================================================================
1961
+ // EXPORTS
1962
+ // ============================================================================
1963
+ export const consumerTemplates = [
1964
+ // Phase 3.1: Budget Constraints
1965
+ budgetConstraintBasic,
1966
+ budgetConstraintIncomeChange,
1967
+ budgetConstraintPriceChange,
1968
+ budgetConstraintKinked,
1969
+ budgetConstraintEndowment,
1970
+ // Phase 3.2: Preferences and Indifference Curves
1971
+ indifferenceCurvesCobbDouglas,
1972
+ indifferenceCurvesPerfectSubstitutes,
1973
+ indifferenceCurvesPerfectComplements,
1974
+ consumerChoiceOptimal,
1975
+ incomeConsumptionCurve,
1976
+ priceConsumptionCurve,
1977
+ ];
1978
+ //# sourceMappingURL=consumer.js.map