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.
- package/CHANGELOG.md +142 -0
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/builders/YAMLBuilder.d.ts +77 -0
- package/dist/builders/YAMLBuilder.d.ts.map +1 -0
- package/dist/builders/YAMLBuilder.js +251 -0
- package/dist/builders/YAMLBuilder.js.map +1 -0
- package/dist/economics/constants.d.ts +39 -0
- package/dist/economics/constants.d.ts.map +1 -0
- package/dist/economics/constants.js +46 -0
- package/dist/economics/constants.js.map +1 -0
- package/dist/economics/formulas.d.ts +19 -0
- package/dist/economics/formulas.d.ts.map +1 -0
- package/dist/economics/formulas.js +260 -0
- package/dist/economics/formulas.js.map +1 -0
- package/dist/economics/index.d.ts +9 -0
- package/dist/economics/index.d.ts.map +1 -0
- package/dist/economics/index.js +9 -0
- package/dist/economics/index.js.map +1 -0
- package/dist/economics/validators.d.ts +18 -0
- package/dist/economics/validators.d.ts.map +1 -0
- package/dist/economics/validators.js +111 -0
- package/dist/economics/validators.js.map +1 -0
- package/dist/errors/index.d.ts +172 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +313 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/formatters/index.d.ts +8 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +8 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/redux.d.ts +15 -0
- package/dist/formatters/redux.d.ts.map +1 -0
- package/dist/formatters/redux.js +35 -0
- package/dist/formatters/redux.js.map +1 -0
- package/dist/formatters/yaml.d.ts +18 -0
- package/dist/formatters/yaml.d.ts.map +1 -0
- package/dist/formatters/yaml.js +40 -0
- package/dist/formatters/yaml.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +86 -0
- package/dist/server.js.map +1 -0
- package/dist/templates/advancedMicro.d.ts +8 -0
- package/dist/templates/advancedMicro.d.ts.map +1 -0
- package/dist/templates/advancedMicro.js +834 -0
- package/dist/templates/advancedMicro.js.map +1 -0
- package/dist/templates/consumer.d.ts +12 -0
- package/dist/templates/consumer.d.ts.map +1 -0
- package/dist/templates/consumer.js +1978 -0
- package/dist/templates/consumer.js.map +1 -0
- package/dist/templates/elasticity.d.ts +8 -0
- package/dist/templates/elasticity.d.ts.map +1 -0
- package/dist/templates/elasticity.js +500 -0
- package/dist/templates/elasticity.js.map +1 -0
- package/dist/templates/externalities.d.ts +11 -0
- package/dist/templates/externalities.d.ts.map +1 -0
- package/dist/templates/externalities.js +997 -0
- package/dist/templates/externalities.js.map +1 -0
- package/dist/templates/financeBehavioral.d.ts +8 -0
- package/dist/templates/financeBehavioral.d.ts.map +1 -0
- package/dist/templates/financeBehavioral.js +860 -0
- package/dist/templates/financeBehavioral.js.map +1 -0
- package/dist/templates/growth.d.ts +8 -0
- package/dist/templates/growth.d.ts.map +1 -0
- package/dist/templates/growth.js +740 -0
- package/dist/templates/growth.js.map +1 -0
- package/dist/templates/index.d.ts +31 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +91 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/inequality.d.ts +8 -0
- package/dist/templates/inequality.d.ts.map +1 -0
- package/dist/templates/inequality.js +562 -0
- package/dist/templates/inequality.js.map +1 -0
- package/dist/templates/intertemporalMacro.d.ts +8 -0
- package/dist/templates/intertemporalMacro.d.ts.map +1 -0
- package/dist/templates/intertemporalMacro.js +550 -0
- package/dist/templates/intertemporalMacro.js.map +1 -0
- package/dist/templates/isLM.d.ts +8 -0
- package/dist/templates/isLM.d.ts.map +1 -0
- package/dist/templates/isLM.js +747 -0
- package/dist/templates/isLM.js.map +1 -0
- package/dist/templates/macro.d.ts +8 -0
- package/dist/templates/macro.d.ts.map +1 -0
- package/dist/templates/macro.js +600 -0
- package/dist/templates/macro.js.map +1 -0
- package/dist/templates/marketStructures.d.ts +11 -0
- package/dist/templates/marketStructures.d.ts.map +1 -0
- package/dist/templates/marketStructures.js +1135 -0
- package/dist/templates/marketStructures.js.map +1 -0
- package/dist/templates/newKeynesian.d.ts +8 -0
- package/dist/templates/newKeynesian.d.ts.map +1 -0
- package/dist/templates/newKeynesian.js +633 -0
- package/dist/templates/newKeynesian.js.map +1 -0
- package/dist/templates/oligopoly.d.ts +11 -0
- package/dist/templates/oligopoly.d.ts.map +1 -0
- package/dist/templates/oligopoly.js +1113 -0
- package/dist/templates/oligopoly.js.map +1 -0
- package/dist/templates/ppf.d.ts +8 -0
- package/dist/templates/ppf.d.ts.map +1 -0
- package/dist/templates/ppf.js +439 -0
- package/dist/templates/ppf.js.map +1 -0
- package/dist/templates/producer.d.ts +11 -0
- package/dist/templates/producer.d.ts.map +1 -0
- package/dist/templates/producer.js +979 -0
- package/dist/templates/producer.js.map +1 -0
- package/dist/templates/production.d.ts +8 -0
- package/dist/templates/production.d.ts.map +1 -0
- package/dist/templates/production.js +574 -0
- package/dist/templates/production.js.map +1 -0
- package/dist/templates/supplyDemand.d.ts +8 -0
- package/dist/templates/supplyDemand.d.ts.map +1 -0
- package/dist/templates/supplyDemand.js +1282 -0
- package/dist/templates/supplyDemand.js.map +1 -0
- package/dist/templates/tradeGrowth.d.ts +8 -0
- package/dist/templates/tradeGrowth.d.ts.map +1 -0
- package/dist/templates/tradeGrowth.js +637 -0
- package/dist/templates/tradeGrowth.js.map +1 -0
- package/dist/tools/index.d.ts +25 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +54 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/models.d.ts +8 -0
- package/dist/tools/models.d.ts.map +1 -0
- package/dist/tools/models.js +828 -0
- package/dist/tools/models.js.map +1 -0
- package/dist/tools/output.d.ts +8 -0
- package/dist/tools/output.d.ts.map +1 -0
- package/dist/tools/output.js +236 -0
- package/dist/tools/output.js.map +1 -0
- package/dist/tools/templates.d.ts +8 -0
- package/dist/tools/templates.d.ts.map +1 -0
- package/dist/tools/templates.js +247 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/validation.d.ts +8 -0
- package/dist/tools/validation.d.ts.map +1 -0
- package/dist/tools/validation.js +181 -0
- package/dist/tools/validation.js.map +1 -0
- package/dist/types/index.d.ts +187 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cache.d.ts +99 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +192 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +128 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +251 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/validation/index.d.ts +42 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +282 -0
- package/dist/validation/index.js.map +1 -0
- 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
|