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,1282 @@
1
+ /**
2
+ * Supply and Demand Templates
3
+ *
4
+ * Templates for basic supply and demand models.
5
+ */
6
+ /**
7
+ * Basic Supply and Demand
8
+ */
9
+ const supplyDemandBasic = {
10
+ id: 'supply_demand_basic',
11
+ name: 'Basic Supply and Demand',
12
+ description: 'A simple supply and demand model showing market equilibrium. Linear demand and supply curves with equilibrium point and droplines.',
13
+ category: 'supply_demand',
14
+ level: 'high_school',
15
+ tags: ['supply', 'demand', 'equilibrium', 'market', 'basic'],
16
+ parameters: {
17
+ demandIntercept: {
18
+ type: 'number',
19
+ default: 100,
20
+ min: 50,
21
+ max: 200,
22
+ description: 'Y-intercept of the demand curve (maximum price)',
23
+ },
24
+ demandSlope: {
25
+ type: 'number',
26
+ default: 1,
27
+ min: 0.1,
28
+ max: 5,
29
+ description: 'Slope of the demand curve',
30
+ },
31
+ supplyIntercept: {
32
+ type: 'number',
33
+ default: 0,
34
+ min: 0,
35
+ max: 50,
36
+ description: 'Y-intercept of the supply curve (minimum price)',
37
+ },
38
+ supplySlope: {
39
+ type: 'number',
40
+ default: 1,
41
+ min: 0.1,
42
+ max: 5,
43
+ description: 'Slope of the supply curve',
44
+ },
45
+ },
46
+ generate: (params) => {
47
+ const demandIntercept = params.demandIntercept ?? 100;
48
+ const demandSlope = params.demandSlope ?? 1;
49
+ const supplyIntercept = params.supplyIntercept ?? 0;
50
+ const supplySlope = params.supplySlope ?? 1;
51
+ return {
52
+ metadata: {
53
+ specVersion: '1.3',
54
+ title: 'Basic Supply and Demand',
55
+ description: 'Market equilibrium with linear supply and demand curves',
56
+ },
57
+ parameters: {
58
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept' },
59
+ demandSlope: { value: demandSlope, label: 'Demand Slope' },
60
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept' },
61
+ supplySlope: { value: supplySlope, label: 'Supply Slope' },
62
+ eqQ: {
63
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
64
+ label: 'Equilibrium Quantity',
65
+ },
66
+ eqP: {
67
+ expression: 'supplyIntercept + supplySlope * eqQ',
68
+ label: 'Equilibrium Price',
69
+ },
70
+ },
71
+ charts: [
72
+ {
73
+ id: 'main',
74
+ title: 'Supply and Demand',
75
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
76
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
77
+ elements: [
78
+ {
79
+ id: 'demand',
80
+ type: 'line',
81
+ equation: 'demandIntercept - demandSlope * x',
82
+ color: '#2563EB',
83
+ label: 'Demand (D)',
84
+ curveLabel: {
85
+ text: 'D',
86
+ x: 'demandIntercept * 0.9',
87
+ position: 'below',
88
+ },
89
+ },
90
+ {
91
+ id: 'supply',
92
+ type: 'line',
93
+ equation: 'supplyIntercept + supplySlope * x',
94
+ color: '#DC2626',
95
+ label: 'Supply (S)',
96
+ curveLabel: {
97
+ text: 'S',
98
+ x: 'demandIntercept * 0.9',
99
+ position: 'above',
100
+ },
101
+ },
102
+ {
103
+ id: 'equilibrium',
104
+ type: 'point',
105
+ x: 'eqQ',
106
+ y: 'eqP',
107
+ color: '#16A34A',
108
+ label: 'E',
109
+ droplines: {
110
+ x: 'Q*',
111
+ y: 'P*',
112
+ style: 'dashed',
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ ],
118
+ };
119
+ },
120
+ };
121
+ /**
122
+ * Supply and Demand with Surplus
123
+ */
124
+ const supplyDemandSurplus = {
125
+ id: 'supply_demand_surplus',
126
+ name: 'Supply and Demand with Surplus',
127
+ description: 'Supply and demand model showing consumer surplus and producer surplus areas.',
128
+ category: 'supply_demand',
129
+ level: 'high_school',
130
+ tags: ['supply', 'demand', 'surplus', 'consumer surplus', 'producer surplus', 'welfare'],
131
+ parameters: {
132
+ demandIntercept: {
133
+ type: 'number',
134
+ default: 100,
135
+ min: 50,
136
+ max: 200,
137
+ description: 'Y-intercept of the demand curve',
138
+ },
139
+ demandSlope: {
140
+ type: 'number',
141
+ default: 1,
142
+ min: 0.1,
143
+ max: 5,
144
+ description: 'Slope of the demand curve',
145
+ },
146
+ supplyIntercept: {
147
+ type: 'number',
148
+ default: 10,
149
+ min: 0,
150
+ max: 50,
151
+ description: 'Y-intercept of the supply curve',
152
+ },
153
+ supplySlope: {
154
+ type: 'number',
155
+ default: 1,
156
+ min: 0.1,
157
+ max: 5,
158
+ description: 'Slope of the supply curve',
159
+ },
160
+ },
161
+ generate: (params) => {
162
+ const demandIntercept = params.demandIntercept ?? 100;
163
+ const demandSlope = params.demandSlope ?? 1;
164
+ const supplyIntercept = params.supplyIntercept ?? 10;
165
+ const supplySlope = params.supplySlope ?? 1;
166
+ return {
167
+ metadata: {
168
+ specVersion: '1.3',
169
+ title: 'Supply and Demand with Surplus',
170
+ description: 'Market equilibrium showing consumer and producer surplus',
171
+ },
172
+ parameters: {
173
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept', min: 50, max: 200 },
174
+ demandSlope: { value: demandSlope, label: 'Demand Slope', min: 0.1, max: 5, step: 0.1 },
175
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept', min: 0, max: 50 },
176
+ supplySlope: { value: supplySlope, label: 'Supply Slope', min: 0.1, max: 5, step: 0.1 },
177
+ eqQ: {
178
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
179
+ hidden: true,
180
+ },
181
+ eqP: {
182
+ expression: 'supplyIntercept + supplySlope * eqQ',
183
+ hidden: true,
184
+ },
185
+ CS: {
186
+ expression: '0.5 * (demandIntercept - eqP) * eqQ',
187
+ label: 'Consumer Surplus',
188
+ readonly: true,
189
+ },
190
+ PS: {
191
+ expression: '0.5 * (eqP - supplyIntercept) * eqQ',
192
+ label: 'Producer Surplus',
193
+ readonly: true,
194
+ },
195
+ TS: {
196
+ expression: 'CS + PS',
197
+ label: 'Total Surplus',
198
+ readonly: true,
199
+ },
200
+ },
201
+ charts: [
202
+ {
203
+ id: 'main',
204
+ title: 'Consumer and Producer Surplus',
205
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
206
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
207
+ elements: [
208
+ {
209
+ id: 'cs-area',
210
+ type: 'area',
211
+ topBoundary: 'demand',
212
+ bottomBoundary: 'eqP',
213
+ leftBoundary: '0',
214
+ rightBoundary: 'eqQ',
215
+ color: '#3B82F6',
216
+ opacity: 0.3,
217
+ label: 'Consumer Surplus',
218
+ },
219
+ {
220
+ id: 'ps-area',
221
+ type: 'area',
222
+ topBoundary: 'eqP',
223
+ bottomBoundary: 'supply',
224
+ leftBoundary: '0',
225
+ rightBoundary: 'eqQ',
226
+ color: '#EF4444',
227
+ opacity: 0.3,
228
+ label: 'Producer Surplus',
229
+ },
230
+ {
231
+ id: 'demand',
232
+ type: 'line',
233
+ equation: 'demandIntercept - demandSlope * x',
234
+ color: '#2563EB',
235
+ label: 'Demand',
236
+ },
237
+ {
238
+ id: 'supply',
239
+ type: 'line',
240
+ equation: 'supplyIntercept + supplySlope * x',
241
+ color: '#DC2626',
242
+ label: 'Supply',
243
+ },
244
+ {
245
+ id: 'equilibrium',
246
+ type: 'point',
247
+ x: 'eqQ',
248
+ y: 'eqP',
249
+ color: '#16A34A',
250
+ label: 'E',
251
+ droplines: { x: true, y: true },
252
+ },
253
+ ],
254
+ annotations: [
255
+ {
256
+ id: 'cs-label',
257
+ text: 'CS = ${CS:,.0f}',
258
+ x: 'eqQ / 3',
259
+ y: '(demandIntercept + eqP) / 2',
260
+ color: '#1E40AF',
261
+ },
262
+ {
263
+ id: 'ps-label',
264
+ text: 'PS = ${PS:,.0f}',
265
+ x: 'eqQ / 3',
266
+ y: '(supplyIntercept + eqP) / 2',
267
+ color: '#991B1B',
268
+ },
269
+ ],
270
+ },
271
+ ],
272
+ };
273
+ },
274
+ };
275
+ /**
276
+ * Supply and Demand with Tax
277
+ */
278
+ const supplyDemandTax = {
279
+ id: 'supply_demand_tax',
280
+ name: 'Supply and Demand with Tax',
281
+ description: 'Supply and demand model showing the effect of a per-unit tax, including tax incidence, deadweight loss, and tax revenue.',
282
+ category: 'supply_demand',
283
+ level: 'undergraduate',
284
+ tags: ['supply', 'demand', 'tax', 'deadweight loss', 'incidence', 'welfare'],
285
+ parameters: {
286
+ demandIntercept: {
287
+ type: 'number',
288
+ default: 100,
289
+ min: 50,
290
+ max: 200,
291
+ description: 'Y-intercept of the demand curve',
292
+ },
293
+ demandSlope: {
294
+ type: 'number',
295
+ default: 1,
296
+ min: 0.1,
297
+ max: 5,
298
+ description: 'Slope of the demand curve',
299
+ },
300
+ supplyIntercept: {
301
+ type: 'number',
302
+ default: 10,
303
+ min: 0,
304
+ max: 50,
305
+ description: 'Y-intercept of the supply curve',
306
+ },
307
+ supplySlope: {
308
+ type: 'number',
309
+ default: 1,
310
+ min: 0.1,
311
+ max: 5,
312
+ description: 'Slope of the supply curve',
313
+ },
314
+ taxAmount: {
315
+ type: 'number',
316
+ default: 20,
317
+ min: 0,
318
+ max: 50,
319
+ description: 'Per-unit tax amount',
320
+ },
321
+ },
322
+ generate: (params) => {
323
+ const demandIntercept = params.demandIntercept ?? 100;
324
+ const demandSlope = params.demandSlope ?? 1;
325
+ const supplyIntercept = params.supplyIntercept ?? 10;
326
+ const supplySlope = params.supplySlope ?? 1;
327
+ const taxAmount = params.taxAmount ?? 20;
328
+ return {
329
+ metadata: {
330
+ specVersion: '1.3',
331
+ title: 'Tax Incidence and Deadweight Loss',
332
+ description: 'Per-unit tax showing incidence, revenue, and deadweight loss',
333
+ },
334
+ parameters: {
335
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept' },
336
+ demandSlope: { value: demandSlope, label: 'Demand Slope', step: 0.1 },
337
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept' },
338
+ supplySlope: { value: supplySlope, label: 'Supply Slope', step: 0.1 },
339
+ tax: { value: taxAmount, label: 'Tax Amount', min: 0, max: 50 },
340
+ // Pre-tax equilibrium
341
+ Q0: {
342
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
343
+ hidden: true,
344
+ },
345
+ P0: {
346
+ expression: 'supplyIntercept + supplySlope * Q0',
347
+ hidden: true,
348
+ },
349
+ // Post-tax equilibrium
350
+ Q1: {
351
+ expression: '(demandIntercept - supplyIntercept - tax) / (demandSlope + supplySlope)',
352
+ hidden: true,
353
+ },
354
+ Pb: {
355
+ expression: 'demandIntercept - demandSlope * Q1',
356
+ label: 'Buyer Price',
357
+ readonly: true,
358
+ },
359
+ Ps: {
360
+ expression: 'Pb - tax',
361
+ label: 'Seller Price',
362
+ readonly: true,
363
+ },
364
+ taxRevenue: {
365
+ expression: 'tax * Q1',
366
+ label: 'Tax Revenue',
367
+ readonly: true,
368
+ },
369
+ DWL: {
370
+ expression: '0.5 * tax * (Q0 - Q1)',
371
+ label: 'Deadweight Loss',
372
+ readonly: true,
373
+ },
374
+ },
375
+ charts: [
376
+ {
377
+ id: 'main',
378
+ title: 'Tax Incidence',
379
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
380
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
381
+ elements: [
382
+ // Tax revenue rectangle
383
+ {
384
+ id: 'tax-revenue',
385
+ type: 'area',
386
+ topBoundary: 'Pb',
387
+ bottomBoundary: 'Ps',
388
+ leftBoundary: '0',
389
+ rightBoundary: 'Q1',
390
+ color: '#22C55E',
391
+ opacity: 0.3,
392
+ label: 'Tax Revenue',
393
+ },
394
+ // Deadweight loss triangle
395
+ {
396
+ id: 'dwl-area',
397
+ type: 'area',
398
+ topBoundary: 'demand',
399
+ bottomBoundary: 'supply',
400
+ leftBoundary: 'Q1',
401
+ rightBoundary: 'Q0',
402
+ color: '#A855F7',
403
+ opacity: 0.4,
404
+ label: 'Deadweight Loss',
405
+ },
406
+ // Curves
407
+ {
408
+ id: 'demand',
409
+ type: 'line',
410
+ equation: 'demandIntercept - demandSlope * x',
411
+ color: '#2563EB',
412
+ label: 'Demand',
413
+ },
414
+ {
415
+ id: 'supply',
416
+ type: 'line',
417
+ equation: 'supplyIntercept + supplySlope * x',
418
+ color: '#DC2626',
419
+ label: 'Supply',
420
+ },
421
+ {
422
+ id: 'supply-tax',
423
+ type: 'line',
424
+ equation: 'supplyIntercept + tax + supplySlope * x',
425
+ color: '#DC2626',
426
+ lineStyle: 'dashed',
427
+ label: 'Supply + Tax',
428
+ },
429
+ // Points
430
+ {
431
+ id: 'eq-original',
432
+ type: 'point',
433
+ x: 'Q0',
434
+ y: 'P0',
435
+ color: '#6B7280',
436
+ label: 'E₀',
437
+ },
438
+ {
439
+ id: 'buyer-point',
440
+ type: 'point',
441
+ x: 'Q1',
442
+ y: 'Pb',
443
+ color: '#2563EB',
444
+ label: 'Pb',
445
+ droplines: { x: 'Q₁', y: true },
446
+ },
447
+ {
448
+ id: 'seller-point',
449
+ type: 'point',
450
+ x: 'Q1',
451
+ y: 'Ps',
452
+ color: '#DC2626',
453
+ label: 'Ps',
454
+ droplines: { y: true },
455
+ },
456
+ // Tax wedge
457
+ {
458
+ id: 'tax-wedge',
459
+ type: 'segment',
460
+ x1: 'Q1',
461
+ y1: 'Ps',
462
+ x2: 'Q1',
463
+ y2: 'Pb',
464
+ color: '#16A34A',
465
+ strokeWidth: 3,
466
+ },
467
+ ],
468
+ annotations: [
469
+ {
470
+ id: 'tax-label',
471
+ text: 't = ${tax}',
472
+ x: 'Q1 + 5',
473
+ y: '(Pb + Ps) / 2',
474
+ color: '#16A34A',
475
+ },
476
+ ],
477
+ },
478
+ ],
479
+ };
480
+ },
481
+ };
482
+ /**
483
+ * Price Ceiling
484
+ */
485
+ const supplyDemandPriceCeiling = {
486
+ id: 'supply_demand_price_ceiling',
487
+ name: 'Price Ceiling',
488
+ description: 'Supply and demand model showing the effect of a binding price ceiling, creating a shortage.',
489
+ category: 'supply_demand',
490
+ level: 'high_school',
491
+ tags: ['supply', 'demand', 'price ceiling', 'price control', 'shortage'],
492
+ parameters: {
493
+ demandIntercept: {
494
+ type: 'number',
495
+ default: 100,
496
+ description: 'Y-intercept of the demand curve',
497
+ },
498
+ demandSlope: {
499
+ type: 'number',
500
+ default: 1,
501
+ description: 'Slope of the demand curve',
502
+ },
503
+ supplyIntercept: {
504
+ type: 'number',
505
+ default: 10,
506
+ description: 'Y-intercept of the supply curve',
507
+ },
508
+ supplySlope: {
509
+ type: 'number',
510
+ default: 1,
511
+ description: 'Slope of the supply curve',
512
+ },
513
+ priceCeiling: {
514
+ type: 'number',
515
+ default: 40,
516
+ description: 'Maximum allowed price',
517
+ },
518
+ },
519
+ generate: (params) => {
520
+ const demandIntercept = params.demandIntercept ?? 100;
521
+ const demandSlope = params.demandSlope ?? 1;
522
+ const supplyIntercept = params.supplyIntercept ?? 10;
523
+ const supplySlope = params.supplySlope ?? 1;
524
+ const priceCeiling = params.priceCeiling ?? 40;
525
+ return {
526
+ metadata: {
527
+ specVersion: '1.3',
528
+ title: 'Price Ceiling',
529
+ description: 'Market with a binding price ceiling creating a shortage',
530
+ },
531
+ parameters: {
532
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept' },
533
+ demandSlope: { value: demandSlope, label: 'Demand Slope' },
534
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept' },
535
+ supplySlope: { value: supplySlope, label: 'Supply Slope' },
536
+ Pc: { value: priceCeiling, label: 'Price Ceiling', min: 0, max: 80 },
537
+ eqQ: {
538
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
539
+ hidden: true,
540
+ },
541
+ eqP: {
542
+ expression: 'supplyIntercept + supplySlope * eqQ',
543
+ hidden: true,
544
+ },
545
+ Qs: {
546
+ expression: 'max(0, (Pc - supplyIntercept) / supplySlope)',
547
+ label: 'Quantity Supplied',
548
+ readonly: true,
549
+ },
550
+ Qd: {
551
+ expression: 'max(0, (demandIntercept - Pc) / demandSlope)',
552
+ label: 'Quantity Demanded',
553
+ readonly: true,
554
+ },
555
+ shortage: {
556
+ expression: 'max(0, Qd - Qs)',
557
+ label: 'Shortage',
558
+ readonly: true,
559
+ },
560
+ isBinding: {
561
+ expression: 'Pc < eqP',
562
+ hidden: true,
563
+ },
564
+ },
565
+ charts: [
566
+ {
567
+ id: 'main',
568
+ title: 'Price Ceiling and Shortage',
569
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
570
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
571
+ elements: [
572
+ // Shortage bracket
573
+ {
574
+ id: 'shortage-bracket',
575
+ type: 'bracket',
576
+ axis: 'x',
577
+ from: 'Qs',
578
+ to: 'Qd',
579
+ label: 'Shortage',
580
+ style: 'brace',
581
+ position: 'outside',
582
+ visible: 'isBinding',
583
+ },
584
+ // Curves
585
+ {
586
+ id: 'demand',
587
+ type: 'line',
588
+ equation: 'demandIntercept - demandSlope * x',
589
+ color: '#2563EB',
590
+ label: 'Demand',
591
+ },
592
+ {
593
+ id: 'supply',
594
+ type: 'line',
595
+ equation: 'supplyIntercept + supplySlope * x',
596
+ color: '#DC2626',
597
+ label: 'Supply',
598
+ },
599
+ // Price ceiling line
600
+ {
601
+ id: 'ceiling-line',
602
+ type: 'horizontalLine',
603
+ y: 'Pc',
604
+ color: '#7C3AED',
605
+ strokeDasharray: '5,5',
606
+ },
607
+ // Equilibrium
608
+ {
609
+ id: 'equilibrium',
610
+ type: 'point',
611
+ x: 'eqQ',
612
+ y: 'eqP',
613
+ color: '#6B7280',
614
+ label: 'E*',
615
+ },
616
+ // Points at ceiling
617
+ {
618
+ id: 'supply-at-ceiling',
619
+ type: 'point',
620
+ x: 'Qs',
621
+ y: 'Pc',
622
+ color: '#DC2626',
623
+ label: 'Qs',
624
+ droplines: { x: true },
625
+ visible: 'isBinding',
626
+ },
627
+ {
628
+ id: 'demand-at-ceiling',
629
+ type: 'point',
630
+ x: 'Qd',
631
+ y: 'Pc',
632
+ color: '#2563EB',
633
+ label: 'Qd',
634
+ droplines: { x: true },
635
+ visible: 'isBinding',
636
+ },
637
+ ],
638
+ annotations: [
639
+ {
640
+ id: 'ceiling-label',
641
+ text: 'Price Ceiling = ${Pc}',
642
+ x: 'demandIntercept * 0.7',
643
+ y: 'Pc + 5',
644
+ color: '#7C3AED',
645
+ },
646
+ ],
647
+ },
648
+ ],
649
+ };
650
+ },
651
+ };
652
+ /**
653
+ * Price Floor
654
+ */
655
+ const supplyDemandPriceFloor = {
656
+ id: 'supply_demand_price_floor',
657
+ name: 'Price Floor',
658
+ description: 'Supply and demand model showing the effect of a binding price floor, creating a surplus.',
659
+ category: 'supply_demand',
660
+ level: 'high_school',
661
+ tags: ['supply', 'demand', 'price floor', 'price control', 'surplus', 'minimum wage'],
662
+ parameters: {
663
+ demandIntercept: {
664
+ type: 'number',
665
+ default: 100,
666
+ description: 'Y-intercept of the demand curve',
667
+ },
668
+ demandSlope: {
669
+ type: 'number',
670
+ default: 1,
671
+ description: 'Slope of the demand curve',
672
+ },
673
+ supplyIntercept: {
674
+ type: 'number',
675
+ default: 10,
676
+ description: 'Y-intercept of the supply curve',
677
+ },
678
+ supplySlope: {
679
+ type: 'number',
680
+ default: 1,
681
+ description: 'Slope of the supply curve',
682
+ },
683
+ priceFloor: {
684
+ type: 'number',
685
+ default: 70,
686
+ description: 'Minimum allowed price',
687
+ },
688
+ },
689
+ generate: (params) => {
690
+ const demandIntercept = params.demandIntercept ?? 100;
691
+ const demandSlope = params.demandSlope ?? 1;
692
+ const supplyIntercept = params.supplyIntercept ?? 10;
693
+ const supplySlope = params.supplySlope ?? 1;
694
+ const priceFloor = params.priceFloor ?? 70;
695
+ return {
696
+ metadata: {
697
+ specVersion: '1.3',
698
+ title: 'Price Floor',
699
+ description: 'Market with a binding price floor creating a surplus',
700
+ },
701
+ parameters: {
702
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept' },
703
+ demandSlope: { value: demandSlope, label: 'Demand Slope' },
704
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept' },
705
+ supplySlope: { value: supplySlope, label: 'Supply Slope' },
706
+ Pf: { value: priceFloor, label: 'Price Floor', min: 20, max: 90 },
707
+ eqQ: {
708
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
709
+ hidden: true,
710
+ },
711
+ eqP: {
712
+ expression: 'supplyIntercept + supplySlope * eqQ',
713
+ hidden: true,
714
+ },
715
+ Qd: {
716
+ expression: 'max(0, (demandIntercept - Pf) / demandSlope)',
717
+ label: 'Quantity Demanded',
718
+ readonly: true,
719
+ },
720
+ Qs: {
721
+ expression: 'max(0, (Pf - supplyIntercept) / supplySlope)',
722
+ label: 'Quantity Supplied',
723
+ readonly: true,
724
+ },
725
+ surplus: {
726
+ expression: 'max(0, Qs - Qd)',
727
+ label: 'Surplus',
728
+ readonly: true,
729
+ },
730
+ isBinding: {
731
+ expression: 'Pf > eqP',
732
+ hidden: true,
733
+ },
734
+ },
735
+ charts: [
736
+ {
737
+ id: 'main',
738
+ title: 'Price Floor and Surplus',
739
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
740
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
741
+ elements: [
742
+ {
743
+ id: 'surplus-bracket',
744
+ type: 'bracket',
745
+ axis: 'x',
746
+ from: 'Qd',
747
+ to: 'Qs',
748
+ label: 'Surplus',
749
+ style: 'brace',
750
+ position: 'outside',
751
+ visible: 'isBinding',
752
+ },
753
+ {
754
+ id: 'demand',
755
+ type: 'line',
756
+ equation: 'demandIntercept - demandSlope * x',
757
+ color: '#2563EB',
758
+ label: 'Demand',
759
+ },
760
+ {
761
+ id: 'supply',
762
+ type: 'line',
763
+ equation: 'supplyIntercept + supplySlope * x',
764
+ color: '#DC2626',
765
+ label: 'Supply',
766
+ },
767
+ {
768
+ id: 'floor-line',
769
+ type: 'horizontalLine',
770
+ y: 'Pf',
771
+ color: '#7C3AED',
772
+ strokeDasharray: '5,5',
773
+ },
774
+ {
775
+ id: 'equilibrium',
776
+ type: 'point',
777
+ x: 'eqQ',
778
+ y: 'eqP',
779
+ color: '#6B7280',
780
+ label: 'E*',
781
+ },
782
+ {
783
+ id: 'demand-at-floor',
784
+ type: 'point',
785
+ x: 'Qd',
786
+ y: 'Pf',
787
+ color: '#2563EB',
788
+ label: 'Qd',
789
+ droplines: { x: true },
790
+ visible: 'isBinding',
791
+ },
792
+ {
793
+ id: 'supply-at-floor',
794
+ type: 'point',
795
+ x: 'Qs',
796
+ y: 'Pf',
797
+ color: '#DC2626',
798
+ label: 'Qs',
799
+ droplines: { x: true },
800
+ visible: 'isBinding',
801
+ },
802
+ ],
803
+ annotations: [
804
+ {
805
+ id: 'floor-label',
806
+ text: 'Price Floor = ${Pf}',
807
+ x: 'demandIntercept * 0.7',
808
+ y: 'Pf + 5',
809
+ color: '#7C3AED',
810
+ },
811
+ ],
812
+ },
813
+ ],
814
+ };
815
+ },
816
+ };
817
+ /**
818
+ * Subsidy
819
+ */
820
+ const supplyDemandSubsidy = {
821
+ id: 'supply_demand_subsidy',
822
+ name: 'Supply and Demand with Subsidy',
823
+ description: 'Supply and demand model showing the effect of a per-unit subsidy on market outcomes.',
824
+ category: 'supply_demand',
825
+ level: 'undergraduate',
826
+ tags: ['supply', 'demand', 'subsidy', 'government intervention', 'welfare'],
827
+ parameters: {
828
+ demandIntercept: {
829
+ type: 'number',
830
+ default: 100,
831
+ description: 'Y-intercept of the demand curve',
832
+ },
833
+ demandSlope: {
834
+ type: 'number',
835
+ default: 1,
836
+ description: 'Slope of the demand curve',
837
+ },
838
+ supplyIntercept: {
839
+ type: 'number',
840
+ default: 20,
841
+ description: 'Y-intercept of the supply curve',
842
+ },
843
+ supplySlope: {
844
+ type: 'number',
845
+ default: 1,
846
+ description: 'Slope of the supply curve',
847
+ },
848
+ subsidyAmount: {
849
+ type: 'number',
850
+ default: 20,
851
+ description: 'Per-unit subsidy amount',
852
+ },
853
+ },
854
+ generate: (params) => {
855
+ const demandIntercept = params.demandIntercept ?? 100;
856
+ const demandSlope = params.demandSlope ?? 1;
857
+ const supplyIntercept = params.supplyIntercept ?? 20;
858
+ const supplySlope = params.supplySlope ?? 1;
859
+ const subsidyAmount = params.subsidyAmount ?? 20;
860
+ return {
861
+ metadata: {
862
+ specVersion: '1.3',
863
+ title: 'Per-Unit Subsidy',
864
+ description: 'Market effects of a government subsidy to producers',
865
+ },
866
+ parameters: {
867
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept' },
868
+ demandSlope: { value: demandSlope, label: 'Demand Slope', step: 0.1 },
869
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept' },
870
+ supplySlope: { value: supplySlope, label: 'Supply Slope', step: 0.1 },
871
+ subsidy: { value: subsidyAmount, label: 'Subsidy Amount', min: 0, max: 50 },
872
+ Q0: {
873
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
874
+ hidden: true,
875
+ },
876
+ P0: {
877
+ expression: 'supplyIntercept + supplySlope * Q0',
878
+ hidden: true,
879
+ },
880
+ Q1: {
881
+ expression: '(demandIntercept - supplyIntercept + subsidy) / (demandSlope + supplySlope)',
882
+ hidden: true,
883
+ },
884
+ Pb: {
885
+ expression: 'demandIntercept - demandSlope * Q1',
886
+ label: 'Buyer Price',
887
+ readonly: true,
888
+ },
889
+ Ps: {
890
+ expression: 'Pb + subsidy',
891
+ label: 'Seller Price',
892
+ readonly: true,
893
+ },
894
+ govCost: {
895
+ expression: 'subsidy * Q1',
896
+ label: 'Government Cost',
897
+ readonly: true,
898
+ },
899
+ },
900
+ charts: [
901
+ {
902
+ id: 'main',
903
+ title: 'Subsidy Effects',
904
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
905
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
906
+ elements: [
907
+ {
908
+ id: 'gov-cost-area',
909
+ type: 'area',
910
+ topBoundary: 'Ps',
911
+ bottomBoundary: 'Pb',
912
+ leftBoundary: '0',
913
+ rightBoundary: 'Q1',
914
+ color: '#F59E0B',
915
+ opacity: 0.3,
916
+ label: 'Government Cost',
917
+ },
918
+ {
919
+ id: 'demand',
920
+ type: 'line',
921
+ equation: 'demandIntercept - demandSlope * x',
922
+ color: '#2563EB',
923
+ label: 'Demand',
924
+ },
925
+ {
926
+ id: 'supply',
927
+ type: 'line',
928
+ equation: 'supplyIntercept + supplySlope * x',
929
+ color: '#DC2626',
930
+ label: 'Supply',
931
+ },
932
+ {
933
+ id: 'supply-subsidy',
934
+ type: 'line',
935
+ equation: 'supplyIntercept - subsidy + supplySlope * x',
936
+ color: '#DC2626',
937
+ lineStyle: 'dashed',
938
+ label: 'Supply - Subsidy',
939
+ },
940
+ {
941
+ id: 'eq-original',
942
+ type: 'point',
943
+ x: 'Q0',
944
+ y: 'P0',
945
+ color: '#6B7280',
946
+ label: 'E₀',
947
+ },
948
+ {
949
+ id: 'buyer-point',
950
+ type: 'point',
951
+ x: 'Q1',
952
+ y: 'Pb',
953
+ color: '#2563EB',
954
+ label: 'Pb',
955
+ droplines: { x: 'Q₁', y: true },
956
+ },
957
+ {
958
+ id: 'seller-point',
959
+ type: 'point',
960
+ x: 'Q1',
961
+ y: 'Ps',
962
+ color: '#DC2626',
963
+ label: 'Ps',
964
+ droplines: { y: true },
965
+ },
966
+ ],
967
+ },
968
+ ],
969
+ };
970
+ },
971
+ };
972
+ /**
973
+ * Quota
974
+ */
975
+ const supplyDemandQuota = {
976
+ id: 'supply_demand_quota',
977
+ name: 'Supply and Demand with Quota',
978
+ description: 'Supply and demand model showing the effect of a quantity restriction (quota) on market outcomes.',
979
+ category: 'supply_demand',
980
+ level: 'undergraduate',
981
+ tags: ['supply', 'demand', 'quota', 'quantity restriction', 'deadweight loss'],
982
+ parameters: {
983
+ demandIntercept: {
984
+ type: 'number',
985
+ default: 100,
986
+ description: 'Y-intercept of the demand curve',
987
+ },
988
+ demandSlope: {
989
+ type: 'number',
990
+ default: 1,
991
+ description: 'Slope of the demand curve',
992
+ },
993
+ supplyIntercept: {
994
+ type: 'number',
995
+ default: 10,
996
+ description: 'Y-intercept of the supply curve',
997
+ },
998
+ supplySlope: {
999
+ type: 'number',
1000
+ default: 1,
1001
+ description: 'Slope of the supply curve',
1002
+ },
1003
+ quotaQuantity: {
1004
+ type: 'number',
1005
+ default: 30,
1006
+ description: 'Maximum allowed quantity',
1007
+ },
1008
+ },
1009
+ generate: (params) => {
1010
+ const demandIntercept = params.demandIntercept ?? 100;
1011
+ const demandSlope = params.demandSlope ?? 1;
1012
+ const supplyIntercept = params.supplyIntercept ?? 10;
1013
+ const supplySlope = params.supplySlope ?? 1;
1014
+ const quotaQuantity = params.quotaQuantity ?? 30;
1015
+ return {
1016
+ metadata: {
1017
+ specVersion: '1.3',
1018
+ title: 'Quantity Quota',
1019
+ description: 'Market effects of a binding quantity restriction',
1020
+ },
1021
+ parameters: {
1022
+ demandIntercept: { value: demandIntercept, label: 'Demand Intercept' },
1023
+ demandSlope: { value: demandSlope, label: 'Demand Slope' },
1024
+ supplyIntercept: { value: supplyIntercept, label: 'Supply Intercept' },
1025
+ supplySlope: { value: supplySlope, label: 'Supply Slope' },
1026
+ Qbar: { value: quotaQuantity, label: 'Quota Quantity', min: 10, max: 80 },
1027
+ eqQ: {
1028
+ expression: '(demandIntercept - supplyIntercept) / (demandSlope + supplySlope)',
1029
+ hidden: true,
1030
+ },
1031
+ eqP: {
1032
+ expression: 'supplyIntercept + supplySlope * eqQ',
1033
+ hidden: true,
1034
+ },
1035
+ Pd: {
1036
+ expression: 'demandIntercept - demandSlope * Qbar',
1037
+ label: 'Demand Price',
1038
+ readonly: true,
1039
+ },
1040
+ Ps: {
1041
+ expression: 'supplyIntercept + supplySlope * Qbar',
1042
+ label: 'Supply Price',
1043
+ readonly: true,
1044
+ },
1045
+ quotaRent: {
1046
+ expression: '(Pd - Ps) * Qbar',
1047
+ label: 'Quota Rent',
1048
+ readonly: true,
1049
+ },
1050
+ isBinding: {
1051
+ expression: 'Qbar < eqQ',
1052
+ hidden: true,
1053
+ },
1054
+ DWL: {
1055
+ expression: '0.5 * (Pd - Ps) * (eqQ - Qbar)',
1056
+ label: 'Deadweight Loss',
1057
+ readonly: true,
1058
+ },
1059
+ },
1060
+ charts: [
1061
+ {
1062
+ id: 'main',
1063
+ title: 'Quota and Deadweight Loss',
1064
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'demandIntercept' },
1065
+ yAxis: { label: 'Price (P)', min: 0, max: 'demandIntercept' },
1066
+ elements: [
1067
+ {
1068
+ id: 'quota-rent-area',
1069
+ type: 'area',
1070
+ topBoundary: 'Pd',
1071
+ bottomBoundary: 'Ps',
1072
+ leftBoundary: '0',
1073
+ rightBoundary: 'Qbar',
1074
+ color: '#22C55E',
1075
+ opacity: 0.3,
1076
+ label: 'Quota Rent',
1077
+ visible: 'isBinding',
1078
+ },
1079
+ {
1080
+ id: 'dwl-area',
1081
+ type: 'area',
1082
+ topBoundary: 'demand',
1083
+ bottomBoundary: 'supply',
1084
+ leftBoundary: 'Qbar',
1085
+ rightBoundary: 'eqQ',
1086
+ color: '#A855F7',
1087
+ opacity: 0.4,
1088
+ label: 'Deadweight Loss',
1089
+ visible: 'isBinding',
1090
+ },
1091
+ {
1092
+ id: 'demand',
1093
+ type: 'line',
1094
+ equation: 'demandIntercept - demandSlope * x',
1095
+ color: '#2563EB',
1096
+ label: 'Demand',
1097
+ },
1098
+ {
1099
+ id: 'supply',
1100
+ type: 'line',
1101
+ equation: 'supplyIntercept + supplySlope * x',
1102
+ color: '#DC2626',
1103
+ label: 'Supply',
1104
+ },
1105
+ {
1106
+ id: 'quota-line',
1107
+ type: 'verticalLine',
1108
+ x: 'Qbar',
1109
+ color: '#7C3AED',
1110
+ strokeDasharray: '5,5',
1111
+ },
1112
+ {
1113
+ id: 'equilibrium',
1114
+ type: 'point',
1115
+ x: 'eqQ',
1116
+ y: 'eqP',
1117
+ color: '#6B7280',
1118
+ label: 'E*',
1119
+ },
1120
+ {
1121
+ id: 'demand-at-quota',
1122
+ type: 'point',
1123
+ x: 'Qbar',
1124
+ y: 'Pd',
1125
+ color: '#2563EB',
1126
+ label: 'Pd',
1127
+ droplines: { y: true },
1128
+ visible: 'isBinding',
1129
+ },
1130
+ {
1131
+ id: 'supply-at-quota',
1132
+ type: 'point',
1133
+ x: 'Qbar',
1134
+ y: 'Ps',
1135
+ color: '#DC2626',
1136
+ label: 'Ps',
1137
+ droplines: { y: true },
1138
+ visible: 'isBinding',
1139
+ },
1140
+ ],
1141
+ annotations: [
1142
+ {
1143
+ id: 'quota-label',
1144
+ text: 'Quota = ${Qbar}',
1145
+ x: 'Qbar + 3',
1146
+ y: 'demandIntercept * 0.8',
1147
+ color: '#7C3AED',
1148
+ },
1149
+ ],
1150
+ },
1151
+ ],
1152
+ };
1153
+ },
1154
+ };
1155
+ /**
1156
+ * Comparative Statics (Shift)
1157
+ */
1158
+ const supplyDemandShift = {
1159
+ id: 'supply_demand_shift',
1160
+ name: 'Supply and Demand Shift',
1161
+ description: 'Demonstrates comparative statics by showing how shifts in supply or demand affect equilibrium.',
1162
+ category: 'supply_demand',
1163
+ level: 'high_school',
1164
+ tags: ['supply', 'demand', 'shift', 'comparative statics', 'equilibrium'],
1165
+ parameters: {
1166
+ demandIntercept: {
1167
+ type: 'number',
1168
+ default: 100,
1169
+ description: 'Original demand intercept',
1170
+ },
1171
+ demandShift: {
1172
+ type: 'number',
1173
+ default: 20,
1174
+ description: 'Shift in demand (positive = right shift)',
1175
+ },
1176
+ supplyIntercept: {
1177
+ type: 'number',
1178
+ default: 10,
1179
+ description: 'Original supply intercept',
1180
+ },
1181
+ supplyShift: {
1182
+ type: 'number',
1183
+ default: 0,
1184
+ description: 'Shift in supply (positive = right shift, i.e., lower intercept)',
1185
+ },
1186
+ },
1187
+ generate: (params) => {
1188
+ const demandIntercept = params.demandIntercept ?? 100;
1189
+ const demandShift = params.demandShift ?? 20;
1190
+ const supplyIntercept = params.supplyIntercept ?? 10;
1191
+ const supplyShift = params.supplyShift ?? 0;
1192
+ return {
1193
+ metadata: {
1194
+ specVersion: '1.3',
1195
+ title: 'Comparative Statics',
1196
+ description: 'Effects of supply and demand shifts on equilibrium',
1197
+ },
1198
+ parameters: {
1199
+ a0: { value: demandIntercept, label: 'Original Demand Intercept' },
1200
+ dShift: { value: demandShift, label: 'Demand Shift', min: -50, max: 50 },
1201
+ c0: { value: supplyIntercept, label: 'Original Supply Intercept' },
1202
+ sShift: { value: supplyShift, label: 'Supply Shift', min: -50, max: 50 },
1203
+ a1: { expression: 'a0 + dShift', hidden: true },
1204
+ c1: { expression: 'c0 - sShift', hidden: true },
1205
+ Q0: { expression: '(a0 - c0) / 2', label: 'Original Q*', readonly: true },
1206
+ P0: { expression: 'c0 + Q0', label: 'Original P*', readonly: true },
1207
+ Q1: { expression: '(a1 - c1) / 2', label: 'New Q*', readonly: true },
1208
+ P1: { expression: 'c1 + Q1', label: 'New P*', readonly: true },
1209
+ deltaQ: { expression: 'Q1 - Q0', label: 'Change in Q', readonly: true },
1210
+ deltaP: { expression: 'P1 - P0', label: 'Change in P', readonly: true },
1211
+ },
1212
+ charts: [
1213
+ {
1214
+ id: 'main',
1215
+ title: 'Supply and Demand Shifts',
1216
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'max(a0, a1) + 10' },
1217
+ yAxis: { label: 'Price (P)', min: 0, max: 'max(a0, a1) + 10' },
1218
+ elements: [
1219
+ {
1220
+ id: 'demand-original',
1221
+ type: 'line',
1222
+ equation: 'a0 - x',
1223
+ color: '#93C5FD',
1224
+ lineStyle: 'dashed',
1225
+ label: 'D₀',
1226
+ },
1227
+ {
1228
+ id: 'supply-original',
1229
+ type: 'line',
1230
+ equation: 'c0 + x',
1231
+ color: '#FCA5A5',
1232
+ lineStyle: 'dashed',
1233
+ label: 'S₀',
1234
+ },
1235
+ {
1236
+ id: 'demand-new',
1237
+ type: 'line',
1238
+ equation: 'a1 - x',
1239
+ color: '#2563EB',
1240
+ label: 'D₁',
1241
+ },
1242
+ {
1243
+ id: 'supply-new',
1244
+ type: 'line',
1245
+ equation: 'c1 + x',
1246
+ color: '#DC2626',
1247
+ label: 'S₁',
1248
+ },
1249
+ {
1250
+ id: 'eq-original',
1251
+ type: 'point',
1252
+ x: 'Q0',
1253
+ y: 'P0',
1254
+ color: '#6B7280',
1255
+ label: 'E₀',
1256
+ },
1257
+ {
1258
+ id: 'eq-new',
1259
+ type: 'point',
1260
+ x: 'Q1',
1261
+ y: 'P1',
1262
+ color: '#16A34A',
1263
+ label: 'E₁',
1264
+ droplines: { x: true, y: true },
1265
+ },
1266
+ ],
1267
+ },
1268
+ ],
1269
+ };
1270
+ },
1271
+ };
1272
+ export const supplyDemandTemplates = [
1273
+ supplyDemandBasic,
1274
+ supplyDemandSurplus,
1275
+ supplyDemandTax,
1276
+ supplyDemandPriceCeiling,
1277
+ supplyDemandPriceFloor,
1278
+ supplyDemandSubsidy,
1279
+ supplyDemandQuota,
1280
+ supplyDemandShift,
1281
+ ];
1282
+ //# sourceMappingURL=supplyDemand.js.map