feat: Add the Python project
This commit is contained in:
parent
bb8e91b9d8
commit
f1d18ec989
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.9
|
||||
489
building_blocks/financial_concepts.json
Normal file
489
building_blocks/financial_concepts.json
Normal file
@ -0,0 +1,489 @@
|
||||
[
|
||||
{
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_F",
|
||||
"description": "Calculates the Future Value (F_simple) in a simple interest problem, given Principal, annual simple interest rate, and time in years.",
|
||||
"financial_topic": "Simple Interest",
|
||||
"target_unknown": "F_simple",
|
||||
"variables_involved": ["P", "i_simple_annual", "n_time_years", "F_simple"],
|
||||
"formulas": {
|
||||
"F_simple": "P * (1 + i_simple_annual * n_time_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "n_time_years"],
|
||||
"narrative_hooks": ["loan", "investment", "borrow", "deposit", "future amount", "accumulated value", "simple interest", "maturity value"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_P_FROM_F",
|
||||
"description": "Calculates the Principal (P) in a simple interest problem, given Future Value, annual simple interest rate, and time in years.",
|
||||
"financial_topic": "Simple Interest",
|
||||
"target_unknown": "P",
|
||||
"variables_involved": ["P", "i_simple_annual", "n_time_years", "F_simple"],
|
||||
"formulas": {
|
||||
"P": "F_simple / (1 + i_simple_annual * n_time_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["F_simple", "i_simple_annual", "n_time_years"],
|
||||
"narrative_hooks": ["loan", "investment", "present worth", "initial amount", "deposit now", "simple interest"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_I",
|
||||
"description": "Calculates the simple Interest amount (I_simple), given Principal, annual simple interest rate, and time in years.",
|
||||
"financial_topic": "Simple Interest",
|
||||
"target_unknown": "I_simple",
|
||||
"variables_involved": ["P", "i_simple_annual", "n_time_years", "I_simple"],
|
||||
"formulas": {
|
||||
"I_simple": "P * i_simple_annual * n_time_years"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "n_time_years"],
|
||||
"narrative_hooks": ["interest earned", "interest due", "cost of borrowing", "simple interest"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_P_FROM_I",
|
||||
"description": "Calculates the Principal (P) in a simple interest problem, given Interest amount, annual simple interest rate, and time in years.",
|
||||
"financial_topic": "Simple Interest",
|
||||
"target_unknown": "P",
|
||||
"variables_involved": ["P", "i_simple_annual", "n_time_years", "I_simple"],
|
||||
"formulas": {
|
||||
"P": "I_simple / (i_simple_annual * n_time_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["I_simple", "i_simple_annual", "n_time_years"],
|
||||
"narrative_hooks": ["principal amount", "original loan", "initial investment", "simple interest", "interest yielded"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_RATE_FROM_I",
|
||||
"description": "Calculates the annual simple interest rate (i_simple_annual), given Principal, Interest amount, and time in years.",
|
||||
"financial_topic": "Simple Interest",
|
||||
"target_unknown": "i_simple_annual",
|
||||
"variables_involved": ["P", "i_simple_annual", "n_time_years", "I_simple"],
|
||||
"formulas": {
|
||||
"i_simple_annual": "I_simple / (P * n_time_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "I_simple", "n_time_years"],
|
||||
"narrative_hooks": ["interest rate", "rate of return", "annual rate", "simple interest"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_TIME_FROM_I",
|
||||
"description": "Calculates the time period in years (n_time_years), given Principal, Interest amount, and annual simple interest rate.",
|
||||
"financial_topic": "Simple Interest",
|
||||
"target_unknown": "n_time_years",
|
||||
"variables_involved": ["P", "i_simple_annual", "n_time_years", "I_simple"],
|
||||
"formulas": {
|
||||
"n_time_years": "I_simple / (P * i_simple_annual)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "I_simple", "i_simple_annual"],
|
||||
"narrative_hooks": ["time period", "duration", "loan term", "investment horizon", "simple interest"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "COMPOUND_INTEREST_SOLVE_FOR_F",
|
||||
"description": "Calculates the Future Value (F_compound) in a compound interest problem, given Principal, nominal annual interest rate, compounding frequency, and time in years.",
|
||||
"financial_topic": "Compound Interest",
|
||||
"target_unknown": "F_compound",
|
||||
"variables_involved": ["P", "r_nominal_annual", "m_compounding_periods_per_year", "t_years", "i_rate_per_period", "n_total_compounding_periods", "F_compound"],
|
||||
"formulas": {
|
||||
"i_rate_per_period": "r_nominal_annual / m_compounding_periods_per_year",
|
||||
"n_total_compounding_periods": "t_years * m_compounding_periods_per_year",
|
||||
"F_compound": "P * (1 + i_rate_per_period)**n_total_compounding_periods"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "r_nominal_annual", "m_compounding_periods_per_year", "t_years"],
|
||||
"narrative_hooks": ["investment", "deposit", "loan", "future worth", "accumulated amount", "compound interest", "compounded"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.calculate_interest_rate_per_period",
|
||||
"solution_guidance.calculate_total_periods",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "COMPOUND_INTEREST_SOLVE_FOR_P_FROM_F",
|
||||
"description": "Calculates the Principal (P) in a compound interest problem, given Future Value, nominal annual interest rate, compounding frequency, and time in years.",
|
||||
"financial_topic": "Compound Interest",
|
||||
"target_unknown": "P",
|
||||
"variables_involved": ["P", "r_nominal_annual", "m_compounding_periods_per_year", "t_years", "i_rate_per_period", "n_total_compounding_periods", "F_compound"],
|
||||
"formulas": {
|
||||
"i_rate_per_period": "r_nominal_annual / m_compounding_periods_per_year",
|
||||
"n_total_compounding_periods": "t_years * m_compounding_periods_per_year",
|
||||
"P": "F_compound / (1 + i_rate_per_period)**n_total_compounding_periods"
|
||||
},
|
||||
"required_knowns_for_target": ["F_compound", "r_nominal_annual", "m_compounding_periods_per_year", "t_years"],
|
||||
"narrative_hooks": ["present value", "initial investment", "deposit now", "amount to invest", "compound interest", "compounded"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.calculate_interest_rate_per_period",
|
||||
"solution_guidance.calculate_total_periods",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "COMPOUND_INTEREST_SOLVE_FOR_RATE",
|
||||
"description": "Calculates the nominal annual interest rate (r_nominal_annual), given Principal, Future Value, compounding frequency, and time in years.",
|
||||
"financial_topic": "Compound Interest",
|
||||
"target_unknown": "r_nominal_annual",
|
||||
"variables_involved": ["P", "r_nominal_annual", "m_compounding_periods_per_year", "t_years", "i_rate_per_period", "n_total_compounding_periods", "F_compound"],
|
||||
"formulas": {
|
||||
"n_total_compounding_periods": "t_years * m_compounding_periods_per_year",
|
||||
"i_rate_per_period": "(F_compound / P)**(1 / n_total_compounding_periods) - 1",
|
||||
"r_nominal_annual": "i_rate_per_period * m_compounding_periods_per_year"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "F_compound", "m_compounding_periods_per_year", "t_years"],
|
||||
"narrative_hooks": ["interest rate", "nominal rate", "rate of return", "compound interest", "compounded"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.calculate_total_periods",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.intermediate_step",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "COMPOUND_INTEREST_SOLVE_FOR_TIME",
|
||||
"description": "Calculates the time in years (t_years), given Principal, Future Value, nominal annual interest rate, and compounding frequency. Requires math.log.",
|
||||
"financial_topic": "Compound Interest",
|
||||
"target_unknown": "t_years",
|
||||
"variables_involved": ["P", "r_nominal_annual", "m_compounding_periods_per_year", "t_years", "i_rate_per_period", "n_total_compounding_periods", "F_compound"],
|
||||
"formulas": {
|
||||
"i_rate_per_period": "r_nominal_annual / m_compounding_periods_per_year",
|
||||
"n_total_compounding_periods": "math.log(F_compound / P) / math.log(1 + i_rate_per_period)",
|
||||
"t_years": "n_total_compounding_periods / m_compounding_periods_per_year"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "F_compound", "r_nominal_annual", "m_compounding_periods_per_year"],
|
||||
"narrative_hooks": ["time period", "duration", "investment horizon", "how long", "compound interest", "compounded"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.calculate_interest_rate_per_period",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.intermediate_step",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "EFFECTIVE_RATE_SOLVE_FOR_ER",
|
||||
"description": "Calculates the Effective Annual Interest Rate (ER), given the nominal annual interest rate and the number of compounding periods per year.",
|
||||
"financial_topic": "Effective Rate of Interest",
|
||||
"target_unknown": "ER",
|
||||
"variables_involved": ["r_nominal_annual", "m_compounding_periods_per_year", "i_rate_per_period", "ER"],
|
||||
"formulas": {
|
||||
"i_rate_per_period": "r_nominal_annual / m_compounding_periods_per_year",
|
||||
"ER": "(1 + i_rate_per_period)**m_compounding_periods_per_year - 1"
|
||||
},
|
||||
"required_knowns_for_target": ["r_nominal_annual", "m_compounding_periods_per_year"],
|
||||
"narrative_hooks": ["effective rate", "actual annual rate", "true interest rate", "compounding effect"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.calculate_interest_rate_per_period",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "CONTINUOUS_COMPOUNDING_SOLVE_FOR_F",
|
||||
"description": "Calculates the Future Value (F_continuous) with continuous compounding, given Principal, nominal annual interest rate, and time in years. Requires math.exp.",
|
||||
"financial_topic": "Continuous Compounding Interest",
|
||||
"target_unknown": "F_continuous",
|
||||
"variables_involved": ["P", "r_nominal_annual", "t_years", "F_continuous"],
|
||||
"formulas": {
|
||||
"F_continuous": "P * math.exp(r_nominal_annual * t_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "r_nominal_annual", "t_years"],
|
||||
"narrative_hooks": ["continuous compounding", "investment growth", "future value", "accumulated amount"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "CONTINUOUS_COMPOUNDING_SOLVE_FOR_P_FROM_F",
|
||||
"description": "Calculates the Principal (P) with continuous compounding, given Future Value, nominal annual interest rate, and time in years. Requires math.exp.",
|
||||
"financial_topic": "Continuous Compounding Interest",
|
||||
"target_unknown": "P",
|
||||
"variables_involved": ["P", "r_nominal_annual", "t_years", "F_continuous"],
|
||||
"formulas": {
|
||||
"P": "F_continuous / math.exp(r_nominal_annual * t_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["F_continuous", "r_nominal_annual", "t_years"],
|
||||
"narrative_hooks": ["continuous compounding", "present value", "initial investment", "amount to deposit"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "CONTINUOUS_COMPOUNDING_SOLVE_FOR_RATE",
|
||||
"description": "Calculates the nominal annual interest rate (r_nominal_annual) with continuous compounding, given Principal, Future Value, and time in years. Requires math.log.",
|
||||
"financial_topic": "Continuous Compounding Interest",
|
||||
"target_unknown": "r_nominal_annual",
|
||||
"variables_involved": ["P", "r_nominal_annual", "t_years", "F_continuous"],
|
||||
"formulas": {
|
||||
"r_nominal_annual": "math.log(F_continuous / P) / t_years"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "F_continuous", "t_years"],
|
||||
"narrative_hooks": ["continuous compounding", "interest rate", "nominal rate", "rate of growth"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "CONTINUOUS_COMPOUNDING_SOLVE_FOR_TIME",
|
||||
"description": "Calculates the time in years (t_years) with continuous compounding, given Principal, Future Value, and nominal annual interest rate. Requires math.log.",
|
||||
"financial_topic": "Continuous Compounding Interest",
|
||||
"target_unknown": "t_years",
|
||||
"variables_involved": ["P", "r_nominal_annual", "t_years", "F_continuous"],
|
||||
"formulas": {
|
||||
"t_years": "math.log(F_continuous / P) / r_nominal_annual"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "F_continuous", "r_nominal_annual"],
|
||||
"narrative_hooks": ["continuous compounding", "time period", "duration", "how long to reach value"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "CONTINUOUS_COMPOUNDING_EQUIVALENT_SIMPLE_RATE",
|
||||
"description": "Calculates the equivalent simple interest rate for 1 year for a given nominal annual rate compounded continuously. Requires math.exp.",
|
||||
"financial_topic": "Continuous Compounding Interest",
|
||||
"target_unknown": "i_simple_equivalent",
|
||||
"variables_involved": ["r_nominal_annual", "i_simple_equivalent"],
|
||||
"formulas": {
|
||||
"i_simple_equivalent": "math.exp(r_nominal_annual) - 1"
|
||||
},
|
||||
"required_knowns_for_target": ["r_nominal_annual"],
|
||||
"narrative_hooks": ["continuous compounding", "equivalent simple rate", "comparison rate"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "BANKERS_DISCOUNT_SOLVE_FOR_PROCEEDS",
|
||||
"description": "Calculates the Proceeds (P_proceeds) in a Banker's Discount problem, given Maturity Value (F_maturity), discount rate, and time.",
|
||||
"financial_topic": "Banker's Discount",
|
||||
"target_unknown": "P_proceeds",
|
||||
"variables_involved": ["F_maturity", "d_discount_rate", "t_years", "Db_discount_amount", "P_proceeds"],
|
||||
"formulas": {
|
||||
"Db_discount_amount": "F_maturity * d_discount_rate * t_years",
|
||||
"P_proceeds": "F_maturity - Db_discount_amount"
|
||||
},
|
||||
"required_knowns_for_target": ["F_maturity", "d_discount_rate", "t_years"],
|
||||
"narrative_hooks": ["banker's discount", "discounted loan", "proceeds", "amount received", "maturity value", "face value"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.intermediate_step",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "BANKERS_DISCOUNT_SOLVE_FOR_DISCOUNT_RATE",
|
||||
"description": "Calculates the discount rate (d_discount_rate) in a Banker's Discount problem, given Maturity Value, Proceeds, and time.",
|
||||
"financial_topic": "Banker's Discount",
|
||||
"target_unknown": "d_discount_rate",
|
||||
"variables_involved": ["F_maturity", "d_discount_rate", "t_years", "Db_discount_amount", "P_proceeds"],
|
||||
"formulas": {
|
||||
"Db_discount_amount": "F_maturity - P_proceeds",
|
||||
"d_discount_rate": "Db_discount_amount / (F_maturity * t_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["F_maturity", "P_proceeds", "t_years"],
|
||||
"narrative_hooks": ["banker's discount", "discount rate", "rate of discount", "proceeds", "maturity value"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.intermediate_step",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "BANKERS_DISCOUNT_SOLVE_FOR_SIMPLE_INTEREST_EQUIVALENT",
|
||||
"description": "Calculates the equivalent simple interest rate (i_simple_equivalent) for a Banker's Discount scenario, based on Proceeds.",
|
||||
"financial_topic": "Banker's Discount",
|
||||
"target_unknown": "i_simple_equivalent",
|
||||
"variables_involved": ["F_maturity", "t_years", "Db_discount_amount", "P_proceeds", "i_simple_equivalent"],
|
||||
"formulas": {
|
||||
"Db_discount_amount": "F_maturity - P_proceeds",
|
||||
"i_simple_equivalent": "Db_discount_amount / (P_proceeds * t_years)"
|
||||
},
|
||||
"required_knowns_for_target": ["F_maturity", "P_proceeds", "t_years"],
|
||||
"narrative_hooks": ["banker's discount", "equivalent simple interest", "comparison rate", "true cost of borrowing"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.convert_time_to_years",
|
||||
"solution_guidance.intermediate_step",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "EXACT_SIMPLE_INTEREST_SOLVE_FOR_I",
|
||||
"description": "Calculates the Exact Simple Interest amount (I_exact_simple), given Principal, annual simple interest rate, start date, and end date.",
|
||||
"financial_topic": "Exact Simple Interest",
|
||||
"target_unknown": "I_exact_simple",
|
||||
"variables_involved": ["P", "i_simple_annual", "start_date", "end_date", "n_time_days", "time_base_days", "n_time_years_fractional", "I_exact_simple"],
|
||||
"formulas": {
|
||||
"n_time_years_fractional": "n_time_days / time_base_days",
|
||||
"I_exact_simple": "P * i_simple_annual * n_time_years_fractional"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "start_date", "end_date"],
|
||||
"narrative_hooks": ["exact simple interest", "loan interest", "investment earnings", "specific period"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.days_in_period",
|
||||
"solution_guidance.check_leap_year",
|
||||
"solution_guidance.determine_time_base_exact",
|
||||
"solution_guidance.calculate_n_time_years_fractional",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "EXACT_SIMPLE_INTEREST_SOLVE_FOR_F",
|
||||
"description": "Calculates the Future Value (F_exact_simple) with Exact Simple Interest, given Principal, annual simple interest rate, start date, and end date.",
|
||||
"financial_topic": "Exact Simple Interest",
|
||||
"target_unknown": "F_exact_simple",
|
||||
"variables_involved": ["P", "i_simple_annual", "start_date", "end_date", "n_time_days", "time_base_days", "n_time_years_fractional", "F_exact_simple"],
|
||||
"formulas": {
|
||||
"n_time_years_fractional": "n_time_days / time_base_days",
|
||||
"F_exact_simple": "P * (1 + i_simple_annual * n_time_years_fractional)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "start_date", "end_date"],
|
||||
"narrative_hooks": ["exact simple interest", "maturity value", "future amount", "specific period"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.days_in_period",
|
||||
"solution_guidance.check_leap_year",
|
||||
"solution_guidance.determine_time_base_exact",
|
||||
"solution_guidance.calculate_n_time_years_fractional",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "ORDINARY_SIMPLE_INTEREST_SOLVE_FOR_I",
|
||||
"description": "Calculates the Ordinary Simple Interest amount (I_ordinary_simple), given Principal, annual simple interest rate, start date, and end date.",
|
||||
"financial_topic": "Ordinary Simple Interest",
|
||||
"target_unknown": "I_ordinary_simple",
|
||||
"variables_involved": ["P", "i_simple_annual", "start_date", "end_date", "n_time_days", "time_base_days", "n_time_years_fractional", "I_ordinary_simple"],
|
||||
"formulas": {
|
||||
"n_time_years_fractional": "n_time_days / time_base_days",
|
||||
"I_ordinary_simple": "P * i_simple_annual * n_time_years_fractional"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "start_date", "end_date"],
|
||||
"narrative_hooks": ["ordinary simple interest", "loan interest", "investment earnings", "360 day year"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.days_in_period",
|
||||
"solution_guidance.determine_time_base_ordinary",
|
||||
"solution_guidance.calculate_n_time_years_fractional",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
},
|
||||
{
|
||||
"concept_id": "ORDINARY_SIMPLE_INTEREST_SOLVE_FOR_F",
|
||||
"description": "Calculates the Future Value (F_ordinary_simple) with Ordinary Simple Interest, given Principal, annual simple interest rate, start date, and end date.",
|
||||
"financial_topic": "Ordinary Simple Interest",
|
||||
"target_unknown": "F_ordinary_simple",
|
||||
"variables_involved": ["P", "i_simple_annual", "start_date", "end_date", "n_time_days", "time_base_days", "n_time_years_fractional", "F_ordinary_simple"],
|
||||
"formulas": {
|
||||
"n_time_years_fractional": "n_time_days / time_base_days",
|
||||
"F_ordinary_simple": "P * (1 + i_simple_annual * n_time_years_fractional)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "start_date", "end_date"],
|
||||
"narrative_hooks": ["ordinary simple interest", "maturity value", "future amount", "360 day year"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.days_in_period",
|
||||
"solution_guidance.determine_time_base_ordinary",
|
||||
"solution_guidance.calculate_n_time_years_fractional",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
}
|
||||
]
|
||||
69
data/names.json
Normal file
69
data/names.json
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"persons_titles": ["Mr.", "Ms.", "Engr.", "Dr.", "Prof."],
|
||||
"persons_first_names": [
|
||||
"James", "Mary", "John", "Patricia", "Robert", "Jennifer", "Michael", "Linda",
|
||||
"William", "Elizabeth", "David", "Barbara", "Richard", "Susan", "Joseph", "Jessica",
|
||||
"Thomas", "Sarah", "Charles", "Karen", "Christopher", "Nancy", "Daniel", "Lisa",
|
||||
"Matthew", "Betty", "Anthony", "Margaret", "Mark", "Sandra", "Juan", "Maria",
|
||||
"Jose", "Ana", "Carlos", "Sofia"
|
||||
],
|
||||
"persons_last_names": [
|
||||
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
|
||||
"Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson",
|
||||
"Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson",
|
||||
"White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker",
|
||||
"Young", "Allen", "King", "Wright", "Scott", "Green", "Baker", "Adams", "Nelson",
|
||||
"Hill", "Campbell", "Mitchell", "Roberts", "Carter", "Phillips", "Evans", "Turner",
|
||||
"Torres", "Parker", "Collins", "Edwards", "Stewart", "Flores", "Morris", "Nguyen",
|
||||
"Murphy", "Rivera", "Cook", "Rogers", "Morgan", "Peterson", "Cooper", "Reed",
|
||||
"Bailey", "Bell", "Gomez", "Kelly", "Howard", "Ward", "Cox", "Diaz", "Richardson",
|
||||
"Wood", "Watson", "Brooks", "Bennett", "Gray", "James", "Reyes", "Cruz", "Hughes",
|
||||
"Price", "Myers", "Long", "Foster", "Sanders", "Ross", "Morales", "Powell",
|
||||
"Sullivan", "Russell", "Ortiz", "Jenkins", "Gutierrez", "Perry", "Butler",
|
||||
"Barnes", "Fisher", "Henderson", "Coleman", "Simmons", "Patterson", "Jordan",
|
||||
"Reynolds", "Hamilton", "Graham", "Kim", "Gonzales", "Alexander", "Ramos",
|
||||
"Wallace", "Griffin", "West", "Cole", "Hayes", "Chavez", "Gibson", "Bryant",
|
||||
"Ellis", "Stevens", "Murray", "Ford", "Marshall", "Owens", "McDonald", "Harrison",
|
||||
"Ruiz", "Kennedy", "Wells", "Alvarez", "Woods", "Mendoza", "Castillo", "Olson",
|
||||
"Webb", "Washington", "Tucker", "Freeman", "Burns", "Henry", "Vasquez", "Snyder",
|
||||
"Simpson", "Crawford", "Jimenez", "Porter", "Mason", "Shaw", "Gordon", "Wagner",
|
||||
"Hunter", "Romero", "Hicks", "Dixon", "Hunt", "Palmer", "Robertson", "Black",
|
||||
"Holmes", "Stone", "Meyer", "Boyd", "Mills", "Warren", "Fox", "Rose", "Rice",
|
||||
"Moreno", "Schmidt", "Patel", "Ferguson", "Nichols", "Herrera", "Medina",
|
||||
"Ryan", "Fernandez", "Weaver", "Daniels", "Stephens", "Gardner", "Payne",
|
||||
"Kelley", "Dunn", "Pierce", "Arnold", "Tran", "Spencer", "Peters", "Hawkins",
|
||||
"Grant", "Hansen", "Castro", "Hoffman", "Hart", "Elliott", "Cunningham",
|
||||
"Knight", "Bradley", "Santos"
|
||||
],
|
||||
"companies_generic_prefix": [
|
||||
"Apex", "Vertex", "Zenith", "Nova", "Orion", "Quantum", "Synergy", "Global",
|
||||
"United", "Dynamic", "Prime", "Core", "Alpha", "Beta", "Omega", "Delta",
|
||||
"Stellar", "Pinnacle", "Summit", "Horizon", "Matrix", "Nexus", "Catalyst"
|
||||
],
|
||||
"companies_generic_suffix": [
|
||||
"Solutions", "Enterprises", "Group", "Corp.", "Inc.", "LLC", "Technologies",
|
||||
"Systems", "Dynamics", "Innovations", "Ventures", "Holdings", "Logistics",
|
||||
"Manufacturing", "Consulting", "Services", "Industries", "Partners"
|
||||
],
|
||||
"companies_industry_specific": {
|
||||
"finance": ["Capital", "Financial", "Investment Group", "Bank", "Credit Union"],
|
||||
"tech": ["Tech", "Software", "Digital", "Cybernetics", "AI Solutions"],
|
||||
"construction": ["Builders", "Construction Co.", "Development", "Contractors"],
|
||||
"retail": ["Goods", "Emporium", "Mart", "Retailers", "Supply Co."]
|
||||
},
|
||||
"items_loan_general": [
|
||||
"a business expansion", "a new equipment purchase", "a property acquisition",
|
||||
"working capital", "a personal project", "debt consolidation", "a vehicle",
|
||||
"home improvement", "education fees", "a startup venture"
|
||||
],
|
||||
"items_investment_general": [
|
||||
"stocks", "bonds", "a mutual fund", "real estate", "a new business",
|
||||
"a savings account", "a certificate of deposit", "a retirement fund",
|
||||
"a tech startup", "a portfolio of assets"
|
||||
],
|
||||
"project_names": [
|
||||
"Project Alpha", "The Phoenix Initiative", "Operation Starlight", "Blue Sky Project",
|
||||
"Quantum Leap Program", "Project Chimera", "The Vanguard Project", "Odyssey Plan",
|
||||
"Project Nova", "Titan Development"
|
||||
]
|
||||
}
|
||||
127
data/text_snippets.json
Normal file
127
data/text_snippets.json
Normal file
@ -0,0 +1,127 @@
|
||||
{
|
||||
"actors_person": ["{person_title} {person_first_name} {person_last_name}", "{person_first_name} {person_last_name}", "{person_title} {person_last_name}"],
|
||||
"actors_company": ["{company_prefix} {company_suffix}", "{company_prefix} {company_industry}"],
|
||||
|
||||
"actions_loan_present_singular": ["borrows", "takes out a loan for", "secures financing for", "needs a loan of", "is seeking a loan of"],
|
||||
"actions_loan_present_plural": ["borrow", "take out a loan for", "secure financing for", "need a loan of", "are seeking a loan of"],
|
||||
"actions_loan_past_singular": ["borrowed", "took out a loan for", "secured financing for", "received a loan of"],
|
||||
"actions_loan_past_plural": ["borrowed", "took out a loan for", "secured financing for", "received a loan of"],
|
||||
|
||||
"actions_investment_present_singular": ["invests", "deposits", "puts", "plans to invest", "wants to deposit"],
|
||||
"actions_investment_present_plural": ["invest", "deposit", "put", "plan to invest", "want to deposit"],
|
||||
"actions_investment_past_singular": ["invested", "deposited", "put", "made an investment of"],
|
||||
"actions_investment_past_plural": ["invested", "deposited", "put", "made an investment of"],
|
||||
|
||||
"actions_repayment_present_singular": ["repays", "settles", "amortizes", "makes a payment on"],
|
||||
"actions_repayment_present_plural": ["repay", "settle", "amortize", "make a payment on"],
|
||||
"actions_repayment_past_singular": ["repaid", "settled", "amortized", "made a payment on"],
|
||||
"actions_repayment_past_plural": ["repaid", "settled", "amortized", "made a payment on"],
|
||||
|
||||
"actions_receive_present_singular": ["receives", "obtains", "gets", "is due to receive"],
|
||||
"actions_receive_present_plural": ["receive", "obtain", "get", "are due to receive"],
|
||||
"actions_receive_past_singular": ["received", "obtained", "got"],
|
||||
"actions_receive_past_plural": ["received", "obtained", "got"],
|
||||
|
||||
"actions_earn_present_singular": ["earns", "accumulates", "yields"],
|
||||
"actions_earn_present_plural": ["earn", "accumulate", "yield"],
|
||||
"actions_earn_past_singular": ["earned", "accumulated", "yielded"],
|
||||
"actions_earn_past_plural": ["earned", "accumulated", "yielded"],
|
||||
|
||||
"time_phrases_duration": ["for a period of", "over", "for", "during"],
|
||||
"time_phrases_point": ["at the end of", "after", "in"],
|
||||
|
||||
"rate_phrases": ["at a rate of", "with an interest of", "at an annual rate of", "earning interest at"],
|
||||
"compounding_phrases": ["compounded {compounding_frequency_adverb}", "with {compounding_frequency_adverb} compounding"],
|
||||
|
||||
"purpose_phrases_loan": ["for {item_loan}", "to finance {item_loan}", "to purchase {item_loan}"],
|
||||
"purpose_phrases_investment": ["in {item_investment}", "into {item_investment}", "to grow their capital through {item_investment}"],
|
||||
|
||||
"question_starters_what_is": ["What is the", "Determine the", "Calculate the", "Find the", "What will be the", "What was the"],
|
||||
"question_starters_how_much": ["How much is the", "How much will be the", "How much was the", "How much should be"],
|
||||
"question_starters_how_long": ["How long will it take for", "How many years are needed for", "What is the time period for"],
|
||||
|
||||
"scenario_introductions": [
|
||||
"Consider a scenario where {actor}",
|
||||
"{actor} is planning to",
|
||||
"Suppose {actor}",
|
||||
"Imagine {actor} needs to"
|
||||
],
|
||||
"scenario_connectors": [
|
||||
"The terms of the agreement state that",
|
||||
"It is known that",
|
||||
"Given that",
|
||||
"Assuming that"
|
||||
],
|
||||
"scenario_closures_question_prefix": [
|
||||
"Based on this information,",
|
||||
"Therefore,",
|
||||
"With these conditions,"
|
||||
],
|
||||
|
||||
"solution_guidance": {
|
||||
"identify_knowns": "First, let's identify the given values (knowns) in the problem:",
|
||||
"state_formula": "The relevant formula for this problem is:",
|
||||
"substitute_values": "Now, we substitute the known values into the formula:",
|
||||
"perform_calculation": "Performing the calculation:",
|
||||
"intermediate_step": "The intermediate result for {step_name} is:",
|
||||
"final_answer_is": "Therefore, the {unknown_variable_description} is:",
|
||||
"convert_time_to_years": "Convert the time period to years: {original_time_value} {original_time_unit} = {converted_time_value_years} years.",
|
||||
"calculate_interest_rate_per_period": "Calculate the interest rate per compounding period (i): i = r / m = {nominal_rate_decimal} / {compounding_periods_per_year} = {interest_rate_per_period_decimal}.",
|
||||
"calculate_total_periods": "Calculate the total number of compounding periods (n): n = t * m = {time_in_years} years * {compounding_periods_per_year} = {total_periods} periods.",
|
||||
"check_leap_year": "{year} is {is_or_is_not} a leap year.",
|
||||
"days_in_period": "The number of days from {start_date} to {end_date} is {number_of_days} days."
|
||||
},
|
||||
|
||||
"variable_descriptions": {
|
||||
"P": "principal amount",
|
||||
"F": "future value",
|
||||
"I": "interest amount",
|
||||
"i_simple_annual": "annual simple interest rate",
|
||||
"n_time_years": "time period in years",
|
||||
"n_time_months": "time period in months",
|
||||
"n_time_days": "time period in days",
|
||||
"r_nominal_annual": "nominal annual interest rate",
|
||||
"m_compounding_periods_per_year": "number of compounding periods per year",
|
||||
"i_rate_per_period": "interest rate per compounding period",
|
||||
"n_total_compounding_periods": "total number of compounding periods",
|
||||
"ER": "effective interest rate",
|
||||
"Db": "banker's discount amount",
|
||||
"d_discount_rate": "discount rate",
|
||||
"Proceeds": "proceeds from the loan",
|
||||
"I_exact_simple": "exact simple interest amount",
|
||||
"F_exact_simple": "future value with exact simple interest",
|
||||
"I_ordinary_simple": "ordinary simple interest amount",
|
||||
"F_ordinary_simple": "future value with ordinary simple interest",
|
||||
"start_date": "start date of the period",
|
||||
"end_date": "end date of the period",
|
||||
"time_base_days": "day count basis for the year (time base)",
|
||||
"n_time_years_fractional": "time period as a fraction of a year"
|
||||
},
|
||||
|
||||
"solution_guidance": {
|
||||
"identify_knowns": "First, let's identify the given values (knowns) in the problem:",
|
||||
"state_formula": "The relevant formula for this problem is:",
|
||||
"substitute_values": "Now, we substitute the known values into the formula:",
|
||||
"perform_calculation": "Performing the calculation:",
|
||||
"intermediate_step": "The intermediate result for {step_name} is:",
|
||||
"final_answer_is": "Therefore, the {unknown_variable_description} is:",
|
||||
"convert_time_to_years": "Convert the time period to years: {original_time_value} {original_time_unit} = {converted_time_value_years} years.",
|
||||
"calculate_interest_rate_per_period": "Calculate the interest rate per compounding period (i): i = r / m = {nominal_rate_decimal} / {compounding_periods_per_year} = {interest_rate_per_period_decimal}.",
|
||||
"calculate_total_periods": "Calculate the total number of compounding periods (n): n = t * m = {time_in_years} years * {compounding_periods_per_year} = {total_periods} periods.",
|
||||
"check_leap_year": "{year} is {is_or_is_not} a leap year.",
|
||||
"days_in_period": "The number of days from {start_date} to {end_date} is {number_of_days} days.",
|
||||
"determine_time_base_exact": "For exact simple interest, the time base is the actual number of days in the reference year ({year}), which is {days_in_year} days.",
|
||||
"determine_time_base_ordinary": "For ordinary simple interest, the time base is 360 days.",
|
||||
"calculate_n_time_years_fractional": "Calculate the time as a fraction of a year (t_fractional): t_fractional = number of days / time base = {n_time_days} / {time_base_days} = {n_time_years_fractional_value}."
|
||||
},
|
||||
|
||||
"compounding_frequency_adverbs": {
|
||||
"annually": "annually",
|
||||
"semi-annually": "semi-annually",
|
||||
"quarterly": "quarterly",
|
||||
"monthly": "monthly",
|
||||
"bi-monthly": "bi-monthly",
|
||||
"semi-monthly": "semi-monthly",
|
||||
"continuously": "continuously"
|
||||
}
|
||||
}
|
||||
137
data/value_ranges.json
Normal file
137
data/value_ranges.json
Normal file
@ -0,0 +1,137 @@
|
||||
{
|
||||
"principal": {
|
||||
"min": 500,
|
||||
"max": 200000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"loan_amount": {
|
||||
"min": 1000,
|
||||
"max": 500000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"investment_amount": {
|
||||
"min": 100,
|
||||
"max": 100000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"future_value": {
|
||||
"min": 1000,
|
||||
"max": 1000000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"interest_amount": {
|
||||
"min": 50,
|
||||
"max": 50000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"payment_amount": {
|
||||
"min": 100,
|
||||
"max": 50000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"simple_interest_rate_annual": {
|
||||
"min": 0.02,
|
||||
"max": 0.25,
|
||||
"unit_display": "% per annum",
|
||||
"internal_precision": 8,
|
||||
"display_precision": 2
|
||||
},
|
||||
"compound_interest_rate_nominal": {
|
||||
"min": 0.03,
|
||||
"max": 0.18,
|
||||
"unit_display": "%",
|
||||
"internal_precision": 8,
|
||||
"display_precision": 2
|
||||
},
|
||||
"discount_rate_bankers": {
|
||||
"min": 0.05,
|
||||
"max": 0.20,
|
||||
"unit_display": "%",
|
||||
"internal_precision": 8,
|
||||
"display_precision": 2
|
||||
},
|
||||
"time_years": {
|
||||
"min": 1,
|
||||
"max": 20,
|
||||
"unit": "years",
|
||||
"integer": true,
|
||||
"allow_fractional_if_months_also_present": true
|
||||
},
|
||||
"time_months": {
|
||||
"min": 1,
|
||||
"max": 48,
|
||||
"unit": "months",
|
||||
"integer": true
|
||||
},
|
||||
"time_days": {
|
||||
"min": 10,
|
||||
"max": 360,
|
||||
"unit": "days",
|
||||
"integer": true
|
||||
},
|
||||
"number_of_periods_general": {
|
||||
"min": 2,
|
||||
"max": 30,
|
||||
"unit": "periods",
|
||||
"integer": true
|
||||
},
|
||||
"loan_payment_count": {
|
||||
"min": 2,
|
||||
"max": 5,
|
||||
"integer": true
|
||||
},
|
||||
"compounding_frequency_options": {
|
||||
"annually": 1,
|
||||
"semi-annually": 2,
|
||||
"quarterly": 4,
|
||||
"monthly": 12,
|
||||
"bi-monthly": 6,
|
||||
"semi-monthly": 24
|
||||
},
|
||||
"gradient_amount": {
|
||||
"min": 100,
|
||||
"max": 5000,
|
||||
"currency": "Php",
|
||||
"decimals": 2,
|
||||
"default_display_precision": 2
|
||||
},
|
||||
"date_general": {
|
||||
"description": "General date, used for start_date and end_date generation.",
|
||||
"type": "date",
|
||||
"min_year": 1990,
|
||||
"max_year": 2030
|
||||
},
|
||||
"time_days_exact_ordinary": {
|
||||
"description": "Number of days for exact/ordinary interest calculations (derived from dates).",
|
||||
"type": "integer",
|
||||
"min": 30,
|
||||
"max": 730,
|
||||
"unit": "days",
|
||||
"display_precision": 0
|
||||
},
|
||||
"time_base_days_exact_ordinary": {
|
||||
"description": "Time base in days for exact (365/366) or ordinary (360) interest.",
|
||||
"type": "integer",
|
||||
"options": [360, 365, 366],
|
||||
"unit": "days",
|
||||
"display_precision": 0
|
||||
},
|
||||
"time_years_fractional": {
|
||||
"description": "Time period as a fraction of a year (calculated).",
|
||||
"type": "float",
|
||||
"unit": "years",
|
||||
"display_precision": 6
|
||||
}
|
||||
}
|
||||
6
main.py
Normal file
6
main.py
Normal file
@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from problem-generator!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "problem-generator"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = []
|
||||
63
src/data_loader.py
Normal file
63
src/data_loader.py
Normal file
@ -0,0 +1,63 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data')
|
||||
BUILDING_BLOCKS_DIR = os.path.join(os.path.dirname(__file__), '..', 'building_blocks')
|
||||
|
||||
def load_json_file(file_path):
|
||||
"""Loads a JSON file from the given path."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found at {file_path}")
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
print(f"Error: Could not decode JSON from {file_path}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred while loading {file_path}: {e}")
|
||||
return None
|
||||
|
||||
def get_value_ranges():
|
||||
"""Loads value_ranges.json data."""
|
||||
file_path = os.path.join(DATA_DIR, 'value_ranges.json')
|
||||
return load_json_file(file_path)
|
||||
|
||||
def get_names_data():
|
||||
"""Loads names.json data."""
|
||||
file_path = os.path.join(DATA_DIR, 'names.json')
|
||||
return load_json_file(file_path)
|
||||
|
||||
def get_text_snippets():
|
||||
"""Loads text_snippets.json data."""
|
||||
file_path = os.path.join(DATA_DIR, 'text_snippets.json')
|
||||
return load_json_file(file_path)
|
||||
|
||||
def get_financial_concepts():
|
||||
"""Loads financial_concepts.json data."""
|
||||
file_path = os.path.join(BUILDING_BLOCKS_DIR, 'financial_concepts.json')
|
||||
return load_json_file(file_path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Test functions
|
||||
value_ranges = get_value_ranges()
|
||||
if value_ranges:
|
||||
print("Successfully loaded value_ranges.json")
|
||||
# print(json.dumps(value_ranges, indent=2))
|
||||
|
||||
names_data = get_names_data()
|
||||
if names_data:
|
||||
print("Successfully loaded names.json")
|
||||
# print(json.dumps(names_data, indent=2))
|
||||
|
||||
text_snippets = get_text_snippets()
|
||||
if text_snippets:
|
||||
print("Successfully loaded text_snippets.json")
|
||||
# print(json.dumps(text_snippets, indent=2))
|
||||
|
||||
financial_concepts = get_financial_concepts()
|
||||
if financial_concepts:
|
||||
print("Successfully loaded financial_concepts.json")
|
||||
# print(json.dumps(financial_concepts, indent=2))
|
||||
133
src/date_utils.py
Normal file
133
src/date_utils.py
Normal file
@ -0,0 +1,133 @@
|
||||
from datetime import date, timedelta
|
||||
import random
|
||||
|
||||
def is_leap_year(year):
|
||||
"""Determines if a year is a leap year."""
|
||||
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
||||
|
||||
def days_in_year(year):
|
||||
"""Returns the number of days in a given year."""
|
||||
return 366 if is_leap_year(year) else 365
|
||||
|
||||
def days_in_month(year, month):
|
||||
"""Returns the number of days in a specific month of a year."""
|
||||
if month == 2:
|
||||
return 29 if is_leap_year(year) else 28
|
||||
elif month in [4, 6, 9, 11]:
|
||||
return 30
|
||||
else:
|
||||
return 31
|
||||
|
||||
def get_random_date(start_year=2000, end_year=2030):
|
||||
"""Generates a random date within a given year range."""
|
||||
year = random.randint(start_year, end_year)
|
||||
month = random.randint(1, 12)
|
||||
day = random.randint(1, days_in_month(year, month))
|
||||
return date(year, month, day)
|
||||
|
||||
def get_random_date_period(min_days=30, max_days=730, base_year_range=(1990, 2025)):
|
||||
"""
|
||||
Generates a random start date and an end date that is min_days to max_days after the start date.
|
||||
Returns (start_date, end_date, number_of_days).
|
||||
"""
|
||||
start_date = get_random_date(base_year_range[0], base_year_range[1])
|
||||
num_days_in_period = random.randint(min_days, max_days)
|
||||
end_date = start_date + timedelta(days=num_days_in_period)
|
||||
|
||||
# The actual number of days between two dates, inclusive of start and exclusive of end,
|
||||
# or inclusive of both if counting "duration". For interest calculations, it's often
|
||||
# the difference as calculated by date objects.
|
||||
# For exact simple interest, the problem usually implies counting the actual days in the period.
|
||||
# timedelta already gives the difference. If the problem implies "from date X to date Y inclusive",
|
||||
# then it might be (end_date - start_date).days + 1.
|
||||
# However, the sample problems (e.g. Dec 27 to Mar 23) count days like:
|
||||
# Dec: 31-27 = 4. Jan: 31. Feb: 28. Mar: 23. Total = 86.
|
||||
# (date(2003,3,23) - date(2002,12,27)).days = 86. So timedelta is correct.
|
||||
|
||||
actual_days_difference = (end_date - start_date).days
|
||||
|
||||
return start_date, end_date, actual_days_difference
|
||||
|
||||
def calculate_exact_days(start_date_obj, end_date_obj):
|
||||
"""
|
||||
Calculates the exact number of days between two date objects.
|
||||
This is consistent with how financial day counts are often done.
|
||||
"""
|
||||
if not isinstance(start_date_obj, date) or not isinstance(end_date_obj, date):
|
||||
raise ValueError("Inputs must be datetime.date objects.")
|
||||
if start_date_obj > end_date_obj:
|
||||
raise ValueError("Start date cannot be after end date.")
|
||||
|
||||
return (end_date_obj - start_date_obj).days
|
||||
|
||||
def format_date_for_display(date_obj):
|
||||
"""Formats a date object as 'Month Day, Year' (e.g., January 1, 2023)."""
|
||||
if not isinstance(date_obj, date):
|
||||
return str(date_obj)
|
||||
return date_obj.strftime("%B %d, %Y")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Testing Date Utilities:")
|
||||
|
||||
# Test is_leap_year
|
||||
print(f"\n--- Testing is_leap_year ---")
|
||||
print(f"Is 2000 a leap year? {is_leap_year(2000)} (Expected: True)")
|
||||
assert is_leap_year(2000)
|
||||
print(f"Is 1900 a leap year? {is_leap_year(1900)} (Expected: False)")
|
||||
assert not is_leap_year(1900)
|
||||
print(f"Is 2023 a leap year? {is_leap_year(2023)} (Expected: False)")
|
||||
assert not is_leap_year(2023)
|
||||
print(f"Is 2024 a leap year? {is_leap_year(2024)} (Expected: True)")
|
||||
assert is_leap_year(2024)
|
||||
|
||||
# Test days_in_year
|
||||
print(f"\n--- Testing days_in_year ---")
|
||||
print(f"Days in 2023: {days_in_year(2023)} (Expected: 365)")
|
||||
assert days_in_year(2023) == 365
|
||||
print(f"Days in 2024: {days_in_year(2024)} (Expected: 366)")
|
||||
assert days_in_year(2024) == 366
|
||||
|
||||
# Test days_in_month
|
||||
print(f"\n--- Testing days_in_month ---")
|
||||
print(f"Days in Feb 2023: {days_in_month(2023, 2)} (Expected: 28)")
|
||||
assert days_in_month(2023, 2) == 28
|
||||
print(f"Days in Feb 2024: {days_in_month(2024, 2)} (Expected: 29)")
|
||||
assert days_in_month(2024, 2) == 29
|
||||
print(f"Days in Apr 2023: {days_in_month(2023, 4)} (Expected: 30)")
|
||||
assert days_in_month(2023, 4) == 30
|
||||
print(f"Days in Jan 2023: {days_in_month(2023, 1)} (Expected: 31)")
|
||||
assert days_in_month(2023, 1) == 31
|
||||
|
||||
# Test get_random_date
|
||||
print(f"\n--- Testing get_random_date ---")
|
||||
for _ in range(3):
|
||||
rd = get_random_date(2020, 2022)
|
||||
print(f"Random date: {rd} ({format_date_for_display(rd)})")
|
||||
assert 2020 <= rd.year <= 2022
|
||||
|
||||
# Test get_random_date_period
|
||||
print(f"\n--- Testing get_random_date_period ---")
|
||||
for _ in range(3):
|
||||
start, end, num_days = get_random_date_period(min_days=10, max_days=90)
|
||||
print(f"Period: {format_date_for_display(start)} to {format_date_for_display(end)}, Days: {num_days}")
|
||||
assert 10 <= num_days <= 90
|
||||
assert (end - start).days == num_days
|
||||
|
||||
# Test calculate_exact_days (matches sample problem from notes)
|
||||
# Problem 3: December 27, 2002 to March 23, 2003 -> 86 days
|
||||
print(f"\n--- Testing calculate_exact_days (Sample Problem) ---")
|
||||
d1 = date(2002, 12, 27)
|
||||
d2 = date(2003, 3, 23)
|
||||
exact_days_sample = calculate_exact_days(d1, d2)
|
||||
print(f"Days from {format_date_for_display(d1)} to {format_date_for_display(d2)}: {exact_days_sample} (Expected: 86)")
|
||||
assert exact_days_sample == 86
|
||||
|
||||
# Problem 5: February 14, 1984 to November 30, 1984 -> 290 days (1984 is leap)
|
||||
d3 = date(1984, 2, 14)
|
||||
d4 = date(1984, 11, 30)
|
||||
exact_days_sample2 = calculate_exact_days(d3, d4)
|
||||
print(f"Days from {format_date_for_display(d3)} to {format_date_for_display(d4)}: {exact_days_sample2} (Expected: 290)")
|
||||
assert exact_days_sample2 == 290
|
||||
|
||||
print("\nAll date utility tests passed if no assertion errors.")
|
||||
123
src/formula_evaluator.py
Normal file
123
src/formula_evaluator.py
Normal file
@ -0,0 +1,123 @@
|
||||
import math
|
||||
import decimal
|
||||
|
||||
# Define a context for safe evaluation
|
||||
# Only include necessary math functions and constants
|
||||
SAFE_MATH_CONTEXT = {
|
||||
"math": math, # Provides access to math.log, math.exp, math.pow, etc.
|
||||
"Decimal": decimal.Decimal, # For precise arithmetic if needed
|
||||
# Standard operators (+, -, *, /, **) are inherently available.
|
||||
# Built-in functions like round() are also available.
|
||||
}
|
||||
|
||||
def evaluate_formula(formula_str, context_vars):
|
||||
"""
|
||||
Safely evaluates a mathematical formula string using a given context of variables.
|
||||
|
||||
Args:
|
||||
formula_str (str): The formula to evaluate (e.g., "P * (1 + i * n)").
|
||||
context_vars (dict): A dictionary of variable names and their numerical values
|
||||
(e.g., {"P": 1000, "i": 0.05, "n": 2}).
|
||||
|
||||
Returns:
|
||||
The result of the evaluation, or None if an error occurs.
|
||||
"""
|
||||
if not isinstance(formula_str, str):
|
||||
print(f"Error: Formula string must be a string, got {type(formula_str)}")
|
||||
return None
|
||||
if not isinstance(context_vars, dict):
|
||||
print(f"Error: Context variables must be a dictionary, got {type(context_vars)}")
|
||||
return None
|
||||
|
||||
# Combine the safe math context with the problem-specific variables
|
||||
# Problem-specific variables can override items in SAFE_MATH_CONTEXT if names clash,
|
||||
# but this is generally not expected for simple variable names like P, F, i, n.
|
||||
evaluation_context = {**SAFE_MATH_CONTEXT, **context_vars}
|
||||
|
||||
try:
|
||||
# Using eval() here, which is generally risky if formula_str is from an untrusted source.
|
||||
# However, in this application, formula_str comes from our own controlled
|
||||
# financial_concepts.json file, and evaluation_context is restricted.
|
||||
result = eval(formula_str, {"__builtins__": {}}, evaluation_context)
|
||||
|
||||
# Ensure result is a standard float or int if it's a Decimal, for consistency
|
||||
if isinstance(result, decimal.Decimal):
|
||||
return float(result)
|
||||
return result
|
||||
except NameError as e:
|
||||
print(f"Error evaluating formula '{formula_str}': Variable not defined - {e}")
|
||||
print(f"Available context keys: {list(evaluation_context.keys())}")
|
||||
return None
|
||||
except TypeError as e:
|
||||
print(f"Error evaluating formula '{formula_str}': Type error - {e}")
|
||||
return None
|
||||
except ZeroDivisionError:
|
||||
print(f"Error evaluating formula '{formula_str}': Division by zero.")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred while evaluating formula '{formula_str}': {e}")
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Testing Formula Evaluator:")
|
||||
|
||||
# Test Case 1: Simple Interest Future Value
|
||||
formula1 = "P * (1 + i * n)"
|
||||
context1 = {"P": 1000, "i": 0.05, "n": 2}
|
||||
result1 = evaluate_formula(formula1, context1)
|
||||
print(f"\nFormula: {formula1}, Context: {context1}")
|
||||
print(f"Expected: {1000 * (1 + 0.05 * 2)}")
|
||||
print(f"Actual: {result1}")
|
||||
assert result1 == 1100.0
|
||||
|
||||
# Test Case 2: Compound Interest Future Value
|
||||
formula2 = "P * (1 + i)**n"
|
||||
context2 = {"P": 5000, "i": 0.02, "n": 10} # e.g. 8% quarterly for 2.5 years -> i=0.08/4=0.02, n=2.5*4=10
|
||||
result2 = evaluate_formula(formula2, context2)
|
||||
print(f"\nFormula: {formula2}, Context: {context2}")
|
||||
expected2 = 5000 * (1 + 0.02)**10
|
||||
print(f"Expected: {expected2}")
|
||||
print(f"Actual: {result2}")
|
||||
assert abs(result2 - expected2) < 1e-9 # Compare floats with tolerance
|
||||
|
||||
# Test Case 3: Using math.log (e.g., solving for n in compound interest)
|
||||
# n = math.log(F / P) / math.log(1 + i)
|
||||
formula3 = "math.log(F / P) / math.log(1 + i)"
|
||||
context3 = {"F": 6094.972103200001, "P": 5000, "i": 0.02}
|
||||
result3 = evaluate_formula(formula3, context3)
|
||||
print(f"\nFormula: {formula3}, Context: {context3}")
|
||||
expected3 = math.log(6094.972103200001 / 5000) / math.log(1 + 0.02)
|
||||
print(f"Expected: {expected3}") # Should be close to 10
|
||||
print(f"Actual: {result3}")
|
||||
assert abs(result3 - 10.0) < 1e-9
|
||||
|
||||
# Test Case 4: Using math.exp (e.g., continuous compounding)
|
||||
# F = P * math.exp(r * t)
|
||||
formula4 = "P * math.exp(r * t)"
|
||||
context4 = {"P": 100, "r": 0.05, "t": 1}
|
||||
result4 = evaluate_formula(formula4, context4)
|
||||
print(f"\nFormula: {formula4}, Context: {context4}")
|
||||
expected4 = 100 * math.exp(0.05 * 1)
|
||||
print(f"Expected: {expected4}")
|
||||
print(f"Actual: {result4}")
|
||||
assert abs(result4 - expected4) < 1e-9
|
||||
|
||||
# Test Case 5: Undefined variable
|
||||
formula5 = "P * (1 + interest_rate * n)" # 'interest_rate' not in context
|
||||
context5 = {"P": 1000, "i": 0.05, "n": 2}
|
||||
result5 = evaluate_formula(formula5, context5)
|
||||
print(f"\nFormula: {formula5}, Context: {context5}")
|
||||
print(f"Expected: None (due to error)")
|
||||
print(f"Actual: {result5}")
|
||||
assert result5 is None
|
||||
|
||||
# Test Case 6: Division by zero
|
||||
formula6 = "P / n"
|
||||
context6 = {"P": 1000, "n": 0}
|
||||
result6 = evaluate_formula(formula6, context6)
|
||||
print(f"\nFormula: {formula6}, Context: {context6}")
|
||||
print(f"Expected: None (due to error)")
|
||||
print(f"Actual: {result6}")
|
||||
assert result6 is None
|
||||
|
||||
print("\nAll tests passed if no assertion errors.")
|
||||
209
src/narrative_builder.py
Normal file
209
src/narrative_builder.py
Normal file
@ -0,0 +1,209 @@
|
||||
import random
|
||||
from src.data_loader import get_names_data, get_text_snippets
|
||||
from src.value_sampler import format_value_for_display
|
||||
|
||||
# Cache loaded data
|
||||
NAMES_DATA = None
|
||||
TEXT_SNIPPETS = None
|
||||
|
||||
def _get_names_data_cached():
|
||||
global NAMES_DATA
|
||||
if NAMES_DATA is None:
|
||||
NAMES_DATA = get_names_data()
|
||||
return NAMES_DATA
|
||||
|
||||
def _get_text_snippets_cached():
|
||||
global TEXT_SNIPPETS
|
||||
if TEXT_SNIPPETS is None:
|
||||
TEXT_SNIPPETS = get_text_snippets()
|
||||
return TEXT_SNIPPETS
|
||||
|
||||
def get_random_actor():
|
||||
"""Generates a random actor (person or company)."""
|
||||
names_data = _get_names_data_cached()
|
||||
snippets = _get_text_snippets_cached()
|
||||
|
||||
actor_type = random.choice(["person", "company"])
|
||||
actor_string_template = ""
|
||||
|
||||
if actor_type == "person":
|
||||
actor_string_template = random.choice(snippets["actors_person"])
|
||||
title = random.choice(names_data["persons_titles"])
|
||||
first_name = random.choice(names_data["persons_first_names"])
|
||||
last_name = random.choice(names_data["persons_last_names"])
|
||||
return actor_string_template.format(person_title=title, person_first_name=first_name, person_last_name=last_name)
|
||||
else: # company
|
||||
actor_string_template = random.choice(snippets["actors_company"])
|
||||
prefix = random.choice(names_data["companies_generic_prefix"])
|
||||
|
||||
# Decide if to use generic suffix or industry specific
|
||||
if random.random() < 0.7: # 70% chance for generic suffix
|
||||
suffix = random.choice(names_data["companies_generic_suffix"])
|
||||
return actor_string_template.format(company_prefix=prefix, company_suffix=suffix, company_industry=suffix) # company_industry for templates that might use it
|
||||
else:
|
||||
industry_key = random.choice(list(names_data["companies_industry_specific"].keys()))
|
||||
industry_suffix = random.choice(names_data["companies_industry_specific"][industry_key])
|
||||
return actor_string_template.format(company_prefix=prefix, company_suffix=industry_suffix, company_industry=industry_suffix)
|
||||
|
||||
|
||||
def build_narrative(financial_concept, known_values_data, unknown_variable_key):
|
||||
"""
|
||||
Constructs a problem narrative dynamically.
|
||||
|
||||
Args:
|
||||
financial_concept (dict): The financial concept definition.
|
||||
known_values_data (dict): Dict of known variable data (from value_sampler).
|
||||
e.g., {"P": {'value': 1000, ...}, "i_simple_annual": {'value': 0.05, ...}}
|
||||
unknown_variable_key (str): The key of the variable to be solved for (e.g., "F_simple").
|
||||
|
||||
Returns:
|
||||
str: The generated problem narrative.
|
||||
"""
|
||||
snippets = _get_text_snippets_cached()
|
||||
actor = get_random_actor()
|
||||
|
||||
narrative_parts = []
|
||||
|
||||
# 1. Introduction / Scenario Setup
|
||||
intro_template = random.choice(snippets["scenario_introductions"])
|
||||
narrative_parts.append(intro_template.format(actor=actor))
|
||||
|
||||
# 2. Describe the action (loan, investment) and known amounts
|
||||
# This part needs to be more intelligent based on the concept and knowns/unknowns
|
||||
|
||||
action_type = "loan" # Default, can be refined by concept hooks
|
||||
if any(hook in financial_concept.get("narrative_hooks", []) for hook in ["investment", "deposit"]):
|
||||
action_type = "investment"
|
||||
|
||||
# Choose verb tense (past is common for setting up a problem)
|
||||
if action_type == "loan":
|
||||
action_verb_template_group = snippets["actions_loan_past_singular"] # Assuming singular actor for now
|
||||
else: # investment
|
||||
action_verb_template_group = snippets["actions_investment_past_singular"]
|
||||
|
||||
action_verb = random.choice(action_verb_template_group)
|
||||
|
||||
# Primary known value (e.g., Principal if F is unknown, or Future Value if P is unknown)
|
||||
primary_value_key = ""
|
||||
primary_value_desc = ""
|
||||
|
||||
if "P" in known_values_data and unknown_variable_key.startswith("F"): # Solving for Future Value
|
||||
primary_value_key = "P"
|
||||
primary_value_desc = "an initial amount of"
|
||||
elif "F_simple" in known_values_data and unknown_variable_key == "P": # Solving for Principal from Simple Future
|
||||
primary_value_key = "F_simple"
|
||||
primary_value_desc = "a future target of"
|
||||
action_verb = random.choice(snippets["actions_receive_present_singular"]) # "wants to receive"
|
||||
elif "F_compound" in known_values_data and unknown_variable_key == "P": # Solving for Principal from Compound Future
|
||||
primary_value_key = "F_compound"
|
||||
primary_value_desc = "a future target of"
|
||||
action_verb = random.choice(snippets["actions_receive_present_singular"])
|
||||
# Add more conditions for other unknowns like I_simple, rates, time etc.
|
||||
|
||||
if primary_value_key and primary_value_key in known_values_data:
|
||||
val_data = known_values_data[primary_value_key]
|
||||
narrative_parts.append(f"{action_verb} {primary_value_desc} {format_value_for_display(val_data)}")
|
||||
elif "P" in known_values_data: # Fallback if primary logic missed, describe P if known
|
||||
narrative_parts.append(f"{action_verb} {format_value_for_display(known_values_data['P'])}")
|
||||
|
||||
|
||||
# 3. Add purpose if applicable
|
||||
if action_type == "loan" and random.random() < 0.5:
|
||||
item_loan = random.choice(_get_names_data_cached()["items_loan_general"])
|
||||
purpose_phrase = random.choice(snippets["purpose_phrases_loan"]).format(item_loan=item_loan)
|
||||
narrative_parts.append(purpose_phrase)
|
||||
elif action_type == "investment" and random.random() < 0.5:
|
||||
item_investment = random.choice(_get_names_data_cached()["items_investment_general"])
|
||||
purpose_phrase = random.choice(snippets["purpose_phrases_investment"]).format(item_investment=item_investment)
|
||||
narrative_parts.append(purpose_phrase)
|
||||
|
||||
# 4. Describe other knowns (rate, time, compounding)
|
||||
# Simple Interest Rate
|
||||
if "i_simple_annual" in known_values_data:
|
||||
rate_phrase = random.choice(snippets["rate_phrases"])
|
||||
narrative_parts.append(f"{rate_phrase} {format_value_for_display(known_values_data['i_simple_annual'])}")
|
||||
|
||||
# Nominal Compound Interest Rate
|
||||
if "r_nominal_annual" in known_values_data:
|
||||
rate_phrase = random.choice(snippets["rate_phrases"])
|
||||
narrative_parts.append(f"{rate_phrase} {format_value_for_display(known_values_data['r_nominal_annual'])}")
|
||||
if "m_compounding_periods_per_year" in known_values_data:
|
||||
freq_name = known_values_data["m_compounding_periods_per_year"]["name"]
|
||||
freq_adverb = snippets["compounding_frequency_adverbs"].get(freq_name, freq_name)
|
||||
comp_phrase = random.choice(snippets["compounding_phrases"]).format(compounding_frequency_adverb=freq_adverb)
|
||||
narrative_parts.append(comp_phrase)
|
||||
|
||||
# Time (handle years, months, days - prefer years if available)
|
||||
time_described = False
|
||||
if "n_time_years" in known_values_data:
|
||||
time_phrase = random.choice(snippets["time_phrases_duration"])
|
||||
narrative_parts.append(f"{time_phrase} {format_value_for_display(known_values_data['n_time_years'])}")
|
||||
time_described = True
|
||||
elif "t_years" in known_values_data: # for compound interest
|
||||
time_phrase = random.choice(snippets["time_phrases_duration"])
|
||||
narrative_parts.append(f"{time_phrase} {format_value_for_display(known_values_data['t_years'])}")
|
||||
time_described = True
|
||||
|
||||
# If time in months or days is primary and years not directly given (less common for this structure)
|
||||
if not time_described and "n_time_months" in known_values_data:
|
||||
time_phrase = random.choice(snippets["time_phrases_duration"])
|
||||
narrative_parts.append(f"{time_phrase} {format_value_for_display(known_values_data['n_time_months'])}")
|
||||
elif not time_described and "n_time_days" in known_values_data:
|
||||
# For exact simple interest, dates are usually given instead of number of days directly
|
||||
if "start_date" in known_values_data and "end_date" in known_values_data:
|
||||
narrative_parts.append(f"from {format_value_for_display(known_values_data['start_date'])} to {format_value_for_display(known_values_data['end_date'])}")
|
||||
else: # Fallback if only days are given
|
||||
time_phrase = random.choice(snippets["time_phrases_duration"])
|
||||
narrative_parts.append(f"{time_phrase} {format_value_for_display(known_values_data['n_time_days'])}")
|
||||
|
||||
|
||||
# 5. Formulate the question about the unknown variable
|
||||
question_starter = random.choice(snippets["question_starters_what_is"]) # Default
|
||||
if unknown_variable_key.lower().startswith("p"):
|
||||
question_starter = random.choice(snippets["question_starters_how_much"])
|
||||
elif "time" in unknown_variable_key.lower():
|
||||
question_starter = random.choice(snippets["question_starters_how_long"])
|
||||
|
||||
unknown_desc = snippets.get("variable_descriptions", {}).get(unknown_variable_key, unknown_variable_key.replace("_", " "))
|
||||
|
||||
question_closure_prefix = random.choice(snippets["scenario_closures_question_prefix"])
|
||||
narrative_parts.append(f"{question_closure_prefix} {question_starter.lower()} {unknown_desc}?")
|
||||
|
||||
return " ".join(part.strip() for part in narrative_parts if part).replace(" .", ".").replace(" ?", "?") + "."
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Testing Narrative Builder:")
|
||||
# Mock data for testing (normally loaded by problem_engine)
|
||||
mock_concept_simple_f = {
|
||||
"concept_id": "SIMPLE_INTEREST_SOLVE_FOR_F",
|
||||
"narrative_hooks": ["loan", "simple interest", "future amount"]
|
||||
}
|
||||
mock_known_simple_f = {
|
||||
"P": {'key': 'P', 'value': 10000.0, 'currency': 'Php', 'display_precision': 2},
|
||||
"i_simple_annual": {'key': 'i_simple_annual', 'value': 0.12, 'unit_display': '% per annum', 'display_precision': 2},
|
||||
"n_time_years": {'key': 'n_time_years', 'value': 2.0, 'unit': 'years', 'display_precision': 1}
|
||||
}
|
||||
|
||||
print("\n--- Test Case 1: Simple Interest, Solve for F ---")
|
||||
narrative1 = build_narrative(mock_concept_simple_f, mock_known_simple_f, "F_simple")
|
||||
print(narrative1)
|
||||
|
||||
mock_concept_compound_p = {
|
||||
"concept_id": "COMPOUND_INTEREST_SOLVE_FOR_P_FROM_F",
|
||||
"narrative_hooks": ["investment", "compound interest", "present value"]
|
||||
}
|
||||
mock_known_compound_p = {
|
||||
"F_compound": {'key': 'F_compound', 'value': 25000.0, 'currency': 'Php', 'display_precision': 2},
|
||||
"r_nominal_annual": {'key': 'r_nominal_annual', 'value': 0.08, 'unit_display': '%', 'display_precision': 2},
|
||||
"m_compounding_periods_per_year": {'key': 'm_compounding_periods_per_year', 'name': 'quarterly', 'value': 4},
|
||||
"t_years": {'key': 't_years', 'value': 5.0, 'unit': 'years', 'display_precision': 1}
|
||||
}
|
||||
print("\n--- Test Case 2: Compound Interest, Solve for P ---")
|
||||
narrative2 = build_narrative(mock_concept_compound_p, mock_known_compound_p, "P")
|
||||
print(narrative2)
|
||||
|
||||
# Test with a different actor type
|
||||
print("\n--- Test Case 3: Different Actor ---")
|
||||
narrative3 = build_narrative(mock_concept_simple_f, mock_known_simple_f, "F_simple")
|
||||
print(narrative3)
|
||||
279
src/problem_engine.py
Normal file
279
src/problem_engine.py
Normal file
@ -0,0 +1,279 @@
|
||||
import random
|
||||
import math # For direct use if not going through formula_evaluator for simple conversions
|
||||
from src import data_loader
|
||||
from src import value_sampler
|
||||
from src import formula_evaluator
|
||||
from src import narrative_builder
|
||||
from src import solution_presenter
|
||||
from src import date_utils # For potential date-based time calculations
|
||||
|
||||
# --- Cached Data ---
|
||||
FINANCIAL_CONCEPTS = None
|
||||
TEXT_SNIPPETS_DATA = None # To avoid conflict with value_sampler's TEXT_SNIPPETS
|
||||
NAMES_DATA_CACHE = None
|
||||
VALUE_RANGES_CACHE = None
|
||||
|
||||
def _load_all_data_cached():
|
||||
global FINANCIAL_CONCEPTS, TEXT_SNIPPETS_DATA, NAMES_DATA_CACHE, VALUE_RANGES_CACHE
|
||||
if FINANCIAL_CONCEPTS is None:
|
||||
FINANCIAL_CONCEPTS = data_loader.get_financial_concepts()
|
||||
if TEXT_SNIPPETS_DATA is None:
|
||||
TEXT_SNIPPETS_DATA = data_loader.get_text_snippets() # Loaded for solution_presenter and narrative_builder
|
||||
if NAMES_DATA_CACHE is None:
|
||||
NAMES_DATA_CACHE = data_loader.get_names_data() # Loaded for narrative_builder
|
||||
if VALUE_RANGES_CACHE is None:
|
||||
VALUE_RANGES_CACHE = data_loader.get_value_ranges() # Loaded for value_sampler
|
||||
|
||||
# Ensure sub-modules also use cached data if they have their own caches
|
||||
# This is already handled by them checking their global cache variables.
|
||||
|
||||
# --- Variable Mapping ---
|
||||
# Maps concept variable names to value_ranges.json keys
|
||||
# This might need to be more dynamic or extensive based on concept variations.
|
||||
CONCEPT_VAR_TO_VALUERANGE_KEY = {
|
||||
"P": "principal", # Could also be loan_amount, investment_amount
|
||||
"F_simple": "future_value",
|
||||
"F_compound": "future_value",
|
||||
"F_continuous": "future_value",
|
||||
"F_maturity": "future_value", # For Banker's Discount, F is the maturity value
|
||||
"I_simple": "interest_amount",
|
||||
"Db_discount_amount": "interest_amount", # Or a specific "discount_amount" range
|
||||
"P_proceeds": "principal", # Proceeds are a present value
|
||||
|
||||
"i_simple_annual": "simple_interest_rate_annual",
|
||||
"n_time_years": "time_years", # For simple interest
|
||||
"n_time_months": "time_months",
|
||||
"n_time_days": "time_days",
|
||||
|
||||
"r_nominal_annual": "compound_interest_rate_nominal",
|
||||
"t_years": "time_years", # For compound/continuous interest time
|
||||
# m_compounding_periods_per_year is handled specially
|
||||
"ER": "compound_interest_rate_nominal", # Effective rate is a rate
|
||||
"d_discount_rate": "discount_rate_bankers",
|
||||
"i_simple_equivalent": "simple_interest_rate_annual" # for comparison rates
|
||||
}
|
||||
|
||||
def generate_problem():
|
||||
"""
|
||||
Generates a complete financial math problem with narrative and solution.
|
||||
"""
|
||||
_load_all_data_cached()
|
||||
if not FINANCIAL_CONCEPTS:
|
||||
return {"error": "Failed to load financial concepts."}
|
||||
|
||||
selected_concept = random.choice(FINANCIAL_CONCEPTS)
|
||||
target_unknown = selected_concept["target_unknown"]
|
||||
|
||||
all_variables_data_formatted = {} # Stores full data dicts from value_sampler, keyed by concept var name
|
||||
formula_context_vars = {} # Stores raw numerical values for formula evaluation, keyed by concept var name
|
||||
|
||||
# 1. Generate Known Values
|
||||
for var_key in selected_concept.get("required_knowns_for_target", []):
|
||||
if var_key == "m_compounding_periods_per_year":
|
||||
comp_freq_data = value_sampler.get_random_compounding_frequency()
|
||||
if comp_freq_data:
|
||||
all_variables_data_formatted[var_key] = comp_freq_data # Stores {'name': 'monthly', 'm_value': 12, ...}
|
||||
formula_context_vars[var_key] = comp_freq_data["m_value"]
|
||||
else:
|
||||
return {"error": f"Failed to generate compounding frequency for {selected_concept['concept_id']}"}
|
||||
else:
|
||||
value_range_key = CONCEPT_VAR_TO_VALUERANGE_KEY.get(var_key)
|
||||
if not value_range_key:
|
||||
# This might be an intermediate variable that shouldn't be generated directly
|
||||
# Or a variable that doesn't directly map to a single range (e.g. time in days from dates)
|
||||
# For now, assume required_knowns are directly generatable or handled specifically.
|
||||
print(f"Warning: No value_range_key mapping for required known '{var_key}' in concept '{selected_concept['concept_id']}'. Skipping direct generation.")
|
||||
continue
|
||||
|
||||
var_data = value_sampler.get_value_for_variable(value_range_key)
|
||||
if var_data:
|
||||
all_variables_data_formatted[var_key] = var_data
|
||||
formula_context_vars[var_key] = var_data["value"]
|
||||
else:
|
||||
return {"error": f"Failed to generate value for '{var_key}' (mapped from '{value_range_key}') for concept '{selected_concept['concept_id']}'"}
|
||||
|
||||
# 2. Handle Time Conversions (Example for Simple Interest if n_time_years is needed but months/days are used)
|
||||
# This logic can be expanded. For now, assume n_time_years is directly generated if listed as required.
|
||||
# If a concept *requires* n_time_years, but we want to sometimes *start* from months or days:
|
||||
if selected_concept["financial_topic"] == "Simple Interest":
|
||||
if "n_time_years" in formula_context_vars and random.random() < 0.3: # 30% chance to convert from months
|
||||
original_months_data = value_sampler.get_value_for_variable("time_months")
|
||||
if original_months_data:
|
||||
all_variables_data_formatted["n_time_months"] = original_months_data
|
||||
# Override n_time_years with converted value
|
||||
formula_context_vars["n_time_years"] = original_months_data["value"] / 12.0
|
||||
# Update the n_time_years entry in all_variables_data_formatted as well
|
||||
all_variables_data_formatted["n_time_years"] = {
|
||||
'key': 'time_years', # original key from value_ranges
|
||||
'value': formula_context_vars["n_time_years"],
|
||||
'unit': 'years',
|
||||
'display_precision': all_variables_data_formatted["n_time_years"].get('display_precision', 4) # keep original precision setting
|
||||
}
|
||||
# Could add similar logic for days -> years conversion here, possibly using date_utils for exact days.
|
||||
|
||||
# 3. Calculate Intermediate Variables defined in the concept's formulas
|
||||
# These are formulas NOT for the target_unknown.
|
||||
# Ensure they are calculated in a sensible order if there are dependencies.
|
||||
# For now, assume formulas are simple enough or ordered correctly in JSON.
|
||||
# A more robust way would be to build a dependency graph.
|
||||
|
||||
# Create a list of formulas to evaluate, target last
|
||||
formulas_to_eval = []
|
||||
if isinstance(selected_concept["formulas"], dict):
|
||||
for var, formula_str in selected_concept["formulas"].items():
|
||||
if var != target_unknown:
|
||||
formulas_to_eval.append((var, formula_str))
|
||||
if target_unknown in selected_concept["formulas"]: # Add target formula last
|
||||
formulas_to_eval.append((target_unknown, selected_concept["formulas"][target_unknown]))
|
||||
else: # Should not happen based on current JSON structure
|
||||
return {"error": f"Formulas for concept {selected_concept['concept_id']} are not in expected dict format."}
|
||||
|
||||
|
||||
calculated_solution_value = None
|
||||
calculated_solution_data_formatted = None
|
||||
|
||||
for var_to_calc, formula_str in formulas_to_eval:
|
||||
calc_value = formula_evaluator.evaluate_formula(formula_str, formula_context_vars)
|
||||
if calc_value is None:
|
||||
return {"error": f"Failed to evaluate formula for '{var_to_calc}' in concept '{selected_concept['concept_id']}'. Context: {formula_context_vars}"}
|
||||
|
||||
formula_context_vars[var_to_calc] = calc_value # Add to context for subsequent formulas
|
||||
|
||||
# Create formatted data for this calculated variable (for narrative/solution)
|
||||
# Need to determine its type (currency, rate, time, etc.) for proper formatting.
|
||||
# We can infer from CONCEPT_VAR_TO_VALUERANGE_KEY or add 'type' to financial_concepts.json vars.
|
||||
value_range_key_for_calc_var = CONCEPT_VAR_TO_VALUERANGE_KEY.get(var_to_calc)
|
||||
base_config = VALUE_RANGES_CACHE.get(value_range_key_for_calc_var, {}) if VALUE_RANGES_CACHE else {}
|
||||
|
||||
formatted_data_for_calc_var = {
|
||||
'key': var_to_calc, # Use the concept's variable name
|
||||
'value': calc_value,
|
||||
'currency': base_config.get('currency'),
|
||||
'unit': base_config.get('unit'),
|
||||
'unit_display': base_config.get('unit_display'),
|
||||
'display_precision': base_config.get('display_precision', base_config.get('decimals'))
|
||||
}
|
||||
# If it's a rate, ensure unit_display is set correctly
|
||||
if "rate" in var_to_calc.lower() and not formatted_data_for_calc_var.get('unit_display'):
|
||||
if value_range_key_for_calc_var and VALUE_RANGES_CACHE and value_range_key_for_calc_var in VALUE_RANGES_CACHE:
|
||||
formatted_data_for_calc_var['unit_display'] = VALUE_RANGES_CACHE[value_range_key_for_calc_var].get('unit_display', '%') # Default to %
|
||||
else: # Fallback if no specific range key
|
||||
formatted_data_for_calc_var['unit_display'] = '%' if var_to_calc != "n_total_compounding_periods" else "periods"
|
||||
|
||||
|
||||
all_variables_data_formatted[var_to_calc] = formatted_data_for_calc_var
|
||||
|
||||
if var_to_calc == target_unknown:
|
||||
calculated_solution_value = calc_value
|
||||
calculated_solution_data_formatted = formatted_data_for_calc_var
|
||||
|
||||
|
||||
if calculated_solution_value is None:
|
||||
return {"error": f"Target unknown '{target_unknown}' was not calculated for concept '{selected_concept['concept_id']}'."}
|
||||
|
||||
# 4. Build Narrative
|
||||
problem_narrative = narrative_builder.build_narrative(selected_concept, all_variables_data_formatted, target_unknown)
|
||||
|
||||
# 5. Generate Guided Solution
|
||||
solution_steps = solution_presenter.generate_guided_solution(selected_concept, all_variables_data_formatted, calculated_solution_data_formatted)
|
||||
|
||||
return {
|
||||
"concept_id": selected_concept["concept_id"],
|
||||
"topic": selected_concept["financial_topic"],
|
||||
"problem_statement": problem_narrative,
|
||||
"target_unknown_key": target_unknown,
|
||||
"known_values_data": {k: v for k, v in all_variables_data_formatted.items() if k in selected_concept.get("required_knowns_for_target", []) or k in ["m_compounding_periods_per_year", "n_time_months", "start_date", "end_date"]}, # Show originally generated knowns
|
||||
"all_variables_for_solution": all_variables_data_formatted, # Includes intermediates
|
||||
"calculated_answer_raw": calculated_solution_value,
|
||||
"calculated_answer_formatted": value_sampler.format_value_for_display(calculated_solution_data_formatted),
|
||||
"solution_steps": solution_steps
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Generating a sample financial problem...\n")
|
||||
# Ensure data is loaded for sub-modules if they cache independently on first call
|
||||
value_sampler._get_value_ranges_cached()
|
||||
narrative_builder._get_names_data_cached()
|
||||
narrative_builder._get_text_snippets_cached()
|
||||
solution_presenter._get_text_snippets_cached()
|
||||
|
||||
|
||||
for i in range(3): # Generate a few problems
|
||||
problem = generate_problem()
|
||||
print(f"\n--- Problem {i+1} ---")
|
||||
if "error" in problem:
|
||||
print(f"Error generating problem: {problem['error']}")
|
||||
else:
|
||||
print(f"Concept ID: {problem['concept_id']}")
|
||||
print(f"Topic: {problem['topic']}")
|
||||
print("\nProblem Statement:")
|
||||
print(problem['problem_statement'])
|
||||
|
||||
# print("\nKnown Values (Formatted for display):")
|
||||
# for k, v_data in problem['known_values_data'].items():
|
||||
# print(f" {k}: {value_sampler.format_value_for_display(v_data)}")
|
||||
|
||||
print(f"\nQuestion: What is the {TEXT_SNIPPETS_DATA['variable_descriptions'].get(problem['target_unknown_key'], problem['target_unknown_key'])}?")
|
||||
|
||||
print("\nGuided Solution:")
|
||||
for step in problem['solution_steps']:
|
||||
print(step)
|
||||
|
||||
print(f"\nFinal Answer ({problem['target_unknown_key']}): {problem['calculated_answer_formatted']}")
|
||||
print("---------------------------------------\n")
|
||||
|
||||
# Test a specific concept if needed for debugging
|
||||
# For example, to test COMPOUND_INTEREST_SOLVE_FOR_TIME which uses math.log
|
||||
# You might need to temporarily modify FINANCIAL_CONCEPTS to pick only this one.
|
||||
# Or add a loop to find it.
|
||||
# concepts = data_loader.get_financial_concepts()
|
||||
# time_concept = next((c for c in concepts if c["concept_id"] == "COMPOUND_INTEREST_SOLVE_FOR_TIME"), None)
|
||||
# if time_concept:
|
||||
# FINANCIAL_CONCEPTS = data_loader.get_financial_concepts() # Reload to get all
|
||||
|
||||
concepts_to_test = [
|
||||
"EXACT_SIMPLE_INTEREST_SOLVE_FOR_I",
|
||||
"ORDINARY_SIMPLE_INTEREST_SOLVE_FOR_F"
|
||||
]
|
||||
|
||||
all_concepts_loaded = data_loader.get_financial_concepts() # Ensure fresh load if not already cached by _load_all_data_cached
|
||||
|
||||
for concept_id_to_test in concepts_to_test:
|
||||
specific_concept = next((c for c in all_concepts_loaded if c["concept_id"] == concept_id_to_test), None)
|
||||
if specific_concept:
|
||||
# Temporarily override the global FINANCIAL_CONCEPTS for this specific test run
|
||||
global FINANCIAL_CONCEPTS
|
||||
original_financial_concepts = FINANCIAL_CONCEPTS
|
||||
FINANCIAL_CONCEPTS = [specific_concept]
|
||||
|
||||
print(f"\n--- Testing Specific Concept: {concept_id_to_test} ---")
|
||||
problem = generate_problem()
|
||||
if "error" in problem:
|
||||
print(f"Error generating problem: {problem['error']}")
|
||||
else:
|
||||
print(f"Concept ID: {problem['concept_id']}")
|
||||
print(f"Topic: {problem['topic']}")
|
||||
print("\nProblem Statement:")
|
||||
print(problem['problem_statement'])
|
||||
|
||||
print(f"\nQuestion: What is the {TEXT_SNIPPETS_DATA['variable_descriptions'].get(problem['target_unknown_key'], problem['target_unknown_key'])}?")
|
||||
|
||||
# print("\nKnown Values (Raw for debugging):")
|
||||
# for k, v_data in problem['known_values_data'].items():
|
||||
# print(f" {k}: {v_data['value']} (Type: {type(v_data['value'])})")
|
||||
# print("\nAll Variables for Solution (Raw for debugging):")
|
||||
# for k, v_data in problem['all_variables_for_solution'].items():
|
||||
# print(f" {k}: {v_data['value']} (Type: {type(v_data['value'])})")
|
||||
|
||||
|
||||
print("\nGuided Solution:")
|
||||
for step in problem['solution_steps']:
|
||||
print(step)
|
||||
|
||||
print(f"\nFinal Answer ({problem['target_unknown_key']}): {problem['calculated_answer_formatted']}")
|
||||
print("---------------------------------------\n")
|
||||
|
||||
FINANCIAL_CONCEPTS = original_financial_concepts # Restore original concepts
|
||||
else:
|
||||
print(f"\n--- Concept {concept_id_to_test} not found for specific testing. ---")
|
||||
377
src/solution_presenter.py
Normal file
377
src/solution_presenter.py
Normal file
@ -0,0 +1,377 @@
|
||||
import random
|
||||
from src.data_loader import get_text_snippets
|
||||
from src.value_sampler import format_value_for_display
|
||||
|
||||
# Cache loaded data
|
||||
TEXT_SNIPPETS = None
|
||||
|
||||
def _get_text_snippets_cached():
|
||||
global TEXT_SNIPPETS
|
||||
if TEXT_SNIPPETS is None:
|
||||
TEXT_SNIPPETS = get_text_snippets()
|
||||
return TEXT_SNIPPETS
|
||||
|
||||
def get_snippet(key_path):
|
||||
"""Retrieves a snippet from text_snippets.json using a dot-separated path."""
|
||||
snippets = _get_text_snippets_cached()
|
||||
keys = key_path.split('.')
|
||||
current_level = snippets
|
||||
for key in keys:
|
||||
if isinstance(current_level, dict) and key in current_level:
|
||||
current_level = current_level[key]
|
||||
else:
|
||||
return f"{{Error: Snippet not found for {key_path}}}"
|
||||
return current_level
|
||||
|
||||
def format_formula_for_display(formula_str, context_vars_data, target_variable):
|
||||
"""
|
||||
Formats a formula string for display by substituting variable placeholders with their display values.
|
||||
Example: "F = P * (1 + i * n)" with context becomes "F = Php 1,000.00 * (1 + 0.05 * 2.0 years)"
|
||||
"""
|
||||
# This is a simplified version. A more robust version might parse the formula
|
||||
# or use regex to replace only whole variable names.
|
||||
# For now, simple string replacement.
|
||||
|
||||
# Sort keys by length (descending) to replace longer variable names first (e.g., "i_simple_annual" before "i")
|
||||
# This is not perfect but helps in some cases.
|
||||
# A better approach would be to use the actual variable names from the financial_concept.
|
||||
|
||||
# We need the actual variable names used in the formula string, not necessarily the keys in context_vars_data.
|
||||
# The financial_concept's formula string uses specific names.
|
||||
|
||||
# For now, let's assume formula_str uses the keys from context_vars_data directly.
|
||||
# This needs to align with how formulas are defined in financial_concepts.json.
|
||||
|
||||
# Let's refine this: the formula string in financial_concepts.json uses specific variable names.
|
||||
# The context_vars_data keys should match these.
|
||||
|
||||
display_formula = formula_str
|
||||
|
||||
# Replace variables with their formatted display values
|
||||
# We need to be careful not to replace parts of words.
|
||||
# Example: if 'P' is a variable, don't replace 'P' in 'Principal'.
|
||||
# This is tricky with simple string replacement.
|
||||
# A better way is to format the *values* that will be substituted into the formula string
|
||||
# when it's presented as "Substitute: Formula_with_values".
|
||||
|
||||
# Let's re-think. The formula itself (symbolic) should be displayed as is.
|
||||
# The "substitution" step is where values are shown.
|
||||
|
||||
return formula_str # For now, just return the symbolic formula. Substitution will be handled in a specific step.
|
||||
|
||||
def generate_guided_solution(financial_concept, all_variables_data, calculated_solution_data):
|
||||
"""
|
||||
Generates a step-by-step guided solution.
|
||||
|
||||
Args:
|
||||
financial_concept (dict): The financial concept definition.
|
||||
all_variables_data (dict): Dict of all variable data (knowns, intermediates, and the final unknown's calculated value).
|
||||
e.g., {"P": {'value': 1000, ...}, "i_simple_annual": {'value': 0.05, ...}, "F_simple": {'value': 1100, ...}}
|
||||
calculated_solution_data (dict): Data for the final calculated unknown variable.
|
||||
|
||||
Returns:
|
||||
list: A list of strings, where each string is a step in the solution.
|
||||
"""
|
||||
solution_steps_text = []
|
||||
step_counter = 1
|
||||
|
||||
# Get the symbolic formula for the target unknown
|
||||
target_unknown_key = financial_concept["target_unknown"]
|
||||
symbolic_formula_for_target = financial_concept["formulas"].get(target_unknown_key, "Formula not defined")
|
||||
|
||||
# If there are multiple formulas (e.g. for intermediate steps), we might need to list them all
|
||||
# or pick the primary one. For now, assume the target_unknown key in "formulas" is the main one.
|
||||
|
||||
for step_key_path in financial_concept.get("solution_step_keys", []):
|
||||
step_text_template = get_snippet(step_key_path)
|
||||
|
||||
formatted_step_text = f"{step_counter}. {step_text_template}"
|
||||
|
||||
# --- Handle specific step types for dynamic content ---
|
||||
if step_key_path == "solution_guidance.identify_knowns":
|
||||
knowns_list_text = [f"{step_counter}. {step_text_template}"]
|
||||
sub_step_counter = 0
|
||||
for var_key in financial_concept.get("required_knowns_for_target", []):
|
||||
if var_key in all_variables_data:
|
||||
var_data = all_variables_data[var_key]
|
||||
var_desc = get_snippet(f"variable_descriptions.{var_key}")
|
||||
knowns_list_text.append(f" - {var_desc} ({var_key}): {format_value_for_display(var_data)}")
|
||||
sub_step_counter +=1
|
||||
# Add other relevant knowns if not in required_knowns_for_target but present (e.g. m_compounding_periods_per_year name)
|
||||
if "m_compounding_periods_per_year" in all_variables_data and "m_compounding_periods_per_year" not in financial_concept.get("required_knowns_for_target", []):
|
||||
var_data = all_variables_data["m_compounding_periods_per_year"]
|
||||
var_desc = get_snippet(f"variable_descriptions.m_compounding_periods_per_year")
|
||||
knowns_list_text.append(f" - {var_desc} (m): {var_data['name']} (m={var_data['value']})")
|
||||
|
||||
solution_steps_text.extend(knowns_list_text)
|
||||
step_counter += 1
|
||||
continue # Move to next main step
|
||||
|
||||
elif step_key_path == "solution_guidance.state_formula":
|
||||
# Display the main formula for the target unknown
|
||||
formula_to_display = symbolic_formula_for_target
|
||||
if financial_concept["target_unknown"] == "i_rate_per_period" and "COMPOUND_INTEREST_SOLVE_FOR_RATE" in financial_concept["concept_id"]: # Special case for rate
|
||||
formula_to_display = "i = (F/P)^(1/n) - 1, then r = i * m" # More user friendly
|
||||
elif financial_concept["target_unknown"] == "n_total_compounding_periods" and "COMPOUND_INTEREST_SOLVE_FOR_TIME" in financial_concept["concept_id"]:
|
||||
formula_to_display = "n = log(F/P) / log(1+i), then t = n / m"
|
||||
# For Exact/Ordinary interest, the formula might involve n_time_years_fractional
|
||||
elif "n_time_years_fractional" in symbolic_formula_for_target:
|
||||
# The formula in financial_concepts.json already uses n_time_years_fractional
|
||||
pass # formula_to_display is already correct
|
||||
|
||||
formatted_step_text = formatted_step_text.replace("{formula_symbolic}", formula_to_display)
|
||||
|
||||
elif step_key_path == "solution_guidance.substitute_values":
|
||||
subst_formula_str = symbolic_formula_for_target
|
||||
sub_values_text = [f"{step_counter}. {step_text_template.replace('{formula_symbolic}', symbolic_formula_for_target)}"]
|
||||
solution_steps_text.extend(sub_values_text)
|
||||
step_counter +=1
|
||||
continue
|
||||
|
||||
elif step_key_path == "solution_guidance.perform_calculation":
|
||||
pass
|
||||
|
||||
elif step_key_path == "solution_guidance.final_answer_is":
|
||||
unknown_desc = get_snippet(f"variable_descriptions.{target_unknown_key}")
|
||||
formatted_step_text = formatted_step_text.replace("{unknown_variable_description}", unknown_desc)
|
||||
formatted_step_text += f" {format_value_for_display(calculated_solution_data)}"
|
||||
|
||||
elif step_key_path == "solution_guidance.convert_time_to_years":
|
||||
original_time_var = None
|
||||
original_time_unit = ""
|
||||
if "n_time_months" in all_variables_data and "n_time_years" in all_variables_data:
|
||||
original_time_var = all_variables_data["n_time_months"]
|
||||
original_time_unit = "months"
|
||||
elif "n_time_days" in all_variables_data and "n_time_years" in all_variables_data and \
|
||||
"start_date" not in all_variables_data: # Ensure not Exact/Ordinary context
|
||||
original_time_var = all_variables_data["n_time_days"]
|
||||
original_time_unit = "days"
|
||||
|
||||
if original_time_var and "n_time_years" in all_variables_data:
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
original_time_value=original_time_var['value'],
|
||||
original_time_unit=original_time_unit,
|
||||
converted_time_value_years=f"{all_variables_data['n_time_years']['value']:.4f}"
|
||||
)
|
||||
else:
|
||||
if (financial_concept["financial_topic"] == "Simple Interest" and \
|
||||
"n_time_years" in financial_concept.get("required_knowns_for_target", [])) or \
|
||||
(financial_concept["financial_topic"] in ["Exact Simple Interest", "Ordinary Simple Interest"]):
|
||||
continue
|
||||
formatted_step_text = f"{step_counter}. (Time conversion step - data missing or not applicable)"
|
||||
|
||||
elif step_key_path == "solution_guidance.calculate_interest_rate_per_period":
|
||||
if "r_nominal_annual" in all_variables_data and \
|
||||
"m_compounding_periods_per_year" in all_variables_data and \
|
||||
"i_rate_per_period" in all_variables_data:
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
nominal_rate_decimal=f"{all_variables_data['r_nominal_annual']['value']:.4f}",
|
||||
compounding_periods_per_year=all_variables_data['m_compounding_periods_per_year']['value'],
|
||||
interest_rate_per_period_decimal=f"{all_variables_data['i_rate_per_period']['value']:.6f}"
|
||||
)
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.calculate_total_periods":
|
||||
if "t_years" in all_variables_data and \
|
||||
"m_compounding_periods_per_year" in all_variables_data and \
|
||||
"n_total_compounding_periods" in all_variables_data:
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
time_in_years=all_variables_data['t_years']['value'],
|
||||
compounding_periods_per_year=all_variables_data['m_compounding_periods_per_year']['value'],
|
||||
total_periods=all_variables_data['n_total_compounding_periods']['value']
|
||||
)
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.days_in_period":
|
||||
if "start_date" in all_variables_data and \
|
||||
"end_date" in all_variables_data and \
|
||||
"n_time_days" in all_variables_data:
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
start_date=format_value_for_display(all_variables_data['start_date']),
|
||||
end_date=format_value_for_display(all_variables_data['end_date']),
|
||||
number_of_days=all_variables_data['n_time_days']['value']
|
||||
)
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.check_leap_year":
|
||||
year_to_check_data = all_variables_data.get("time_base_year_for_exact")
|
||||
if not year_to_check_data and "start_date" in all_variables_data:
|
||||
year_to_check_data = {'value': all_variables_data["start_date"]['value'].year}
|
||||
|
||||
if year_to_check_data:
|
||||
from src.date_utils import is_leap_year
|
||||
year_val = year_to_check_data['value']
|
||||
is_leap = is_leap_year(year_val)
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
year=year_val,
|
||||
is_or_is_not="is" if is_leap else "is not"
|
||||
)
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.determine_time_base_exact":
|
||||
year_for_base_data = all_variables_data.get("time_base_year_for_exact")
|
||||
days_in_year_data = all_variables_data.get("time_base_days")
|
||||
|
||||
if year_for_base_data and days_in_year_data:
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
year=year_for_base_data['value'],
|
||||
days_in_year=days_in_year_data['value']
|
||||
)
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.determine_time_base_ordinary":
|
||||
if "time_base_days" in all_variables_data and all_variables_data["time_base_days"]['value'] == 360:
|
||||
pass
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.calculate_n_time_years_fractional":
|
||||
if "n_time_days" in all_variables_data and \
|
||||
"time_base_days" in all_variables_data and \
|
||||
"n_time_years_fractional" in all_variables_data:
|
||||
formatted_step_text = formatted_step_text.format(
|
||||
n_time_days=all_variables_data['n_time_days']['value'],
|
||||
time_base_days=all_variables_data['time_base_days']['value'],
|
||||
n_time_years_fractional_value=f"{all_variables_data['n_time_years_fractional']['value']:.6f}"
|
||||
)
|
||||
else: continue
|
||||
|
||||
elif step_key_path == "solution_guidance.intermediate_step":
|
||||
if "Db_discount_amount" in all_variables_data and "BANKERS_DISCOUNT" in financial_concept["concept_id"]:
|
||||
intermediate_var_name = "Db_discount_amount"
|
||||
intermediate_var_desc = get_snippet(f"variable_descriptions.{intermediate_var_name}")
|
||||
formatted_step_text = formatted_step_text.format(step_name=intermediate_var_desc) + f" {format_value_for_display(all_variables_data[intermediate_var_name])}"
|
||||
elif "i_rate_per_period" in all_variables_data and "COMPOUND_INTEREST_SOLVE_FOR_RATE" in financial_concept["concept_id"] and target_unknown_key == "r_nominal_annual":
|
||||
intermediate_var_name = "i_rate_per_period"
|
||||
intermediate_var_desc = get_snippet(f"variable_descriptions.{intermediate_var_name}")
|
||||
formatted_step_text = formatted_step_text.format(step_name=intermediate_var_desc) + f" {all_variables_data[intermediate_var_name]['value']:.6f}"
|
||||
elif "n_total_compounding_periods" in all_variables_data and "COMPOUND_INTEREST_SOLVE_FOR_TIME" in financial_concept["concept_id"] and target_unknown_key == "t_years":
|
||||
intermediate_var_name = "n_total_compounding_periods"
|
||||
intermediate_var_desc = get_snippet(f"variable_descriptions.{intermediate_var_name}")
|
||||
formatted_step_text = formatted_step_text.format(step_name=intermediate_var_desc) + f" {all_variables_data[intermediate_var_name]['value']:.4f} periods"
|
||||
# For Exact/Ordinary interest, n_time_years_fractional is an intermediate step before the main formula
|
||||
elif "n_time_years_fractional" in all_variables_data and \
|
||||
financial_concept["financial_topic"] in ["Exact Simple Interest", "Ordinary Simple Interest"] and \
|
||||
step_key_path == "solution_guidance.intermediate_step": # This condition might be too generic
|
||||
# The specific step "calculate_n_time_years_fractional" should handle this.
|
||||
# If "intermediate_step" is used generically for this, it needs better targeting.
|
||||
# For now, assume "calculate_n_time_years_fractional" is the explicit step.
|
||||
# This generic "intermediate_step" might not be needed for these concepts if steps are explicit.
|
||||
continue # Let specific handlers deal with it.
|
||||
|
||||
else:
|
||||
formatted_step_text = f"{step_counter}. (Intermediate calculation step)"
|
||||
|
||||
solution_steps_text.append(formatted_step_text)
|
||||
step_counter += 1
|
||||
|
||||
return solution_steps_text
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Testing Solution Presenter:")
|
||||
from datetime import date
|
||||
|
||||
# Mock data for testing
|
||||
mock_snippets_data = {
|
||||
"solution_guidance": {
|
||||
"identify_knowns": "First, identify knowns:",
|
||||
"state_formula": "The formula is: {formula_symbolic}",
|
||||
"substitute_values": "Substitute values into: {formula_symbolic}",
|
||||
"perform_calculation": "Performing calculation...",
|
||||
"final_answer_is": "Therefore, the {unknown_variable_description} is:",
|
||||
"convert_time_to_years": "Convert time: {original_time_value} {original_time_unit} = {converted_time_value_years} years.",
|
||||
"calculate_interest_rate_per_period": "i = r/m = {nominal_rate_decimal}/{compounding_periods_per_year} = {interest_rate_per_period_decimal}.",
|
||||
"calculate_total_periods": "n = t*m = {time_in_years}*{compounding_periods_per_year} = {total_periods} periods.",
|
||||
"intermediate_step": "Intermediate {step_name}:",
|
||||
"days_in_period": "Days from {start_date} to {end_date}: {number_of_days} days.",
|
||||
"check_leap_year": "{year} is {is_or_is_not} a leap year.",
|
||||
"determine_time_base_exact": "Exact time base for {year}: {days_in_year} days.",
|
||||
"determine_time_base_ordinary": "Ordinary time base: 360 days.",
|
||||
"calculate_n_time_years_fractional": "t_fractional = {n_time_days}/{time_base_days} = {n_time_years_fractional_value}."
|
||||
},
|
||||
"variable_descriptions": {
|
||||
"P": "Principal", "F_simple": "Future Value (Simple)", "i_simple_annual": "Annual Simple Interest Rate",
|
||||
"n_time_years": "Time in Years", "n_time_months": "Time in Months", "n_time_days": "Time in Days",
|
||||
"F_compound": "Future Value (Compound)", "r_nominal_annual": "Nominal Annual Rate",
|
||||
"m_compounding_periods_per_year": "Compounding Frequency", "t_years": "Time in Years (Compound)",
|
||||
"i_rate_per_period": "Interest Rate per Period", "n_total_compounding_periods": "Total Compounding Periods",
|
||||
"Db_discount_amount": "Banker's Discount Amount",
|
||||
"start_date": "Start Date", "end_date": "End Date", "time_base_days": "Time Base (Days)",
|
||||
"n_time_years_fractional": "Time (Fractional Years)",
|
||||
"I_exact_simple": "Exact Simple Interest", "F_exact_simple": "Future Value (Exact Simple Interest)",
|
||||
"I_ordinary_simple": "Ordinary Simple Interest", "F_ordinary_simple": "Future Value (Ordinary Simple Interest)"
|
||||
}
|
||||
}
|
||||
# Override loader for testing
|
||||
_get_text_snippets_cached = lambda: mock_snippets_data
|
||||
|
||||
# Test Case 4: Exact Simple Interest, Solve for I
|
||||
concept4 = {
|
||||
"concept_id": "EXACT_SIMPLE_INTEREST_SOLVE_FOR_I",
|
||||
"financial_topic": "Exact Simple Interest",
|
||||
"target_unknown": "I_exact_simple",
|
||||
"formulas": {
|
||||
"n_time_years_fractional": "n_time_days / time_base_days",
|
||||
"I_exact_simple": "P * i_simple_annual * n_time_years_fractional"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "start_date", "end_date"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.days_in_period",
|
||||
"solution_guidance.check_leap_year", # Refers to time_base_year_for_exact
|
||||
"solution_guidance.determine_time_base_exact",
|
||||
"solution_guidance.calculate_n_time_years_fractional",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
}
|
||||
all_vars4 = {
|
||||
"P": {'key': 'P', 'value': 5000.0, 'currency': 'Php', 'display_precision': 2},
|
||||
"i_simple_annual": {'key': 'i_simple_annual', 'value': 0.05, 'unit_display': '% p.a.', 'display_precision': 2},
|
||||
"start_date": {'key': 'start_date', 'value': date(2023, 1, 15), 'unit': 'date'},
|
||||
"end_date": {'key': 'end_date', 'value': date(2023, 7, 15), 'unit': 'date'},
|
||||
"n_time_days": {'key': 'n_time_days', 'value': 181, 'unit': 'days', 'display_precision': 0}, # Calculated by date_utils
|
||||
"time_base_year_for_exact": {'key': 'time_base_year_for_exact', 'value': 2023}, # Set by problem_engine
|
||||
"time_base_days": {'key': 'time_base_days', 'value': 365, 'unit': 'days', 'display_precision': 0}, # Calculated by problem_engine
|
||||
"n_time_years_fractional": {'key': 'n_time_years_fractional', 'value': 181/365, 'display_precision': 6}, # Calculated
|
||||
"I_exact_simple": {'key': 'I_exact_simple', 'value': 5000 * 0.05 * (181/365), 'currency': 'Php', 'display_precision': 2}
|
||||
}
|
||||
solution4_text = generate_guided_solution(concept4, all_vars4, all_vars4["I_exact_simple"])
|
||||
print("\n--- Test Case 4: Exact Simple Interest (I) ---")
|
||||
for step in solution4_text: print(step)
|
||||
|
||||
# Test Case 5: Ordinary Simple Interest, Solve for F
|
||||
concept5 = {
|
||||
"concept_id": "ORDINARY_SIMPLE_INTEREST_SOLVE_FOR_F",
|
||||
"financial_topic": "Ordinary Simple Interest",
|
||||
"target_unknown": "F_ordinary_simple",
|
||||
"formulas": {
|
||||
"n_time_years_fractional": "n_time_days / time_base_days",
|
||||
"F_ordinary_simple": "P * (1 + i_simple_annual * n_time_years_fractional)"
|
||||
},
|
||||
"required_knowns_for_target": ["P", "i_simple_annual", "start_date", "end_date"],
|
||||
"solution_step_keys": [
|
||||
"solution_guidance.identify_knowns",
|
||||
"solution_guidance.days_in_period",
|
||||
"solution_guidance.determine_time_base_ordinary", # No check_leap_year for ordinary
|
||||
"solution_guidance.calculate_n_time_years_fractional",
|
||||
"solution_guidance.state_formula",
|
||||
"solution_guidance.substitute_values",
|
||||
"solution_guidance.perform_calculation",
|
||||
"solution_guidance.final_answer_is"
|
||||
]
|
||||
}
|
||||
all_vars5 = {
|
||||
"P": {'key': 'P', 'value': 10000.0, 'currency': 'Php', 'display_precision': 2},
|
||||
"i_simple_annual": {'key': 'i_simple_annual', 'value': 0.12, 'unit_display': '% p.a.', 'display_precision': 2},
|
||||
"start_date": {'key': 'start_date', 'value': date(2024, 3, 1), 'unit': 'date'}, # 2024 is a leap year
|
||||
"end_date": {'key': 'end_date', 'value': date(2024, 6, 29), 'unit': 'date'}, # 120 days
|
||||
"n_time_days": {'key': 'n_time_days', 'value': 120, 'unit': 'days', 'display_precision': 0},
|
||||
"time_base_days": {'key': 'time_base_days', 'value': 360, 'unit': 'days', 'display_precision': 0},
|
||||
"n_time_years_fractional": {'key': 'n_time_years_fractional', 'value': 120/360, 'display_precision': 6},
|
||||
"F_ordinary_simple": {'key': 'F_ordinary_simple', 'value': 10000 * (1 + 0.12 * (120/360)), 'currency': 'Php', 'display_precision': 2}
|
||||
}
|
||||
solution5_text = generate_guided_solution(concept5, all_vars5, all_vars5["F_ordinary_simple"])
|
||||
print("\n--- Test Case 5: Ordinary Simple Interest (F) ---")
|
||||
for step in solution5_text: print(step)
|
||||
159
src/value_sampler.py
Normal file
159
src/value_sampler.py
Normal file
@ -0,0 +1,159 @@
|
||||
import random
|
||||
import decimal
|
||||
from src.data_loader import get_value_ranges
|
||||
|
||||
# Cache loaded data to avoid repeated file I/O
|
||||
VALUE_RANGES = None
|
||||
|
||||
def _get_value_ranges_cached():
|
||||
"""Returns cached value_ranges data, loading if not already cached."""
|
||||
global VALUE_RANGES
|
||||
if VALUE_RANGES is None:
|
||||
VALUE_RANGES = get_value_ranges()
|
||||
return VALUE_RANGES
|
||||
|
||||
def get_random_float(min_val, max_val, precision=None):
|
||||
"""Generates a random float between min_val and max_val with specified precision."""
|
||||
val = random.uniform(min_val, max_val)
|
||||
if precision is not None:
|
||||
# Using Decimal for precise rounding
|
||||
return float(decimal.Decimal(str(val)).quantize(decimal.Decimal('1e-' + str(precision)), rounding=decimal.ROUND_HALF_UP))
|
||||
return val
|
||||
|
||||
def get_random_int(min_val, max_val):
|
||||
"""Generates a random integer between min_val and max_val (inclusive)."""
|
||||
return random.randint(min_val, max_val)
|
||||
|
||||
def get_value_for_variable(variable_key):
|
||||
"""
|
||||
Generates a random value for a given variable key based on value_ranges.json.
|
||||
Returns a dictionary containing the value and its unit/currency if applicable.
|
||||
Example: {'value': 15000.50, 'currency': 'Php', 'display_str': 'Php 15,000.50'}
|
||||
{'value': 0.12, 'unit_display': '% per annum', 'display_str': '12.00% per annum'}
|
||||
{'value': 5, 'unit': 'years', 'display_str': '5 years'}
|
||||
"""
|
||||
ranges = _get_value_ranges_cached()
|
||||
if not ranges or variable_key not in ranges:
|
||||
print(f"Error: No range definition found for variable key '{variable_key}' in value_ranges.json")
|
||||
return None
|
||||
|
||||
config = ranges[variable_key]
|
||||
val = None
|
||||
result = {'key': variable_key}
|
||||
|
||||
if config.get("integer", False):
|
||||
val = get_random_int(config["min"], config["max"])
|
||||
result['value'] = val
|
||||
else:
|
||||
# For floats, use internal_precision for generation if available, otherwise default to a reasonable precision
|
||||
# Display precision will be handled separately or by a formatting function
|
||||
internal_precision = config.get("internal_precision", config.get("decimals", 4)) # Default to 4 if no precision specified
|
||||
val = get_random_float(config["min"], config["max"], internal_precision)
|
||||
result['value'] = val
|
||||
|
||||
if "currency" in config:
|
||||
result["currency"] = config["currency"]
|
||||
if "unit" in config:
|
||||
result["unit"] = config["unit"]
|
||||
if "unit_display" in config: # For rates like "% per annum"
|
||||
result["unit_display"] = config["unit_display"]
|
||||
|
||||
# Store precision details for later formatting
|
||||
result["display_precision"] = config.get("display_precision", config.get("decimals")) # Decimals is often used for currency
|
||||
|
||||
return result
|
||||
|
||||
def get_random_compounding_frequency():
|
||||
"""Selects a random compounding frequency and its 'm' value."""
|
||||
ranges = _get_value_ranges_cached()
|
||||
if not ranges or "compounding_frequency_options" not in ranges:
|
||||
print("Error: 'compounding_frequency_options' not found in value_ranges.json")
|
||||
return None, None
|
||||
|
||||
options = ranges["compounding_frequency_options"]
|
||||
frequency_name = random.choice(list(options.keys()))
|
||||
m_value = options[frequency_name]
|
||||
return {"name": frequency_name, "m_value": m_value, "key": "m_compounding_periods_per_year", "value": m_value}
|
||||
|
||||
|
||||
def format_value_for_display(value_data):
|
||||
"""
|
||||
Formats a value generated by get_value_for_variable for display.
|
||||
Example input: {'key': 'principal', 'value': 15000.5, 'currency': 'Php', 'display_precision': 2}
|
||||
Example output: "Php 15,000.50"
|
||||
Example input: {'key': 'simple_interest_rate_annual', 'value': 0.12, 'unit_display': '% per annum', 'display_precision': 2}
|
||||
Example output: "12.00% per annum"
|
||||
"""
|
||||
if not isinstance(value_data, dict) or 'value' not in value_data:
|
||||
return str(value_data) # Fallback
|
||||
|
||||
val = value_data['value']
|
||||
display_precision = value_data.get('display_precision')
|
||||
|
||||
# Format number with precision
|
||||
if display_precision is not None and isinstance(val, (float, decimal.Decimal)):
|
||||
# Check if it's a rate to be displayed as percentage
|
||||
if "unit_display" in value_data and "%" in value_data["unit_display"]:
|
||||
formatted_num = f"{val * 100:.{display_precision}f}"
|
||||
else:
|
||||
formatted_num = f"{val:.{display_precision}f}"
|
||||
# Add thousands separator for currency-like values
|
||||
if "currency" in value_data and val >= 1000:
|
||||
parts = formatted_num.split('.')
|
||||
parts[0] = "{:,}".format(int(parts[0].replace(',', ''))) # Apply to integer part
|
||||
formatted_num = ".".join(parts)
|
||||
|
||||
elif isinstance(val, int):
|
||||
formatted_num = str(val)
|
||||
if "currency" in value_data and val >= 1000:
|
||||
formatted_num = "{:,}".format(val)
|
||||
else:
|
||||
formatted_num = str(val)
|
||||
|
||||
|
||||
if "currency" in value_data:
|
||||
return f"{value_data['currency']} {formatted_num}"
|
||||
elif "unit_display" in value_data: # e.g., "% per annum"
|
||||
return f"{formatted_num}{value_data['unit_display']}" # Assumes % is part of unit_display or handled by *100
|
||||
elif "unit" in value_data: # e.g., "years"
|
||||
return f"{formatted_num} {value_data['unit']}"
|
||||
else:
|
||||
return formatted_num
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Testing Value Sampler:")
|
||||
|
||||
# Test get_value_for_variable
|
||||
print("\n--- Testing get_value_for_variable ---")
|
||||
principal_data = get_value_for_variable("principal")
|
||||
if principal_data:
|
||||
print(f"Generated Principal Data: {principal_data}")
|
||||
print(f"Formatted Principal: {format_value_for_display(principal_data)}")
|
||||
|
||||
rate_data = get_value_for_variable("simple_interest_rate_annual")
|
||||
if rate_data:
|
||||
print(f"Generated Rate Data: {rate_data}")
|
||||
print(f"Formatted Rate: {format_value_for_display(rate_data)}")
|
||||
|
||||
time_data = get_value_for_variable("time_years")
|
||||
if time_data:
|
||||
print(f"Generated Time Data: {time_data}")
|
||||
print(f"Formatted Time: {format_value_for_display(time_data)}")
|
||||
|
||||
# Test get_random_compounding_frequency
|
||||
print("\n--- Testing get_random_compounding_frequency ---")
|
||||
comp_freq = get_random_compounding_frequency()
|
||||
if comp_freq:
|
||||
print(f"Generated Compounding Frequency: {comp_freq}")
|
||||
|
||||
# Test edge cases or specific formatting
|
||||
print("\n--- Testing Specific Formatting ---")
|
||||
test_currency_large = {'key': 'principal', 'value': 1234567.89, 'currency': 'Php', 'display_precision': 2}
|
||||
print(f"Large Currency: {format_value_for_display(test_currency_large)}")
|
||||
test_currency_small = {'key': 'principal', 'value': 123.45, 'currency': 'Php', 'display_precision': 2}
|
||||
print(f"Small Currency: {format_value_for_display(test_currency_small)}")
|
||||
test_rate_percent = {'key': 'simple_interest_rate_annual', 'value': 0.085, 'unit_display': '% per annum', 'display_precision': 2}
|
||||
print(f"Rate as Percent: {format_value_for_display(test_rate_percent)}")
|
||||
test_integer_unit = {'key': 'time_years', 'value': 5, 'unit': 'years'}
|
||||
print(f"Integer with Unit: {format_value_for_display(test_integer_unit)}")
|
||||
Loading…
x
Reference in New Issue
Block a user