feat: Add the Python project

This commit is contained in:
Jose Daniel G. Percy 2025-05-09 10:22:35 +08:00
parent bb8e91b9d8
commit f1d18ec989
14 changed files with 2179 additions and 0 deletions

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.9

View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
def main():
print("Hello from problem-generator!")
if __name__ == "__main__":
main()

7
pyproject.toml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)}")