import type { Point2D } from "../../../../engine/shared/types"; import { createCobbDouglas } from "../../../../engine/micro/preferences"; import { createLinearBudget, createPriceCeilingBudget, } from "../../../../engine/micro/constraints"; import { solveConsumerOptimum, type OptimalChoice, } from "../../../../engine/micro/solver"; import { findPriceCeilingBreakevenX } from "../../../../engine/micro/welfare"; import { defineScenario } from "../../../types"; import { PriceCeilingExplain } from "./explain"; export interface PriceCeilingParams extends Record { pFixed: number; xCap: number; } export interface PriceCeilingResult { optA: OptimalChoice; optB: OptimalChoice; desiredX: number; desiredY: number; breakevenX: number; deltaU: number; isBetterOff: boolean; isWorseOff: boolean; isBreakeven: boolean; pFixed: number; xCap: number; boundaryPoints: Point2D[]; vertices: Point2D[]; } export const priceCeilingScenario = defineScenario< PriceCeilingParams, PriceCeilingResult >({ meta: { id: "micro.consumer.price_ceiling", domain: "micro", category: "consumer", chapter: "PR Ch.3 • Câu 10b", title: "Giá trần và thiếu hụt nguồn cung", subtitle: "Đánh đổi giữa lợi ích giảm giá và tổn thất do khan hiếm hàng hóa dưới mức giá trần", source: "Pindyck, R. S., & Rubinfeld, D. L. (2015). Kinh tế học vi mô (Ấn bản thứ 8, Chương 3, Câu 10b, tr. 107). NXB Kinh tế TP.HCM.", notebook_id: "e8a30630-0df2-4932-8740-9ffe7d8882bc", tags: [ "Vi mô", "Người tiêu dùng", "Giá trần", "Khan hiếm", "Hòa vốn phúc lợi", ], inputBanner: [ { label: "Thu nhập I", value: "200 USD" }, { label: "Giá gốc Px", value: "10 USD" }, { label: "Giá Py", value: "10 USD" }, { label: "Hàm thỏa dụng", value: "U(X, Y) = X • Y" }, { label: "Tối ưu gốc A", value: "(10, 10)" }, ], }, params: { pFixed: { id: "pFixed", label: "Mức giá trần Px'", symbol: "P_X'", min: 2, max: 9, step: 0.5, default: 5, unit: "USD", marks: [ { value: 2, label: "2" }, { value: 5, label: "5 (Trần)" }, { value: 9, label: "9" }, ], description: "Mức giá tối đa do nhà nước ấn định thấp hơn giá thị trường 10 USD.", }, xCap: { id: "xCap", label: "Lượng cung thực tế mua được X", symbol: "X_{thực}", min: 2, max: 16, step: 0.25, default: 6, unit: "đơn vị", marks: [ { value: 2, label: "2" }, { value: 5.86, label: "5.86 (Hòa vốn)" }, { value: 6, label: "6" }, { value: 16, label: "16" }, ], description: "Số lượng hàng hóa tối đa người tiêu dùng thực tế mua được do thiếu hụt cung.", }, }, presets: { better_off: { id: "better_off", label: "Dư giả hơn: Px' = 5, X = 6", badge: "U = 102", params: { pFixed: 5, xCap: 6 }, }, worse_off: { id: "worse_off", label: "Khó khăn hơn: Px' = 5, X = 4", badge: "U = 72", params: { pFixed: 5, xCap: 4 }, }, breakeven: { id: "breakeven", label: "Hòa vốn thỏa dụng: Px' = 5, X = 5.86", badge: "U xấp xỉ 100", params: { pFixed: 5, xCap: 5.86 }, }, }, model: (params) => { const pref = createCobbDouglas({ alpha: 0.5, productForm: true }); const linearBudget = createLinearBudget({ I: 200, Px: 10, Py: 10 }); const optA = solveConsumerOptimum(pref, linearBudget); const ceilingBudget = createPriceCeilingBudget({ I: 200, Px_ceiling: params.pFixed, Py: 10, supplyCapX: params.xCap, }); const optB = solveConsumerOptimum(pref, ceilingBudget); const breakevenX = findPriceCeilingBreakevenX({ pref, I: 200, Px_ceiling: params.pFixed, Py: 10, baseUtility: 100, }); const desiredX = 200 / (2 * params.pFixed); const desiredY = (200 - params.pFixed * desiredX) / 10; const deltaU = optB.utility - 100; const isBetterOff = optB.utility > 100 + 0.5; const isWorseOff = optB.utility < 100 - 0.5; const isBreakeven = !isBetterOff && !isWorseOff; return { optA, optB, desiredX, desiredY, breakevenX, deltaU, isBetterOff, isWorseOff, isBreakeven, pFixed: params.pFixed, xCap: params.xCap, boundaryPoints: ceilingBudget.boundaryPoints, vertices: ceilingBudget.vertices, }; }, view: (result, params) => { const isBetterOff = result.isBetterOff; const isBreakeven = result.isBreakeven; const unconstrainedMaxX = 200 / params.pFixed; return { graph: { title: "Đồ thị Lựa chọn Dưới Giá Trần và Thiếu Hụt", subtitle: "So sánh thỏa dụng thực tế UB với đường bàng quan gốc U0: 100", xAxis: { label: "Hàng hóa X", unit: "đơn vị", max: 28, step: 4 }, yAxis: { label: "Hàng hóa Y", unit: "đơn vị", max: 26, step: 4 }, legends: [ { label: "Đường ngân sách gốc", color: "#94a3b8", style: "dashed" }, { label: "Ngân sách giá trần lý thuyết", color: "#818cf8", style: "dashed" }, { label: "Ngân sách thực tế (Bị chặn)", color: "#4f46e5", style: "solid" }, { label: "Đường bàng quan U0: 100", color: "#0ea5e9", style: "solid" }, { label: "Đường bàng quan UB: " + result.optB.utility.toFixed(0), color: isBetterOff ? "#059669" : "#e11d48", style: "dashed", }, { label: "Vạch hòa vốn X*", color: "#f59e0b", style: "dashed" }, ], regions: [ { id: "feasible-region", points: result.vertices, fill: "#6366f1", opacity: 0.1, label: "Tập khả thi", }, ], polylines: [ { id: "orig-budget", points: [ { x: 0, y: 20 }, { x: 20, y: 0 }, ], color: "#94a3b8", width: 2, dashed: true, label: "Ngân sách gốc", }, { id: "ceiling-unconstrained", points: [ { x: 0, y: 20 }, { x: Math.min(28, unconstrainedMaxX), y: Math.max(0, (200 - params.pFixed * Math.min(28, unconstrainedMaxX)) / 10) }, ], color: "#818cf8", width: 1.5, dashed: true, label: "Giá trần lý thuyết", }, { id: "effective-budget", points: result.boundaryPoints, color: "#4f46e5", width: 3, label: "Ngân sách thực tế", }, ], curves: [ { id: "curve-u0", fn: (x) => 100 / x, xMin: 4, xMax: 28, color: "#0ea5e9", width: 2, label: "U0: 100", labelAtX: 20, }, { id: "curve-ub", fn: (x) => result.optB.utility / x, xMin: 3, xMax: 28, color: isBetterOff ? "#059669" : "#e11d48", width: 2.2, dashed: true, label: "UB: " + result.optB.utility.toFixed(0), labelAtX: 16, }, ], guides: [ { id: "guide-a", point: { x: result.optA.x, y: result.optA.y }, color: "#94a3b8", labelX: "XA: " + result.optA.x.toFixed(0), labelY: "YA: " + result.optA.y.toFixed(0), }, { id: "guide-b", point: { x: result.optB.x, y: result.optB.y }, color: "#6366f1", labelX: "XB: " + result.optB.x.toFixed(2), labelY: "YB: " + result.optB.y.toFixed(2), }, { id: "guide-breakeven", verticalX: result.breakevenX, color: "#f59e0b", dashed: true, topLabel: "X* hòa vốn: " + result.breakevenX.toFixed(2), }, ], arrows: [ { id: "shift-ab", from: { x: result.optA.x, y: result.optA.y }, to: { x: result.optB.x, y: result.optB.y }, color: isBetterOff ? "#059669" : "#e11d48", label: isBetterOff ? "Tăng thỏa dụng" : "Giảm thỏa dụng", }, ], points: [ { id: "pt-a", x: result.optA.x, y: result.optA.y, label: "A", sublabel: "(10, 10)", color: "#0ea5e9", labelOffset: { dx: 14, dy: -14 }, }, { id: "pt-b", x: result.optB.x, y: result.optB.y, label: "B", sublabel: "(" + result.optB.x.toFixed(2) + ", " + result.optB.y.toFixed(2) + ")", color: isBetterOff ? "#059669" : "#e11d48", labelOffset: { dx: -18, dy: -18 }, }, ...(result.desiredX <= 26 ? [ { id: "pt-b-star", x: result.desiredX, y: result.desiredY, label: "B*", sublabel: "(" + result.desiredX.toFixed(0) + ", " + result.desiredY.toFixed(0) + ")", color: "#8b5cf6", labelOffset: { dx: 14, dy: 14 }, }, ] : []), ], tooltipEvaluator: (x, y) => [ { label: "Thỏa dụng U", value: (x * y).toFixed(1) }, { label: "Chi phí", value: (params.pFixed * x + 10 * y).toFixed(0) + " USD", }, ], }, stats: [ { id: "opt-a", label: "Tối ưu gốc (Điểm A)", value: "(10, 10)", subtext: "U: 100 • MRS: 1.00", color: "blue", }, { id: "opt-b", label: "Lựa chọn thực tế (Điểm B)", value: "(" + result.optB.x.toFixed(2) + ", " + result.optB.y.toFixed(2) + ")", subtext: "U: " + result.optB.utility.toFixed(1) + " • MRS: " + result.optB.mrs.toFixed(2), color: isBetterOff ? "emerald" : isBreakeven ? "amber" : "rose", }, { id: "delta-u", label: "Biến thiên thỏa dụng", value: (result.optB.utility - 100).toFixed(1), delta: { value: Math.abs(result.optB.utility - 100).toFixed(1), direction: isBetterOff ? "up" : isBreakeven ? "neutral" : "down", }, subtext: isBetterOff ? "Khấm khá hơn (Tăng thỏa dụng)" : isBreakeven ? "Hòa vốn thỏa dụng" : "Thiệt thòi hơn (Giảm thỏa dụng)", color: isBetterOff ? "emerald" : isBreakeven ? "amber" : "rose", }, { id: "breakeven", label: "Ngưỡng hòa vốn X*", value: result.breakevenX.toFixed(2), unit: "đơn vị", subtext: "Cần tối thiểu để UB >= 100", color: "purple", }, ], steps: [ { stepNumber: 1, title: "Xác định điểm cân bằng tự do ban đầu A", latexFormula: "\\max U(X,Y) = X \\cdot Y \\quad \\text{với} \\quad 10X + 10Y = 200", explanation: "Khi chưa áp giá trần, người tiêu dùng cân bằng tại điểm A(10, 10) với mức thỏa dụng UA = 100.", resultText: "A(10, 10) với UA = 100", }, { stepNumber: 2, title: "Tính toán lượng cầu mong muốn tại giá trần", latexFormula: "X^*_{\\text{cầu}} = \\frac{I}{2 P_X'} = \\frac{200}{2 \\times " + params.pFixed.toFixed(1) + "}", explanation: "Nếu nguồn cung dồi dào, người tiêu dùng muốn mua lượng hàng hóa lớn hơn rất nhiều để tận dụng mức giá rẻ.", resultText: "X_cầu = " + result.desiredX.toFixed(1) + ", Y_cầu = " + result.desiredY.toFixed(1), }, { stepNumber: 3, title: "Xác định điểm thực tế B do thiếu hụt cung", latexFormula: "X_B = \\min(X_{\\text{thực}}, X^*_{\\text{cầu}})", explanation: "Do lượng cung bị nghẽn tại X_thực = " + params.xCap.toFixed(2) + ", người tiêu dùng chỉ mua được lượng này và dùng số tiền còn lại mua Y.", resultText: "XB = " + result.optB.x.toFixed(2) + ", YB = " + result.optB.y.toFixed(2), }, { stepNumber: 4, title: "Giải nghiệm ngưỡng hòa vốn thỏa dụng X*", latexFormula: "X \\cdot \\frac{200 - P_X' X}{10} = 100 \\implies P_X' X^2 - 200X + 1000 = 0", explanation: "Ngưỡng lượng cung tối thiểu để mức thỏa dụng mới UB không bị sụt giảm so với trạng thái tự do ban đầu.", resultText: "X* hòa vốn = " + result.breakevenX.toFixed(2), }, ], comparison: { headers: ["Tiêu chí", "Điểm A (Gốc)", "Điểm B (Hiện tại)", "Nhận xét"], rows: [ { criterion: "Mức giá Px", baseValue: "10 USD", currentValue: params.pFixed.toFixed(1) + " USD", note: "Nhà nước khống chế giá trần", }, { criterion: "Rổ hàng (X, Y)", baseValue: "(10, 10)", currentValue: "(" + result.optB.x.toFixed(2) + ", " + result.optB.y.toFixed(2) + ")", note: "Mua được " + result.optB.x.toFixed(2) + " đơn vị X và " + result.optB.y.toFixed(2) + " đơn vị Y", }, { criterion: "Mức thỏa dụng U", baseValue: "100", currentValue: result.optB.utility.toFixed(1), note: isBetterOff ? "Khấm khá hơn (Tăng " + result.deltaU.toFixed(1) + " U)" : isBreakeven ? "Hòa vốn thỏa dụng" : "Thiệt thòi hơn (Giảm " + Math.abs(result.deltaU).toFixed(1) + " U)", }, { criterion: "Ngưỡng hòa vốn", baseValue: "10.00", currentValue: result.breakevenX.toFixed(2), note: params.xCap >= result.breakevenX ? "Lượng mua được vượt trên ngưỡng hòa vốn" : "Lượng mua được thấp hơn ngưỡng hòa vốn", }, ], }, statusBanner: isBetterOff ? { text: "Tại mức giá trần " + params.pFixed.toFixed(1) + " USD và lượng mua được " + params.xCap.toFixed(2) + " đơn vị (lớn hơn ngưỡng hòa vốn " + result.breakevenX.toFixed(2) + "), người tiêu dùng khấm khá hơn (UB = " + result.optB.utility.toFixed(1) + " > 100).", type: "success", } : isBreakeven ? { text: "Tại mức giá trần " + params.pFixed.toFixed(1) + " USD và lượng mua được " + params.xCap.toFixed(2) + " đơn vị, người tiêu dùng hòa vốn thỏa dụng (UB xấp xỉ 100).", type: "info", } : { text: "Tại mức giá trần " + params.pFixed.toFixed(1) + " USD nhưng lượng mua được chỉ là " + params.xCap.toFixed(2) + " đơn vị (thấp hơn ngưỡng hòa vốn " + result.breakevenX.toFixed(2) + "), người tiêu dùng bị thiệt thòi phúc lợi (UB = " + result.optB.utility.toFixed(1) + " < 100).", type: "danger", }, bridgeLinks: [ { title: "Phân phối định lượng hàng hóa", targetScenarioId: "micro.consumer.rationing", domain: "micro", description: "Khảo sát trường hợp hạn ngạch tiêu dùng không đi kèm với việc hạ giá hàng hóa.", }, { title: "Kiểm soát giá và Tổn thất vô ích (DWL)", domain: "micro", description: "Trong thị trường Cung Cầu tổng thể, giá trần luôn tạo ra tổn thất vô ích đối với tổng thặng dư xã hội.", }, { title: "Lạm phát bị kìm nén và Cú sốc cung trong Vĩ mô", domain: "macro", description: "Trong kinh tế vĩ mô, ấn định giá trần kéo dài tích tụ lạm phát tiềm ẩn và làm suy thoái năng lực sản xuất dài hạn.", }, ], footnote: "U(X, Y) = X • Y; I = Px' • X + Py • Y; điều kiện ràng buộc: X <= X_thực.", }; }, explain: PriceCeilingExplain, });