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,997 @@
1
+ /**
2
+ * Externalities and Public Goods Templates
3
+ *
4
+ * Templates for market failures including negative and positive externalities,
5
+ * Pigouvian taxes/subsidies, Coase theorem, and public goods.
6
+ *
7
+ * Phase 3.6: Externalities & Public Goods
8
+ */
9
+ // ============================================================================
10
+ // PHASE 3.6: EXTERNALITIES & PUBLIC GOODS
11
+ // ============================================================================
12
+ /**
13
+ * Negative Externality
14
+ *
15
+ * Market with external costs (pollution). MSC > MPC.
16
+ * Shows market failure: overproduction relative to social optimum.
17
+ */
18
+ const negativeExternality = {
19
+ id: 'negative_externality',
20
+ name: 'Negative Externality',
21
+ description: 'Market with external costs (e.g., pollution). Marginal Social Cost exceeds Marginal Private Cost. Market overproduces relative to socially optimal quantity.',
22
+ category: 'market_structures',
23
+ level: 'undergraduate',
24
+ tags: ['externality', 'negative externality', 'pollution', 'market failure', 'MSC', 'MPC'],
25
+ parameters: {
26
+ demandIntercept: {
27
+ type: 'number',
28
+ default: 100,
29
+ description: 'Demand intercept',
30
+ },
31
+ demandSlope: {
32
+ type: 'number',
33
+ default: 1,
34
+ description: 'Demand slope',
35
+ },
36
+ mpcIntercept: {
37
+ type: 'number',
38
+ default: 10,
39
+ description: 'Marginal private cost intercept',
40
+ },
41
+ mpcSlope: {
42
+ type: 'number',
43
+ default: 1,
44
+ description: 'Marginal private cost slope',
45
+ },
46
+ externalCostPerUnit: {
47
+ type: 'number',
48
+ default: 20,
49
+ description: 'External cost per unit (constant)',
50
+ },
51
+ },
52
+ generate: (params) => {
53
+ const a = params.demandIntercept ?? 100;
54
+ const b = params.demandSlope ?? 1;
55
+ const c0 = params.mpcIntercept ?? 10;
56
+ const c1 = params.mpcSlope ?? 1;
57
+ const EC = params.externalCostPerUnit ?? 20;
58
+ return {
59
+ metadata: {
60
+ specVersion: '1.3',
61
+ title: 'Negative Externality',
62
+ description: 'MSC = MPC + MEC',
63
+ },
64
+ parameters: {
65
+ a: { value: a, label: 'Demand Intercept', min: 50, max: 150 },
66
+ b: { value: b, label: 'Demand Slope', min: 0.5, max: 2, step: 0.1 },
67
+ c0: { value: c0, label: 'MPC Intercept', min: 0, max: 30 },
68
+ c1: { value: c1, label: 'MPC Slope', min: 0.5, max: 2, step: 0.1 },
69
+ EC: { value: EC, label: 'External Cost', min: 5, max: 50 },
70
+ // Market equilibrium (MPC = D)
71
+ Q_market: {
72
+ expression: '(a - c0) / (b + c1)',
73
+ label: 'Q_market',
74
+ readonly: true,
75
+ },
76
+ P_market: {
77
+ expression: 'a - b * Q_market',
78
+ label: 'P_market',
79
+ readonly: true,
80
+ },
81
+ // Social optimum (MSC = D)
82
+ Q_social: {
83
+ expression: '(a - c0 - EC) / (b + c1)',
84
+ label: 'Q_social',
85
+ readonly: true,
86
+ },
87
+ P_social: {
88
+ expression: 'a - b * Q_social',
89
+ label: 'P_social',
90
+ readonly: true,
91
+ },
92
+ // Overproduction
93
+ overproduction: {
94
+ expression: 'Q_market - Q_social',
95
+ label: 'Overproduction',
96
+ readonly: true,
97
+ },
98
+ // Deadweight loss
99
+ DWL: {
100
+ expression: '0.5 * EC * overproduction',
101
+ label: 'DWL',
102
+ readonly: true,
103
+ },
104
+ // Chart bounds
105
+ chartMaxQ: { expression: 'Q_market * 1.3', hidden: true },
106
+ chartMaxP: { expression: 'a * 1.1', hidden: true },
107
+ },
108
+ charts: [
109
+ {
110
+ id: 'main',
111
+ title: 'Negative Externality: Overproduction',
112
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'chartMaxQ' },
113
+ yAxis: { label: 'Price, Cost', min: 0, max: 'chartMaxP' },
114
+ elements: [
115
+ // DWL triangle
116
+ {
117
+ id: 'dwl',
118
+ type: 'polygon',
119
+ points: [
120
+ { x: 'Q_social', y: 'P_social' },
121
+ { x: 'Q_market', y: 'P_market' },
122
+ { x: 'Q_market', y: 'c0 + c1 * Q_market + EC' },
123
+ ],
124
+ fill: '#EF4444',
125
+ opacity: 0.3,
126
+ label: 'DWL',
127
+ },
128
+ // Demand (MPB = MSB for negative externality in production)
129
+ {
130
+ id: 'demand',
131
+ type: 'line',
132
+ equation: 'a - b * x',
133
+ color: '#2563EB',
134
+ strokeWidth: 3,
135
+ label: 'Demand (MPB = MSB)',
136
+ domain: { min: 0, max: 'a / b' },
137
+ },
138
+ // MPC (supply)
139
+ {
140
+ id: 'mpc',
141
+ type: 'line',
142
+ equation: 'c0 + c1 * x',
143
+ color: '#F59E0B',
144
+ strokeWidth: 3,
145
+ label: 'MPC (Supply)',
146
+ },
147
+ // MSC = MPC + MEC
148
+ {
149
+ id: 'msc',
150
+ type: 'line',
151
+ equation: 'c0 + EC + c1 * x',
152
+ color: '#DC2626',
153
+ strokeWidth: 3,
154
+ label: 'MSC = MPC + MEC',
155
+ },
156
+ // External cost arrow
157
+ {
158
+ id: 'ec-arrow',
159
+ type: 'arrow',
160
+ x1: 'Q_market * 0.7',
161
+ y1: 'c0 + c1 * Q_market * 0.7',
162
+ x2: 'Q_market * 0.7',
163
+ y2: 'c0 + EC + c1 * Q_market * 0.7',
164
+ color: '#DC2626',
165
+ },
166
+ // Market equilibrium
167
+ {
168
+ id: 'market-eq',
169
+ type: 'point',
170
+ x: 'Q_market',
171
+ y: 'P_market',
172
+ color: '#F59E0B',
173
+ size: 10,
174
+ label: 'Market Eq.',
175
+ droplines: { x: true, y: true },
176
+ },
177
+ // Social optimum
178
+ {
179
+ id: 'social-opt',
180
+ type: 'point',
181
+ x: 'Q_social',
182
+ y: 'P_social',
183
+ color: '#16A34A',
184
+ size: 10,
185
+ label: 'Social Opt.',
186
+ droplines: { x: true, y: true },
187
+ },
188
+ ],
189
+ annotations: [
190
+ {
191
+ id: 'ec-label',
192
+ text: 'MEC = $${EC}',
193
+ x: 'Q_market * 0.75',
194
+ y: 'c0 + EC / 2 + c1 * Q_market * 0.7',
195
+ color: '#DC2626',
196
+ },
197
+ {
198
+ id: 'dwl-label',
199
+ text: 'DWL = $${DWL:.0f}',
200
+ x: '(Q_social + Q_market) / 2',
201
+ y: '(P_market + c0 + c1 * Q_market + EC) / 2',
202
+ color: '#DC2626',
203
+ },
204
+ {
205
+ id: 'overprod-label',
206
+ text: 'Overproduction = ${overproduction:.1f}',
207
+ x: 'chartMaxQ * 0.6',
208
+ y: 'chartMaxP * 0.15',
209
+ color: '#6B7280',
210
+ },
211
+ ],
212
+ },
213
+ ],
214
+ };
215
+ },
216
+ };
217
+ /**
218
+ * Positive Externality
219
+ *
220
+ * Market with external benefits (education, vaccines). MSB > MPB.
221
+ * Shows market failure: underproduction relative to social optimum.
222
+ */
223
+ const positiveExternality = {
224
+ id: 'positive_externality',
225
+ name: 'Positive Externality',
226
+ description: 'Market with external benefits (e.g., education, vaccines). Marginal Social Benefit exceeds Marginal Private Benefit. Market underproduces.',
227
+ category: 'market_structures',
228
+ level: 'undergraduate',
229
+ tags: ['externality', 'positive externality', 'education', 'market failure', 'MSB', 'MPB'],
230
+ parameters: {
231
+ mpbIntercept: {
232
+ type: 'number',
233
+ default: 100,
234
+ description: 'Marginal private benefit intercept',
235
+ },
236
+ mpbSlope: {
237
+ type: 'number',
238
+ default: 1,
239
+ description: 'MPB slope',
240
+ },
241
+ supplyIntercept: {
242
+ type: 'number',
243
+ default: 10,
244
+ description: 'Supply intercept',
245
+ },
246
+ supplySlope: {
247
+ type: 'number',
248
+ default: 1,
249
+ description: 'Supply slope',
250
+ },
251
+ externalBenefitPerUnit: {
252
+ type: 'number',
253
+ default: 20,
254
+ description: 'External benefit per unit',
255
+ },
256
+ },
257
+ generate: (params) => {
258
+ const a = params.mpbIntercept ?? 100;
259
+ const b = params.mpbSlope ?? 1;
260
+ const c0 = params.supplyIntercept ?? 10;
261
+ const c1 = params.supplySlope ?? 1;
262
+ const EB = params.externalBenefitPerUnit ?? 20;
263
+ return {
264
+ metadata: {
265
+ specVersion: '1.3',
266
+ title: 'Positive Externality',
267
+ description: 'MSB = MPB + MEB',
268
+ },
269
+ parameters: {
270
+ a: { value: a, label: 'MPB Intercept', min: 50, max: 150 },
271
+ b: { value: b, label: 'MPB Slope', min: 0.5, max: 2, step: 0.1 },
272
+ c0: { value: c0, label: 'Supply Intercept', min: 0, max: 30 },
273
+ c1: { value: c1, label: 'Supply Slope', min: 0.5, max: 2, step: 0.1 },
274
+ EB: { value: EB, label: 'External Benefit', min: 5, max: 50 },
275
+ // Market equilibrium (MPB = Supply)
276
+ Q_market: {
277
+ expression: '(a - c0) / (b + c1)',
278
+ label: 'Q_market',
279
+ readonly: true,
280
+ },
281
+ P_market: {
282
+ expression: 'c0 + c1 * Q_market',
283
+ label: 'P_market',
284
+ readonly: true,
285
+ },
286
+ // Social optimum (MSB = Supply)
287
+ Q_social: {
288
+ expression: '(a + EB - c0) / (b + c1)',
289
+ label: 'Q_social',
290
+ readonly: true,
291
+ },
292
+ P_social: {
293
+ expression: 'c0 + c1 * Q_social',
294
+ label: 'P_social',
295
+ readonly: true,
296
+ },
297
+ // Underproduction
298
+ underproduction: {
299
+ expression: 'Q_social - Q_market',
300
+ label: 'Underproduction',
301
+ readonly: true,
302
+ },
303
+ // Deadweight loss
304
+ DWL: {
305
+ expression: '0.5 * EB * underproduction',
306
+ label: 'DWL',
307
+ readonly: true,
308
+ },
309
+ // Chart bounds
310
+ chartMaxQ: { expression: 'Q_social * 1.3', hidden: true },
311
+ chartMaxP: { expression: '(a + EB) * 1.1', hidden: true },
312
+ },
313
+ charts: [
314
+ {
315
+ id: 'main',
316
+ title: 'Positive Externality: Underproduction',
317
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'chartMaxQ' },
318
+ yAxis: { label: 'Price, Benefit', min: 0, max: 'chartMaxP' },
319
+ elements: [
320
+ // DWL triangle
321
+ {
322
+ id: 'dwl',
323
+ type: 'polygon',
324
+ points: [
325
+ { x: 'Q_market', y: 'a - b * Q_market' },
326
+ { x: 'Q_market', y: 'a + EB - b * Q_market' },
327
+ { x: 'Q_social', y: 'P_social' },
328
+ ],
329
+ fill: '#EF4444',
330
+ opacity: 0.3,
331
+ label: 'DWL',
332
+ },
333
+ // MPB (private demand)
334
+ {
335
+ id: 'mpb',
336
+ type: 'line',
337
+ equation: 'a - b * x',
338
+ color: '#F59E0B',
339
+ strokeWidth: 3,
340
+ label: 'MPB (Private Demand)',
341
+ domain: { min: 0, max: 'a / b' },
342
+ },
343
+ // MSB = MPB + MEB
344
+ {
345
+ id: 'msb',
346
+ type: 'line',
347
+ equation: 'a + EB - b * x',
348
+ color: '#16A34A',
349
+ strokeWidth: 3,
350
+ label: 'MSB = MPB + MEB',
351
+ domain: { min: 0, max: '(a + EB) / b' },
352
+ },
353
+ // Supply (MPC = MSC)
354
+ {
355
+ id: 'supply',
356
+ type: 'line',
357
+ equation: 'c0 + c1 * x',
358
+ color: '#2563EB',
359
+ strokeWidth: 3,
360
+ label: 'Supply (MPC = MSC)',
361
+ },
362
+ // External benefit arrow
363
+ {
364
+ id: 'eb-arrow',
365
+ type: 'arrow',
366
+ x1: 'Q_market * 0.6',
367
+ y1: 'a - b * Q_market * 0.6',
368
+ x2: 'Q_market * 0.6',
369
+ y2: 'a + EB - b * Q_market * 0.6',
370
+ color: '#16A34A',
371
+ },
372
+ // Market equilibrium
373
+ {
374
+ id: 'market-eq',
375
+ type: 'point',
376
+ x: 'Q_market',
377
+ y: 'P_market',
378
+ color: '#F59E0B',
379
+ size: 10,
380
+ label: 'Market Eq.',
381
+ droplines: { x: true, y: true },
382
+ },
383
+ // Social optimum
384
+ {
385
+ id: 'social-opt',
386
+ type: 'point',
387
+ x: 'Q_social',
388
+ y: 'P_social',
389
+ color: '#16A34A',
390
+ size: 10,
391
+ label: 'Social Opt.',
392
+ droplines: { x: true, y: true },
393
+ },
394
+ ],
395
+ annotations: [
396
+ {
397
+ id: 'eb-label',
398
+ text: 'MEB = $${EB}',
399
+ x: 'Q_market * 0.65',
400
+ y: 'a + EB / 2 - b * Q_market * 0.6',
401
+ color: '#16A34A',
402
+ },
403
+ {
404
+ id: 'dwl-label',
405
+ text: 'DWL = $${DWL:.0f}',
406
+ x: '(Q_market + Q_social) / 2',
407
+ y: 'a + EB * 0.5 - b * (Q_market + Q_social) / 2',
408
+ color: '#DC2626',
409
+ },
410
+ {
411
+ id: 'underprod-label',
412
+ text: 'Underproduction = ${underproduction:.1f}',
413
+ x: 'chartMaxQ * 0.6',
414
+ y: 'chartMaxP * 0.9',
415
+ color: '#6B7280',
416
+ },
417
+ ],
418
+ },
419
+ ],
420
+ };
421
+ },
422
+ };
423
+ /**
424
+ * Pigouvian Tax
425
+ *
426
+ * Corrective tax on negative externality that internalizes external cost.
427
+ */
428
+ const pigouvianTax = {
429
+ id: 'pigouvian_tax',
430
+ name: 'Pigouvian Tax',
431
+ description: 'Corrective tax equal to marginal external cost. Shifts private supply to align with social cost, achieving efficient outcome.',
432
+ category: 'market_structures',
433
+ level: 'undergraduate',
434
+ tags: ['Pigouvian tax', 'corrective tax', 'externality', 'policy', 'efficiency'],
435
+ parameters: {
436
+ demandIntercept: {
437
+ type: 'number',
438
+ default: 100,
439
+ description: 'Demand intercept',
440
+ },
441
+ demandSlope: {
442
+ type: 'number',
443
+ default: 1,
444
+ description: 'Demand slope',
445
+ },
446
+ mpcIntercept: {
447
+ type: 'number',
448
+ default: 10,
449
+ description: 'MPC intercept',
450
+ },
451
+ mpcSlope: {
452
+ type: 'number',
453
+ default: 1,
454
+ description: 'MPC slope',
455
+ },
456
+ externalCost: {
457
+ type: 'number',
458
+ default: 20,
459
+ description: 'Marginal external cost (= optimal tax)',
460
+ },
461
+ },
462
+ generate: (params) => {
463
+ const a = params.demandIntercept ?? 100;
464
+ const b = params.demandSlope ?? 1;
465
+ const c0 = params.mpcIntercept ?? 10;
466
+ const c1 = params.mpcSlope ?? 1;
467
+ const t = params.externalCost ?? 20;
468
+ return {
469
+ metadata: {
470
+ specVersion: '1.3',
471
+ title: 'Pigouvian Tax',
472
+ description: 't* = MEC',
473
+ },
474
+ parameters: {
475
+ a: { value: a, label: 'Demand Intercept', min: 50, max: 150 },
476
+ b: { value: b, label: 'Demand Slope', min: 0.5, max: 2, step: 0.1 },
477
+ c0: { value: c0, label: 'MPC Intercept', min: 0, max: 30 },
478
+ c1: { value: c1, label: 'MPC Slope', min: 0.5, max: 2, step: 0.1 },
479
+ t: { value: t, label: 'Tax = MEC', min: 5, max: 50 },
480
+ // Before tax (market equilibrium)
481
+ Q_before: { expression: '(a - c0) / (b + c1)', label: 'Q (before)', readonly: true },
482
+ P_before: { expression: 'a - b * Q_before', label: 'P (before)', readonly: true },
483
+ // After tax (efficient equilibrium)
484
+ Q_after: {
485
+ expression: '(a - c0 - t) / (b + c1)',
486
+ label: 'Q (after)',
487
+ readonly: true,
488
+ },
489
+ P_consumer: {
490
+ expression: 'a - b * Q_after',
491
+ label: 'P (consumer)',
492
+ readonly: true,
493
+ },
494
+ P_producer: {
495
+ expression: 'P_consumer - t',
496
+ label: 'P (producer)',
497
+ readonly: true,
498
+ },
499
+ // Tax revenue
500
+ taxRevenue: {
501
+ expression: 't * Q_after',
502
+ label: 'Tax Revenue',
503
+ readonly: true,
504
+ },
505
+ // Reduction in externality
506
+ reduction: {
507
+ expression: 'Q_before - Q_after',
508
+ label: 'Q Reduction',
509
+ readonly: true,
510
+ },
511
+ // Chart bounds
512
+ chartMaxQ: { expression: 'Q_before * 1.3', hidden: true },
513
+ chartMaxP: { expression: 'a * 1.1', hidden: true },
514
+ },
515
+ charts: [
516
+ {
517
+ id: 'main',
518
+ title: 'Pigouvian Tax: t* = MEC',
519
+ xAxis: { label: 'Quantity (Q)', min: 0, max: 'chartMaxQ' },
520
+ yAxis: { label: 'Price, Cost', min: 0, max: 'chartMaxP' },
521
+ elements: [
522
+ // Tax revenue rectangle
523
+ {
524
+ id: 'tax-revenue',
525
+ type: 'rectangle',
526
+ x1: 0,
527
+ y1: 'P_producer',
528
+ x2: 'Q_after',
529
+ y2: 'P_consumer',
530
+ fill: '#7C3AED',
531
+ opacity: 0.3,
532
+ label: 'Tax Revenue',
533
+ },
534
+ // Demand
535
+ {
536
+ id: 'demand',
537
+ type: 'line',
538
+ equation: 'a - b * x',
539
+ color: '#2563EB',
540
+ strokeWidth: 3,
541
+ label: 'Demand',
542
+ domain: { min: 0, max: 'a / b' },
543
+ },
544
+ // MPC (original supply)
545
+ {
546
+ id: 'mpc',
547
+ type: 'line',
548
+ equation: 'c0 + c1 * x',
549
+ color: '#F59E0B',
550
+ strokeWidth: 2,
551
+ lineStyle: 'dashed',
552
+ label: 'MPC',
553
+ },
554
+ // MPC + tax (new supply)
555
+ {
556
+ id: 'mpc-tax',
557
+ type: 'line',
558
+ equation: 'c0 + t + c1 * x',
559
+ color: '#DC2626',
560
+ strokeWidth: 3,
561
+ label: 'MPC + Tax = MSC',
562
+ },
563
+ // Before tax equilibrium
564
+ {
565
+ id: 'before-eq',
566
+ type: 'point',
567
+ x: 'Q_before',
568
+ y: 'P_before',
569
+ color: '#F59E0B',
570
+ size: 8,
571
+ label: 'Before Tax',
572
+ },
573
+ // After tax equilibrium
574
+ {
575
+ id: 'after-eq',
576
+ type: 'point',
577
+ x: 'Q_after',
578
+ y: 'P_consumer',
579
+ color: '#16A34A',
580
+ size: 10,
581
+ label: 'Efficient (After Tax)',
582
+ droplines: { x: true, y: true },
583
+ },
584
+ // Tax wedge
585
+ {
586
+ id: 'tax-wedge',
587
+ type: 'arrow',
588
+ x1: 'Q_after',
589
+ y1: 'P_producer',
590
+ x2: 'Q_after',
591
+ y2: 'P_consumer',
592
+ color: '#7C3AED',
593
+ },
594
+ ],
595
+ annotations: [
596
+ {
597
+ id: 'tax-label',
598
+ text: 'Tax = $${t}',
599
+ x: 'Q_after + chartMaxQ * 0.05',
600
+ y: '(P_consumer + P_producer) / 2',
601
+ color: '#7C3AED',
602
+ },
603
+ {
604
+ id: 'revenue-label',
605
+ text: 'Revenue = $${taxRevenue:.0f}',
606
+ x: 'Q_after / 2',
607
+ y: '(P_consumer + P_producer) / 2',
608
+ color: '#7C3AED',
609
+ },
610
+ {
611
+ id: 'efficiency',
612
+ text: 'Tax internalizes externality → Efficient',
613
+ x: 'chartMaxQ * 0.5',
614
+ y: 'chartMaxP * 0.15',
615
+ color: '#16A34A',
616
+ },
617
+ ],
618
+ },
619
+ ],
620
+ };
621
+ },
622
+ };
623
+ /**
624
+ * Coase Theorem
625
+ *
626
+ * Shows how private bargaining can achieve efficiency
627
+ * regardless of initial property rights allocation.
628
+ */
629
+ const coaseTheorem = {
630
+ id: 'coase_theorem',
631
+ name: 'Coase Theorem',
632
+ description: 'When transaction costs are zero and property rights are well-defined, private bargaining achieves efficient outcome regardless of initial rights allocation.',
633
+ category: 'market_structures',
634
+ level: 'undergraduate',
635
+ tags: ['Coase theorem', 'property rights', 'bargaining', 'externality', 'efficiency'],
636
+ parameters: {
637
+ pollutionLevels: {
638
+ type: 'number',
639
+ default: 10,
640
+ description: 'Maximum pollution units',
641
+ },
642
+ marginalBenefitPolluter: {
643
+ type: 'number',
644
+ default: 100,
645
+ description: 'Polluter MB intercept',
646
+ },
647
+ marginalDamageVictim: {
648
+ type: 'number',
649
+ default: 10,
650
+ description: 'Victim MD per unit',
651
+ },
652
+ },
653
+ generate: (params) => {
654
+ const maxQ = params.pollutionLevels ?? 10;
655
+ const MB_intercept = params.marginalBenefitPolluter ?? 100;
656
+ const MD = params.marginalDamageVictim ?? 10;
657
+ return {
658
+ metadata: {
659
+ specVersion: '1.3',
660
+ title: 'Coase Theorem',
661
+ description: 'Private bargaining solution',
662
+ },
663
+ parameters: {
664
+ maxQ: { value: maxQ, label: 'Max Pollution', min: 5, max: 20 },
665
+ MB_int: { value: MB_intercept, label: 'MB Intercept', min: 50, max: 150 },
666
+ MD: { value: MD, label: 'MD per unit', min: 5, max: 30 },
667
+ // MB slope (linear decline)
668
+ MB_slope: { expression: 'MB_int / maxQ', hidden: true },
669
+ // Efficient pollution level: MB = MD
670
+ Q_efficient: {
671
+ expression: '(MB_int - MD) / MB_slope',
672
+ label: 'Q* (efficient)',
673
+ readonly: true,
674
+ },
675
+ // Total benefit at efficient level
676
+ totalBenefitPolluter: {
677
+ expression: '0.5 * (MB_int + MD) * Q_efficient',
678
+ label: 'Polluter Benefit',
679
+ readonly: true,
680
+ },
681
+ // Total damage at efficient level
682
+ totalDamageVictim: {
683
+ expression: 'MD * Q_efficient',
684
+ label: 'Victim Damage',
685
+ readonly: true,
686
+ },
687
+ // Net social gain
688
+ netGain: {
689
+ expression: 'totalBenefitPolluter - totalDamageVictim',
690
+ label: 'Net Social Gain',
691
+ readonly: true,
692
+ },
693
+ // Chart bounds
694
+ chartMaxQ: { expression: 'maxQ * 1.2', hidden: true },
695
+ chartMaxP: { expression: 'MB_int * 1.1', hidden: true },
696
+ },
697
+ charts: [
698
+ {
699
+ id: 'main',
700
+ title: 'Coase Theorem: Bargaining to Efficiency',
701
+ xAxis: { label: 'Pollution Level (Q)', min: 0, max: 'chartMaxQ' },
702
+ yAxis: { label: 'MB, MD ($)', min: 0, max: 'chartMaxP' },
703
+ elements: [
704
+ // Net gain from pollution (up to efficient level)
705
+ {
706
+ id: 'net-benefit',
707
+ type: 'area',
708
+ topBoundary: 'mb-curve',
709
+ bottomBoundary: 'MD',
710
+ leftBoundary: '0',
711
+ rightBoundary: 'Q_efficient',
712
+ color: '#22C55E',
713
+ opacity: 0.3,
714
+ label: 'Net Social Benefit',
715
+ },
716
+ // Marginal Benefit to polluter (declining)
717
+ {
718
+ id: 'mb-curve',
719
+ type: 'line',
720
+ equation: 'MB_int - MB_slope * x',
721
+ color: '#2563EB',
722
+ strokeWidth: 3,
723
+ label: 'MB (Polluter)',
724
+ domain: { min: 0, max: 'maxQ' },
725
+ },
726
+ // Marginal Damage to victim (constant)
727
+ {
728
+ id: 'md-line',
729
+ type: 'horizontalLine',
730
+ y: 'MD',
731
+ color: '#DC2626',
732
+ strokeWidth: 3,
733
+ label: 'MD (Victim)',
734
+ },
735
+ // Efficient point
736
+ {
737
+ id: 'efficient',
738
+ type: 'point',
739
+ x: 'Q_efficient',
740
+ y: 'MD',
741
+ color: '#16A34A',
742
+ size: 12,
743
+ label: 'MB = MD',
744
+ droplines: { x: true, y: true },
745
+ },
746
+ // Scenario markers
747
+ {
748
+ id: 'victim-rights',
749
+ type: 'point',
750
+ x: 0,
751
+ y: 0,
752
+ color: '#7C3AED',
753
+ label: 'Victim has rights',
754
+ },
755
+ {
756
+ id: 'polluter-rights',
757
+ type: 'point',
758
+ x: 'maxQ',
759
+ y: 0,
760
+ color: '#F59E0B',
761
+ label: 'Polluter has rights',
762
+ },
763
+ ],
764
+ annotations: [
765
+ {
766
+ id: 'efficient-label',
767
+ text: 'Q* = ${Q_efficient:.1f}',
768
+ x: 'Q_efficient + chartMaxQ * 0.05',
769
+ y: 'MD + chartMaxP * 0.1',
770
+ color: '#16A34A',
771
+ },
772
+ {
773
+ id: 'scenario1',
774
+ text: 'If victim has rights: Polluter pays to pollute',
775
+ x: 'chartMaxQ * 0.5',
776
+ y: 'chartMaxP * 0.9',
777
+ color: '#7C3AED',
778
+ },
779
+ {
780
+ id: 'scenario2',
781
+ text: 'If polluter has rights: Victim pays to reduce',
782
+ x: 'chartMaxQ * 0.5',
783
+ y: 'chartMaxP * 0.82',
784
+ color: '#F59E0B',
785
+ },
786
+ {
787
+ id: 'result',
788
+ text: 'Same efficient Q* either way!',
789
+ x: 'chartMaxQ * 0.5',
790
+ y: 'chartMaxP * 0.74',
791
+ color: '#16A34A',
792
+ },
793
+ ],
794
+ },
795
+ ],
796
+ };
797
+ },
798
+ };
799
+ /**
800
+ * Public Goods
801
+ *
802
+ * Non-rivalrous and non-excludable goods. Shows vertical summation of
803
+ * demand curves and the free-rider problem.
804
+ */
805
+ const publicGoods = {
806
+ id: 'public_goods',
807
+ name: 'Public Goods',
808
+ description: 'Non-rivalrous, non-excludable goods. Social demand is vertical sum of individual demands. Free-rider problem leads to underprovision.',
809
+ category: 'market_structures',
810
+ level: 'undergraduate',
811
+ tags: ['public goods', 'non-rivalry', 'non-excludability', 'free rider', 'vertical summation'],
812
+ parameters: {
813
+ person1Intercept: {
814
+ type: 'number',
815
+ default: 50,
816
+ description: 'Person 1 WTP intercept',
817
+ },
818
+ person2Intercept: {
819
+ type: 'number',
820
+ default: 40,
821
+ description: 'Person 2 WTP intercept',
822
+ },
823
+ demandSlope: {
824
+ type: 'number',
825
+ default: 1,
826
+ description: 'Common demand slope',
827
+ },
828
+ marginalCost: {
829
+ type: 'number',
830
+ default: 30,
831
+ description: 'Marginal cost of provision',
832
+ },
833
+ },
834
+ generate: (params) => {
835
+ const a1 = params.person1Intercept ?? 50;
836
+ const a2 = params.person2Intercept ?? 40;
837
+ const b = params.demandSlope ?? 1;
838
+ const MC = params.marginalCost ?? 30;
839
+ return {
840
+ metadata: {
841
+ specVersion: '1.3',
842
+ title: 'Public Goods',
843
+ description: 'Vertical summation of demand',
844
+ },
845
+ parameters: {
846
+ a1: { value: a1, label: 'Person 1 WTP', min: 20, max: 80 },
847
+ a2: { value: a2, label: 'Person 2 WTP', min: 20, max: 80 },
848
+ b: { value: b, label: 'Demand Slope', min: 0.5, max: 2, step: 0.1 },
849
+ MC: { value: MC, label: 'Marginal Cost', min: 10, max: 60 },
850
+ // Social demand (vertical sum): P = (a1 + a2) - 2bQ
851
+ socialIntercept: { expression: 'a1 + a2', hidden: true },
852
+ socialSlope: { expression: '2 * b', hidden: true },
853
+ // Optimal provision: Social MB = MC
854
+ Q_optimal: {
855
+ expression: '(a1 + a2 - MC) / (2 * b)',
856
+ label: 'Q* (optimal)',
857
+ readonly: true,
858
+ },
859
+ // Individual WTP at optimal Q
860
+ WTP1_opt: { expression: 'a1 - b * Q_optimal', label: 'WTP₁ at Q*', readonly: true },
861
+ WTP2_opt: { expression: 'a2 - b * Q_optimal', label: 'WTP₂ at Q*', readonly: true },
862
+ // Market provision (if private good)
863
+ Q_private: { expression: '(a1 - MC) / b', hidden: true },
864
+ // Chart bounds
865
+ chartMaxQ: { expression: 'max(a1, a2) / b * 1.2', hidden: true },
866
+ chartMaxP: { expression: 'socialIntercept * 1.1', hidden: true },
867
+ },
868
+ charts: [
869
+ {
870
+ id: 'main',
871
+ title: 'Public Good: Vertical Summation',
872
+ xAxis: { label: 'Quantity of Public Good', min: 0, max: 'chartMaxQ' },
873
+ yAxis: { label: 'WTP, Cost', min: 0, max: 'chartMaxP' },
874
+ elements: [
875
+ // Person 1 demand
876
+ {
877
+ id: 'd1',
878
+ type: 'line',
879
+ equation: 'a1 - b * x',
880
+ color: '#3B82F6',
881
+ strokeWidth: 2,
882
+ label: 'D₁ (Person 1)',
883
+ domain: { min: 0, max: 'a1 / b' },
884
+ },
885
+ // Person 2 demand
886
+ {
887
+ id: 'd2',
888
+ type: 'line',
889
+ equation: 'a2 - b * x',
890
+ color: '#F59E0B',
891
+ strokeWidth: 2,
892
+ label: 'D₂ (Person 2)',
893
+ domain: { min: 0, max: 'a2 / b' },
894
+ },
895
+ // Social demand (vertical sum)
896
+ {
897
+ id: 'social-demand',
898
+ type: 'line',
899
+ equation: 'socialIntercept - socialSlope * x',
900
+ color: '#16A34A',
901
+ strokeWidth: 3,
902
+ label: 'D_Social (D₁ + D₂)',
903
+ domain: { min: 0, max: 'socialIntercept / socialSlope' },
904
+ },
905
+ // MC
906
+ {
907
+ id: 'mc',
908
+ type: 'horizontalLine',
909
+ y: 'MC',
910
+ color: '#DC2626',
911
+ strokeWidth: 3,
912
+ label: 'MC',
913
+ },
914
+ // Optimal point
915
+ {
916
+ id: 'optimal',
917
+ type: 'point',
918
+ x: 'Q_optimal',
919
+ y: 'MC',
920
+ color: '#16A34A',
921
+ size: 12,
922
+ label: 'Optimal',
923
+ droplines: { x: true },
924
+ },
925
+ // Individual WTP contributions
926
+ {
927
+ id: 'wtp1-point',
928
+ type: 'point',
929
+ x: 'Q_optimal',
930
+ y: 'WTP1_opt',
931
+ color: '#3B82F6',
932
+ size: 6,
933
+ },
934
+ {
935
+ id: 'wtp2-point',
936
+ type: 'point',
937
+ x: 'Q_optimal',
938
+ y: 'WTP2_opt',
939
+ color: '#F59E0B',
940
+ size: 6,
941
+ },
942
+ // Vertical stacking at Q*
943
+ {
944
+ id: 'stack-line',
945
+ type: 'verticalLine',
946
+ x: 'Q_optimal',
947
+ color: '#6B7280',
948
+ lineStyle: 'dashed',
949
+ domain: { min: 0, max: 'MC' },
950
+ },
951
+ ],
952
+ annotations: [
953
+ {
954
+ id: 'sum-label',
955
+ text: 'WTP₁ + WTP₂ = MC',
956
+ x: 'Q_optimal + chartMaxQ * 0.1',
957
+ y: 'MC',
958
+ color: '#16A34A',
959
+ },
960
+ {
961
+ id: 'wtp1-label',
962
+ text: 'WTP₁ = $${WTP1_opt:.1f}',
963
+ x: 'Q_optimal + chartMaxQ * 0.1',
964
+ y: 'WTP1_opt',
965
+ color: '#3B82F6',
966
+ },
967
+ {
968
+ id: 'wtp2-label',
969
+ text: 'WTP₂ = $${WTP2_opt:.1f}',
970
+ x: 'Q_optimal + chartMaxQ * 0.1',
971
+ y: 'WTP2_opt',
972
+ color: '#F59E0B',
973
+ },
974
+ {
975
+ id: 'free-rider',
976
+ text: 'Free-rider problem: Each hopes other pays',
977
+ x: 'chartMaxQ * 0.5',
978
+ y: 'chartMaxP * 0.15',
979
+ color: '#DC2626',
980
+ },
981
+ ],
982
+ },
983
+ ],
984
+ };
985
+ },
986
+ };
987
+ // ============================================================================
988
+ // EXPORTS
989
+ // ============================================================================
990
+ export const externalitiesTemplates = [
991
+ negativeExternality,
992
+ positiveExternality,
993
+ pigouvianTax,
994
+ coaseTheorem,
995
+ publicGoods,
996
+ ];
997
+ //# sourceMappingURL=externalities.js.map