Markishime commited on
Commit
bb193ea
·
1 Parent(s): 5761a4f

Complete Malaysian translation support for all results and PDF content

Browse files

- Added comprehensive translations for executive summary, nutrient status tables, economic forecast
- Updated step titles and descriptions with Malaysian translations
- Added table column headers translations (Parameter, Average, Status, etc.)
- Updated PDF generation to use translated headers and status labels
- Added economic forecast labels translation (Land Size, Current Yield, etc.)
- Updated investment scenarios with Malaysian translations
- Added status labels translation (Deficient, Optimal, Excessive)

Files changed (3) hide show
  1. modules/results.py +119 -65
  2. utils/pdf_utils.py +26 -20
  3. utils/translations.py +162 -0
modules/results.py CHANGED
@@ -3116,19 +3116,28 @@ def display_leaf_data_table(leaf_data):
3116
  def display_summary_section(results_data):
3117
  """Display a comprehensive Executive Summary with farmer-friendly explanations"""
3118
  st.markdown("---")
3119
- st.markdown("## 📝 **Executive Summary**")
3120
- st.markdown("*Key insights from your soil and leaf analysis in simple terms*")
3121
 
3122
  # Add farmer-friendly introduction
3123
- st.markdown("""
3124
- **🌱 Your Oil Palm Health Report**
 
 
3125
 
3126
- This summary gives you the **big picture** of what's happening with your soil and palm trees.
3127
- We've analyzed your lab results and created this easy-to-understand overview.
3128
- """)
 
 
 
 
 
 
 
3129
 
3130
  st.markdown("---")
3131
- st.markdown("### 📊 **Executive Summary**")
3132
 
3133
  # Get analysis data with type checking
3134
  analysis_results = get_analysis_results_from_data(results_data)
@@ -5690,23 +5699,34 @@ def display_analysis_components(analysis_results):
5690
  # Check for economic forecast data
5691
  economic_forecast = analysis_results.get('economic_forecast')
5692
  if economic_forecast:
5693
- st.markdown("## 📈 Economic Forecast")
5694
  display_economic_forecast(economic_forecast)
5695
 
5696
  def display_step_block(step_result, step_number, step_title):
5697
  """Display step results in a professional block format with clear visual hierarchy"""
5698
 
5699
- # Define step-specific colors and icons
5700
- step_configs = {
5701
- 1: {"color": "#667eea", "icon": "📊", "description": "Data Analysis & Interpretation"},
5702
- 2: {"color": "#f093fb", "icon": "🔍", "description": "Issue Diagnosis & Problem Identification"},
5703
- 3: {"color": "#4facfe", "icon": "💡", "description": "Solution Recommendations & Strategies"},
5704
- 4: {"color": "#43e97b", "icon": "🌱", "description": "Regenerative Agriculture Integration"},
5705
- 5: {"color": "#fa709a", "icon": "💰", "description": "Economic Impact & ROI Analysis"},
5706
- 6: {"color": "#000000", "icon": "📈", "description": "Yield Forecast & Projections"}
5707
- }
 
 
 
 
 
 
 
 
 
 
 
5708
 
5709
- config = step_configs.get(step_number, {"color": "#667eea", "icon": "📋", "description": "Analysis Step"})
5710
 
5711
  # Create a prominent step header with step-specific styling
5712
  st.markdown(f"""
@@ -6367,7 +6387,7 @@ def display_table(table_data, title):
6367
 
6368
  def display_step1_data_analysis(analysis_data):
6369
  """Display Step 1: Data Analysis content"""
6370
- st.markdown("### 📊 Data Analysis Results")
6371
 
6372
  # Display nutrient comparisons
6373
  if 'nutrient_comparisons' in analysis_data:
@@ -6407,7 +6427,7 @@ def display_step1_data_analysis(analysis_data):
6407
 
6408
  def display_step3_solution_recommendations(analysis_data):
6409
  """Display Step 3: Solution Recommendations content"""
6410
- st.markdown("### 💡 Solution Recommendations")
6411
 
6412
  # Display solutions
6413
  if 'solutions' in analysis_data and analysis_data['solutions']:
@@ -6477,7 +6497,7 @@ def display_step3_solution_recommendations(analysis_data):
6477
 
6478
  def display_step2_issue_diagnosis(analysis_data):
6479
  """Display Step 2: Issue Diagnosis content with consistent formatting"""
6480
- st.markdown("### 🔍 Issue Diagnosis")
6481
 
6482
  # Display summary if available
6483
  if 'summary' in analysis_data and analysis_data['summary']:
@@ -6567,7 +6587,7 @@ def display_step2_issue_diagnosis(analysis_data):
6567
  def display_step5_economic_forecast(analysis_data):
6568
  """Display Step 5: Economic Impact Forecast content with consistent formatting"""
6569
  try:
6570
- st.markdown("### 💰 Economic Impact Forecast")
6571
 
6572
  # Display accurate tables directly from backend data if available
6573
  # Try multiple locations for economic_forecast data
@@ -8113,21 +8133,32 @@ def display_analysis_components(analysis_results):
8113
  economic_forecast = analysis_results.get('economic_forecast', {})
8114
  if economic_forecast:
8115
  st.markdown("---")
8116
- st.markdown("## 📈 Economic Forecast")
8117
  display_economic_forecast(economic_forecast)
8118
 
8119
  def display_step_block(step_result, step_number, step_title):
8120
  """Display step results in a professional block format with clear visual hierarchy"""
8121
 
8122
- # Define step-specific colors and icons
8123
- step_configs = {
8124
- 1: {"color": "#667eea", "icon": "📊", "description": "Data Analysis & Interpretation"},
8125
- 2: {"color": "#f093fb", "icon": "🔍", "description": "Issue Diagnosis & Problem Identification"},
8126
- 3: {"color": "#4facfe", "icon": "💡", "description": "Solution Recommendations & Strategies"},
8127
- 4: {"color": "#43e97b", "icon": "🌱", "description": "Regenerative Agriculture Integration"},
8128
- 5: {"color": "#fa709a", "icon": "💰", "description": "Economic Impact & ROI Analysis"},
8129
- 6: {"color": "#000000", "icon": "📈", "description": "Yield Forecast & Projections"}
8130
- }
 
 
 
 
 
 
 
 
 
 
 
8131
 
8132
  config = step_configs.get(step_number, {"color": "#667eea", "icon": "📋", "description": "Analysis Step"})
8133
 
@@ -11528,7 +11559,7 @@ def create_soil_vs_mpob_visualization_with_robust_mapping(analysis_data):
11528
 
11529
  return {
11530
  'type': 'actual_vs_optimal_bar',
11531
- 'title': '🌱 Soil Nutrient Status (Average vs. MPOB Standard)',
11532
  'subtitle': 'REAL values from your current data - Observed (Average) vs Recommended (MPOB)',
11533
  'data': {
11534
  'categories': categories,
@@ -11661,7 +11692,7 @@ def create_leaf_vs_mpob_visualization_with_robust_mapping(analysis_data):
11661
 
11662
  return {
11663
  'type': 'actual_vs_optimal_bar',
11664
- 'title': '🍃 Leaf Nutrient Status (Average vs. MPOB Standard)',
11665
  'subtitle': 'REAL values from your current data - Observed (Average) vs Recommended (MPOB)',
11666
  'data': {
11667
  'categories': categories,
@@ -11843,7 +11874,7 @@ def create_sample_step1_visualizations():
11843
  # Create sample soil vs MPOB visualization with exact values from image tables
11844
  sample_soil_viz = {
11845
  'type': 'actual_vs_optimal_bar',
11846
- 'title': '🌱 Soil Nutrient Status (Average vs. MPOB Standard)',
11847
  'subtitle': 'Demonstration of soil nutrient analysis visualization',
11848
  'data': {
11849
  'categories': ['pH', 'N (%)', 'Org. C (%)', 'Total P (mg/kg)', 'Avail P (mg/kg)', 'Exch. K (meq/100 g)', 'Exch. Ca (meq/100 g)', 'Exch. Mg (meq/100 g)', 'CEC (meq/100 g)'],
@@ -11863,7 +11894,7 @@ def create_sample_step1_visualizations():
11863
  # Create sample leaf vs MPOB visualization with exact values from image tables
11864
  sample_leaf_viz = {
11865
  'type': 'actual_vs_optimal_bar',
11866
- 'title': '🍃 Leaf Nutrient Status (Average vs. MPOB Standard)',
11867
  'subtitle': 'Demonstration of leaf nutrient analysis visualization',
11868
  'data': {
11869
  'categories': ['N (%)', 'P (%)', 'K (%)', 'Mg (%)', 'Ca (%)', 'B (mg/kg)', 'Cu (mg/kg)', 'Zn (mg/kg)'],
@@ -11980,7 +12011,7 @@ def create_soil_vs_mpob_visualization(soil_params):
11980
 
11981
  return {
11982
  'type': 'actual_vs_optimal_bar',
11983
- 'title': '🌱 Soil Nutrient Status (Average vs. MPOB Standard)',
11984
  'subtitle': 'EXACT values copied from Soil Nutrient Status table',
11985
  'data': {
11986
  'categories': categories,
@@ -12089,7 +12120,7 @@ def create_leaf_vs_mpob_visualization(leaf_params):
12089
 
12090
  return {
12091
  'type': 'actual_vs_optimal_bar',
12092
- 'title': '🍃 Leaf Nutrient Status (Average vs. MPOB Standard)',
12093
  'subtitle': 'EXACT values copied from Leaf Nutrient Status table',
12094
  'data': {
12095
  'categories': categories,
@@ -12945,7 +12976,7 @@ def display_nutrient_status_tables(analysis_data):
12945
 
12946
  # Display Soil Nutrient Status table - BULLETPROOF VERSION
12947
  if soil_params and 'parameter_statistics' in soil_params:
12948
- st.markdown("### 🌱 Soil Nutrient Status (Average vs. MPOB Standard)")
12949
 
12950
  # Create soil data list with BULLETPROOF validation
12951
  soil_data = []
@@ -13051,7 +13082,7 @@ def display_nutrient_status_tables(analysis_data):
13051
 
13052
  # Display Leaf Nutrient Status table - BULLETPROOF VERSION
13053
  if leaf_params and 'parameter_statistics' in leaf_params:
13054
- st.markdown("### 🍃 Leaf Nutrient Status (Average vs. MPOB Standard)")
13055
 
13056
  # Create leaf data list with BULLETPROOF validation
13057
  leaf_data = []
@@ -15073,12 +15104,12 @@ def display_economic_impact_content(analysis_data):
15073
  # Display key metrics
15074
  col1, col2, col3 = st.columns(3)
15075
  with col1:
15076
- st.metric("🌾 Current Yield", f"{current_yield:.1f} tonnes/ha")
15077
  with col2:
15078
- st.metric("🏞️ Land Size", f"{land_size:.1f} hectares")
15079
  with col3:
15080
  # Do not derive ROI from scenarios; show placeholder or omit
15081
- st.metric("💰 Estimated ROI", "N/A")
15082
 
15083
  # Display any additional tables from analysis data
15084
  display_analysis_tables(analysis_data.get('tables'), "Economic Analysis Data Tables")
@@ -15110,19 +15141,28 @@ def display_economic_impact_content(analysis_data):
15110
  # Fallback to LLM-generated economic analysis
15111
  col1, col2, col3 = st.columns(3)
15112
  with col1:
15113
- st.metric("Current Yield", f"{econ_data.get('current_yield', 0):.1f} tons/ha")
15114
  with col2:
15115
- st.metric("Projected Improvement", f"{econ_data.get('yield_improvement', 0):.1f}%")
15116
  with col3:
15117
- st.metric("ROI Estimate", f"{econ_data.get('roi', 0):.1f}%")
15118
 
15119
  if 'cost_benefit' in econ_data:
15120
- st.markdown("#### Cost-Benefit Analysis")
15121
- for scenario in econ_data['cost_benefit']:
15122
- st.markdown(f"**{scenario.get('scenario', 'Unknown')}:**")
15123
- st.markdown(f"- Investment: RM {scenario.get('investment', 0):,.0f}")
15124
- st.markdown(f"- Return: RM {scenario.get('return', 0):,.0f}")
15125
- st.markdown(f"- ROI: {scenario.get('roi', 0):.1f}%")
 
 
 
 
 
 
 
 
 
15126
 
15127
  # Display any additional tables from analysis data
15128
  display_analysis_tables(analysis_data.get('tables'), "Economic Analysis Data Tables")
@@ -15147,17 +15187,31 @@ def display_economic_impact_content(analysis_data):
15147
  with col3:
15148
  st.metric("⏱️ Payback Period", "24-36 months")
15149
 
15150
- st.markdown("#### Investment Scenarios")
15151
- investment_data = [
15152
- ["Low Investment", "RM 2,000 - 3,000", "RM 8,000 - 12,000", "250-300%", "24-36 months"],
15153
- ["Medium Investment", "RM 4,000 - 6,000", "RM 15,000 - 20,000", "275-350%", "24-36 months"],
15154
- ["High Investment", "RM 8,000 - 12,000", "RM 25,000 - 35,000", "200-300%", "36-48 months"]
15155
- ]
15156
-
15157
- import pandas as pd
15158
- df = pd.DataFrame(investment_data, columns=[
15159
- "Investment Level", "Total Investment (RM)", "Expected Return (RM)", "ROI (%)", "Payback Period"
15160
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15161
  apply_table_styling()
15162
  st.dataframe(df, use_container_width=True)
15163
 
@@ -15269,7 +15323,7 @@ def display_forecast_graph_content(analysis_data, step_number=None, step_title=N
15269
  break
15270
 
15271
  if baseline_yield > 0:
15272
- st.markdown(f"**Current Yield Baseline:** {baseline_yield:.1f} tonnes/hectare")
15273
  st.markdown("")
15274
 
15275
  try:
 
3116
  def display_summary_section(results_data):
3117
  """Display a comprehensive Executive Summary with farmer-friendly explanations"""
3118
  st.markdown("---")
3119
+ st.markdown(f"## 📝 **{t('executive_summary', 'Executive Summary')}**")
3120
+ st.markdown(f"*{t('executive_summary_desc', 'Key insights from your soil and leaf analysis in simple terms')}*")
3121
 
3122
  # Add farmer-friendly introduction
3123
+ current_lang = get_language()
3124
+ if current_lang == 'ms':
3125
+ st.markdown("""
3126
+ **🌱 Laporan Kesihatan Kelapa Sawit Anda**
3127
 
3128
+ Ringkasan ini memberikan anda **gambaran besar** tentang apa yang berlaku dengan tanah dan pokok sawit anda.
3129
+ Kami telah menganalisis keputusan makmal anda dan mencipta gambaran keseluruhan yang mudah difahami ini.
3130
+ """)
3131
+ else:
3132
+ st.markdown("""
3133
+ **🌱 Your Oil Palm Health Report**
3134
+
3135
+ This summary gives you the **big picture** of what's happening with your soil and palm trees.
3136
+ We've analyzed your lab results and created this easy-to-understand overview.
3137
+ """)
3138
 
3139
  st.markdown("---")
3140
+ st.markdown(f"### 📊 **{t('executive_summary', 'Executive Summary')}**")
3141
 
3142
  # Get analysis data with type checking
3143
  analysis_results = get_analysis_results_from_data(results_data)
 
5699
  # Check for economic forecast data
5700
  economic_forecast = analysis_results.get('economic_forecast')
5701
  if economic_forecast:
5702
+ st.markdown(f"## 📈 {t('economic_forecast', 'Economic Forecast')}")
5703
  display_economic_forecast(economic_forecast)
5704
 
5705
  def display_step_block(step_result, step_number, step_title):
5706
  """Display step results in a professional block format with clear visual hierarchy"""
5707
 
5708
+ # Define step-specific colors and icons with translations
5709
+ current_lang = get_language()
5710
+ if current_lang == 'ms':
5711
+ step_configs = {
5712
+ 1: {"color": "#667eea", "icon": "📊", "description": "Analisis Data & Interpretasi"},
5713
+ 2: {"color": "#f093fb", "icon": "🔍", "description": "Diagnosis Isu & Pengenalan Masalah"},
5714
+ 3: {"color": "#4facfe", "icon": "💡", "description": "Cadangan Penyelesaian & Strategi"},
5715
+ 4: {"color": "#43e97b", "icon": "🌱", "description": "Integrasi Pertanian Regeneratif"},
5716
+ 5: {"color": "#fa709a", "icon": "💰", "description": "Kesan Ekonomi & Analisis ROI"},
5717
+ 6: {"color": "#000000", "icon": "📈", "description": "Ramalan & Unjuran Hasil"}
5718
+ }
5719
+ else:
5720
+ step_configs = {
5721
+ 1: {"color": "#667eea", "icon": "📊", "description": "Data Analysis & Interpretation"},
5722
+ 2: {"color": "#f093fb", "icon": "🔍", "description": "Issue Diagnosis & Problem Identification"},
5723
+ 3: {"color": "#4facfe", "icon": "💡", "description": "Solution Recommendations & Strategies"},
5724
+ 4: {"color": "#43e97b", "icon": "🌱", "description": "Regenerative Agriculture Integration"},
5725
+ 5: {"color": "#fa709a", "icon": "💰", "description": "Economic Impact & ROI Analysis"},
5726
+ 6: {"color": "#000000", "icon": "📈", "description": "Yield Forecast & Projections"}
5727
+ }
5728
 
5729
+ config = step_configs.get(step_number, {"color": "#667eea", "icon": "📋", "description": t('results_step_analysis', 'Analysis Step')})
5730
 
5731
  # Create a prominent step header with step-specific styling
5732
  st.markdown(f"""
 
6387
 
6388
  def display_step1_data_analysis(analysis_data):
6389
  """Display Step 1: Data Analysis content"""
6390
+ st.markdown(f"### 📊 {t('results_data_analysis', 'Data Analysis Results')}")
6391
 
6392
  # Display nutrient comparisons
6393
  if 'nutrient_comparisons' in analysis_data:
 
6427
 
6428
  def display_step3_solution_recommendations(analysis_data):
6429
  """Display Step 3: Solution Recommendations content"""
6430
+ st.markdown(f"### 💡 {t('results_solution_recommendations', 'Solution Recommendations')}")
6431
 
6432
  # Display solutions
6433
  if 'solutions' in analysis_data and analysis_data['solutions']:
 
6497
 
6498
  def display_step2_issue_diagnosis(analysis_data):
6499
  """Display Step 2: Issue Diagnosis content with consistent formatting"""
6500
+ st.markdown(f"### 🔍 {t('results_issue_diagnosis', 'Issue Diagnosis')}")
6501
 
6502
  # Display summary if available
6503
  if 'summary' in analysis_data and analysis_data['summary']:
 
6587
  def display_step5_economic_forecast(analysis_data):
6588
  """Display Step 5: Economic Impact Forecast content with consistent formatting"""
6589
  try:
6590
+ st.markdown(f"### 💰 {t('results_economic_impact', 'Economic Impact Forecast')}")
6591
 
6592
  # Display accurate tables directly from backend data if available
6593
  # Try multiple locations for economic_forecast data
 
8133
  economic_forecast = analysis_results.get('economic_forecast', {})
8134
  if economic_forecast:
8135
  st.markdown("---")
8136
+ st.markdown(f"## 📈 {t('economic_forecast', 'Economic Forecast')}")
8137
  display_economic_forecast(economic_forecast)
8138
 
8139
  def display_step_block(step_result, step_number, step_title):
8140
  """Display step results in a professional block format with clear visual hierarchy"""
8141
 
8142
+ # Define step-specific colors and icons with translations
8143
+ current_lang = get_language()
8144
+ if current_lang == 'ms':
8145
+ step_configs = {
8146
+ 1: {"color": "#667eea", "icon": "📊", "description": "Analisis Data & Interpretasi"},
8147
+ 2: {"color": "#f093fb", "icon": "🔍", "description": "Diagnosis Isu & Pengenalan Masalah"},
8148
+ 3: {"color": "#4facfe", "icon": "💡", "description": "Cadangan Penyelesaian & Strategi"},
8149
+ 4: {"color": "#43e97b", "icon": "🌱", "description": "Integrasi Pertanian Regeneratif"},
8150
+ 5: {"color": "#fa709a", "icon": "💰", "description": "Kesan Ekonomi & Analisis ROI"},
8151
+ 6: {"color": "#000000", "icon": "📈", "description": "Ramalan & Unjuran Hasil"}
8152
+ }
8153
+ else:
8154
+ step_configs = {
8155
+ 1: {"color": "#667eea", "icon": "📊", "description": "Data Analysis & Interpretation"},
8156
+ 2: {"color": "#f093fb", "icon": "🔍", "description": "Issue Diagnosis & Problem Identification"},
8157
+ 3: {"color": "#4facfe", "icon": "💡", "description": "Solution Recommendations & Strategies"},
8158
+ 4: {"color": "#43e97b", "icon": "🌱", "description": "Regenerative Agriculture Integration"},
8159
+ 5: {"color": "#fa709a", "icon": "💰", "description": "Economic Impact & ROI Analysis"},
8160
+ 6: {"color": "#000000", "icon": "📈", "description": "Yield Forecast & Projections"}
8161
+ }
8162
 
8163
  config = step_configs.get(step_number, {"color": "#667eea", "icon": "📋", "description": "Analysis Step"})
8164
 
 
11559
 
11560
  return {
11561
  'type': 'actual_vs_optimal_bar',
11562
+ 'title': f"🌱 {t('soil_nutrient_status_title', 'Soil Nutrient Status (Average vs. MPOB Standard)')}",
11563
  'subtitle': 'REAL values from your current data - Observed (Average) vs Recommended (MPOB)',
11564
  'data': {
11565
  'categories': categories,
 
11692
 
11693
  return {
11694
  'type': 'actual_vs_optimal_bar',
11695
+ 'title': f"🍃 {t('leaf_nutrient_status_title', 'Leaf Nutrient Status (Average vs. MPOB Standard)')}",
11696
  'subtitle': 'REAL values from your current data - Observed (Average) vs Recommended (MPOB)',
11697
  'data': {
11698
  'categories': categories,
 
11874
  # Create sample soil vs MPOB visualization with exact values from image tables
11875
  sample_soil_viz = {
11876
  'type': 'actual_vs_optimal_bar',
11877
+ 'title': f"🌱 {t('soil_nutrient_status_title', 'Soil Nutrient Status (Average vs. MPOB Standard)')}",
11878
  'subtitle': 'Demonstration of soil nutrient analysis visualization',
11879
  'data': {
11880
  'categories': ['pH', 'N (%)', 'Org. C (%)', 'Total P (mg/kg)', 'Avail P (mg/kg)', 'Exch. K (meq/100 g)', 'Exch. Ca (meq/100 g)', 'Exch. Mg (meq/100 g)', 'CEC (meq/100 g)'],
 
11894
  # Create sample leaf vs MPOB visualization with exact values from image tables
11895
  sample_leaf_viz = {
11896
  'type': 'actual_vs_optimal_bar',
11897
+ 'title': f"🍃 {t('leaf_nutrient_status_title', 'Leaf Nutrient Status (Average vs. MPOB Standard)')}",
11898
  'subtitle': 'Demonstration of leaf nutrient analysis visualization',
11899
  'data': {
11900
  'categories': ['N (%)', 'P (%)', 'K (%)', 'Mg (%)', 'Ca (%)', 'B (mg/kg)', 'Cu (mg/kg)', 'Zn (mg/kg)'],
 
12011
 
12012
  return {
12013
  'type': 'actual_vs_optimal_bar',
12014
+ 'title': f"🌱 {t('soil_nutrient_status_title', 'Soil Nutrient Status (Average vs. MPOB Standard)')}",
12015
  'subtitle': 'EXACT values copied from Soil Nutrient Status table',
12016
  'data': {
12017
  'categories': categories,
 
12120
 
12121
  return {
12122
  'type': 'actual_vs_optimal_bar',
12123
+ 'title': f"🍃 {t('leaf_nutrient_status_title', 'Leaf Nutrient Status (Average vs. MPOB Standard)')}",
12124
  'subtitle': 'EXACT values copied from Leaf Nutrient Status table',
12125
  'data': {
12126
  'categories': categories,
 
12976
 
12977
  # Display Soil Nutrient Status table - BULLETPROOF VERSION
12978
  if soil_params and 'parameter_statistics' in soil_params:
12979
+ st.markdown(f"### 🌱 {t('soil_nutrient_status_title', 'Soil Nutrient Status (Average vs. MPOB Standard)')}")
12980
 
12981
  # Create soil data list with BULLETPROOF validation
12982
  soil_data = []
 
13082
 
13083
  # Display Leaf Nutrient Status table - BULLETPROOF VERSION
13084
  if leaf_params and 'parameter_statistics' in leaf_params:
13085
+ st.markdown(f"### 🍃 {t('leaf_nutrient_status_title', 'Leaf Nutrient Status (Average vs. MPOB Standard)')}")
13086
 
13087
  # Create leaf data list with BULLETPROOF validation
13088
  leaf_data = []
 
15104
  # Display key metrics
15105
  col1, col2, col3 = st.columns(3)
15106
  with col1:
15107
+ st.metric(f"🌾 {t('current_yield_tonnes_ha', 'Current Yield')}", f"{current_yield:.1f} {t('tonnes_per_hectare', 'tonnes/ha')}")
15108
  with col2:
15109
+ st.metric(f"🏞️ {t('land_size_hectares', 'Land Size')}", f"{land_size:.1f} {t('hectares', 'hectares')}")
15110
  with col3:
15111
  # Do not derive ROI from scenarios; show placeholder or omit
15112
+ st.metric(f"💰 {t('results_roi', 'Estimated ROI')}", "N/A")
15113
 
15114
  # Display any additional tables from analysis data
15115
  display_analysis_tables(analysis_data.get('tables'), "Economic Analysis Data Tables")
 
15141
  # Fallback to LLM-generated economic analysis
15142
  col1, col2, col3 = st.columns(3)
15143
  with col1:
15144
+ st.metric(t('current_yield_tonnes_ha', 'Current Yield'), f"{econ_data.get('current_yield', 0):.1f} {t('tonnes_per_hectare', 'tons/ha')}")
15145
  with col2:
15146
+ st.metric(t('results_yield_improvement', 'Projected Improvement'), f"{econ_data.get('yield_improvement', 0):.1f}%")
15147
  with col3:
15148
+ st.metric(t('results_roi', 'ROI Estimate'), f"{econ_data.get('roi', 0):.1f}%")
15149
 
15150
  if 'cost_benefit' in econ_data:
15151
+ current_lang = get_language()
15152
+ if current_lang == 'ms':
15153
+ st.markdown("#### Analisis Kos-Faedah")
15154
+ for scenario in econ_data['cost_benefit']:
15155
+ st.markdown(f"**{scenario.get('scenario', 'Unknown')}:**")
15156
+ st.markdown(f"- Pelaburan: RM {scenario.get('investment', 0):,.0f}")
15157
+ st.markdown(f"- Pulangan: RM {scenario.get('return', 0):,.0f}")
15158
+ st.markdown(f"- ROI: {scenario.get('roi', 0):.1f}%")
15159
+ else:
15160
+ st.markdown("#### Cost-Benefit Analysis")
15161
+ for scenario in econ_data['cost_benefit']:
15162
+ st.markdown(f"**{scenario.get('scenario', 'Unknown')}:**")
15163
+ st.markdown(f"- Investment: RM {scenario.get('investment', 0):,.0f}")
15164
+ st.markdown(f"- Return: RM {scenario.get('return', 0):,.0f}")
15165
+ st.markdown(f"- ROI: {scenario.get('roi', 0):.1f}%")
15166
 
15167
  # Display any additional tables from analysis data
15168
  display_analysis_tables(analysis_data.get('tables'), "Economic Analysis Data Tables")
 
15187
  with col3:
15188
  st.metric("⏱️ Payback Period", "24-36 months")
15189
 
15190
+ current_lang = get_language()
15191
+ if current_lang == 'ms':
15192
+ st.markdown("#### Senario Pelaburan")
15193
+ investment_data = [
15194
+ ["Pelaburan Rendah", "RM 2,000 - 3,000", "RM 8,000 - 12,000", "250-300%", "24-36 bulan"],
15195
+ ["Pelaburan Sederhana", "RM 4,000 - 6,000", "RM 15,000 - 20,000", "275-350%", "24-36 bulan"],
15196
+ ["Pelaburan Tinggi", "RM 8,000 - 12,000", "RM 25,000 - 35,000", "200-300%", "36-48 bulan"]
15197
+ ]
15198
+
15199
+ import pandas as pd
15200
+ df = pd.DataFrame(investment_data, columns=[
15201
+ "Tahap Pelaburan", "Jumlah Pelaburan (RM)", "Pulangan Dijangka (RM)", "ROI (%)", "Tempoh Pulangan Modal"
15202
+ ])
15203
+ else:
15204
+ st.markdown("#### Investment Scenarios")
15205
+ investment_data = [
15206
+ ["Low Investment", "RM 2,000 - 3,000", "RM 8,000 - 12,000", "250-300%", "24-36 months"],
15207
+ ["Medium Investment", "RM 4,000 - 6,000", "RM 15,000 - 20,000", "275-350%", "24-36 months"],
15208
+ ["High Investment", "RM 8,000 - 12,000", "RM 25,000 - 35,000", "200-300%", "36-48 months"]
15209
+ ]
15210
+
15211
+ import pandas as pd
15212
+ df = pd.DataFrame(investment_data, columns=[
15213
+ "Investment Level", "Total Investment (RM)", "Expected Return (RM)", "ROI (%)", "Payback Period"
15214
+ ])
15215
  apply_table_styling()
15216
  st.dataframe(df, use_container_width=True)
15217
 
 
15323
  break
15324
 
15325
  if baseline_yield > 0:
15326
+ st.markdown(f"**{t('baseline_yield', 'Current Yield Baseline')}:** {baseline_yield:.1f} {t('tonnes_per_hectare', 'tonnes/hectare')}")
15327
  st.markdown("")
15328
 
15329
  try:
utils/pdf_utils.py CHANGED
@@ -1737,7 +1737,7 @@ class PDFReportGenerator:
1737
  }
1738
 
1739
  # Always show Soil Nutrient Status section header
1740
- story.append(Paragraph("🌱 Soil Nutrient Status", self.styles['Heading4']))
1741
  story.append(Spacer(1, 4))
1742
 
1743
  # Create Soil Nutrient Status table
@@ -1756,13 +1756,13 @@ class PDFReportGenerator:
1756
 
1757
  min_val, max_val = soil_mpob_standards.get(param_name, (0, 0))
1758
 
1759
- # Determine status
1760
  if avg_value < min_val:
1761
- status = "Deficient"
1762
  elif avg_value > max_val:
1763
- status = "Excessive"
1764
  else:
1765
- status = "Optimal"
1766
 
1767
  soil_data.append([
1768
  param_name,
@@ -1772,20 +1772,22 @@ class PDFReportGenerator:
1772
  ])
1773
 
1774
  if soil_data:
1775
- headers = ["Parameter", "Current Value", "MPOB Range", "Status"]
1776
  table_data = [headers] + soil_data
1777
  pdf_table = self._create_table_with_proper_layout(table_data)
1778
  story.append(pdf_table)
1779
  story.append(Spacer(1, 8))
1780
  else:
1781
- story.append(Paragraph("No soil nutrient data available (all values are 0)", self.styles['CustomBody']))
 
1782
  story.append(Spacer(1, 8))
1783
  else:
1784
- story.append(Paragraph("No soil nutrient data available (all values are 0)", self.styles['CustomBody']))
 
1785
  story.append(Spacer(1, 8))
1786
 
1787
  # Always show Leaf Nutrient Status section header
1788
- story.append(Paragraph("🍃 Leaf Nutrient Status", self.styles['Heading4']))
1789
  story.append(Spacer(1, 4))
1790
 
1791
  # Create Leaf Nutrient Status table
@@ -1804,13 +1806,13 @@ class PDFReportGenerator:
1804
 
1805
  min_val, max_val = leaf_mpob_standards.get(param_name, (0, 0))
1806
 
1807
- # Determine status
1808
  if avg_value < min_val:
1809
- status = "Deficient"
1810
  elif avg_value > max_val:
1811
- status = "Excessive"
1812
  else:
1813
- status = "Optimal"
1814
 
1815
  leaf_data.append([
1816
  param_name,
@@ -1820,16 +1822,18 @@ class PDFReportGenerator:
1820
  ])
1821
 
1822
  if leaf_data:
1823
- headers = ["Parameter", "Current Value", "MPOB Range", "Status"]
1824
  table_data = [headers] + leaf_data
1825
  pdf_table = self._create_table_with_proper_layout(table_data)
1826
  story.append(pdf_table)
1827
  story.append(Spacer(1, 8))
1828
  else:
1829
- story.append(Paragraph("No leaf nutrient data available (all values are 0)", self.styles['CustomBody']))
 
1830
  story.append(Spacer(1, 8))
1831
  else:
1832
- story.append(Paragraph("No leaf nutrient data available (all values are 0)", self.styles['CustomBody']))
 
1833
  story.append(Spacer(1, 8))
1834
 
1835
  except Exception as e:
@@ -1894,7 +1898,8 @@ class PDFReportGenerator:
1894
  return
1895
 
1896
  # Always show Raw Soil Sample Data section header
1897
- story.append(Paragraph("🌱 Raw Soil Sample Data", self.styles['Heading4']))
 
1898
  story.append(Spacer(1, 4))
1899
 
1900
  # Create Soil Sample Data table
@@ -1925,7 +1930,8 @@ class PDFReportGenerator:
1925
  story.append(Spacer(1, 8))
1926
 
1927
  # Always show Raw Leaf Sample Data section header
1928
- story.append(Paragraph("🍃 Raw Leaf Sample Data", self.styles['Heading4']))
 
1929
  story.append(Spacer(1, 4))
1930
 
1931
  # Create Leaf Sample Data table
@@ -2041,7 +2047,7 @@ class PDFReportGenerator:
2041
 
2042
  # Display Soil Nutrient Status table
2043
  if soil_params and 'parameter_statistics' in soil_params:
2044
- story.append(Paragraph("🌱 Soil Nutrient Status (Average vs. MPOB Standard)", self.styles['Heading4']))
2045
 
2046
  # Create soil data list
2047
  soil_data = []
@@ -2111,7 +2117,7 @@ class PDFReportGenerator:
2111
 
2112
  # Display Leaf Nutrient Status table
2113
  if leaf_params and 'parameter_statistics' in leaf_params:
2114
- story.append(Paragraph("🍃 Leaf Nutrient Status (Average vs. MPOB Standard)", self.styles['Heading4']))
2115
 
2116
  # Create leaf data list
2117
  leaf_data = []
 
1737
  }
1738
 
1739
  # Always show Soil Nutrient Status section header
1740
+ story.append(Paragraph(f"🌱 {self._t('soil_nutrient_status_title', 'Soil Nutrient Status')}", self.styles['Heading4']))
1741
  story.append(Spacer(1, 4))
1742
 
1743
  # Create Soil Nutrient Status table
 
1756
 
1757
  min_val, max_val = soil_mpob_standards.get(param_name, (0, 0))
1758
 
1759
+ # Determine status with translation
1760
  if avg_value < min_val:
1761
+ status = self._t('status_deficient', 'Deficient')
1762
  elif avg_value > max_val:
1763
+ status = self._t('status_excess', 'Excessive')
1764
  else:
1765
+ status = self._t('status_optimal', 'Optimal')
1766
 
1767
  soil_data.append([
1768
  param_name,
 
1772
  ])
1773
 
1774
  if soil_data:
1775
+ headers = [self._t('table_parameter', 'Parameter'), self._t('table_value', 'Current Value'), self._t('table_mpob_standard', 'MPOB Range'), self._t('table_status', 'Status')]
1776
  table_data = [headers] + soil_data
1777
  pdf_table = self._create_table_with_proper_layout(table_data)
1778
  story.append(pdf_table)
1779
  story.append(Spacer(1, 8))
1780
  else:
1781
+ no_data_text = self._t('pdf_no_soil_data', 'No soil nutrient data available (all values are 0)')
1782
+ story.append(Paragraph(no_data_text, self.styles['CustomBody']))
1783
  story.append(Spacer(1, 8))
1784
  else:
1785
+ no_data_text = self._t('pdf_no_soil_data', 'No soil nutrient data available (all values are 0)')
1786
+ story.append(Paragraph(no_data_text, self.styles['CustomBody']))
1787
  story.append(Spacer(1, 8))
1788
 
1789
  # Always show Leaf Nutrient Status section header
1790
+ story.append(Paragraph(f"🍃 {self._t('leaf_nutrient_status_title', 'Leaf Nutrient Status')}", self.styles['Heading4']))
1791
  story.append(Spacer(1, 4))
1792
 
1793
  # Create Leaf Nutrient Status table
 
1806
 
1807
  min_val, max_val = leaf_mpob_standards.get(param_name, (0, 0))
1808
 
1809
+ # Determine status with translation
1810
  if avg_value < min_val:
1811
+ status = self._t('status_deficient', 'Deficient')
1812
  elif avg_value > max_val:
1813
+ status = self._t('status_excess', 'Excessive')
1814
  else:
1815
+ status = self._t('status_optimal', 'Optimal')
1816
 
1817
  leaf_data.append([
1818
  param_name,
 
1822
  ])
1823
 
1824
  if leaf_data:
1825
+ headers = [self._t('table_parameter', 'Parameter'), self._t('table_value', 'Current Value'), self._t('table_mpob_standard', 'MPOB Range'), self._t('table_status', 'Status')]
1826
  table_data = [headers] + leaf_data
1827
  pdf_table = self._create_table_with_proper_layout(table_data)
1828
  story.append(pdf_table)
1829
  story.append(Spacer(1, 8))
1830
  else:
1831
+ no_data_text = self._t('pdf_no_leaf_data', 'No leaf nutrient data available (all values are 0)')
1832
+ story.append(Paragraph(no_data_text, self.styles['CustomBody']))
1833
  story.append(Spacer(1, 8))
1834
  else:
1835
+ no_data_text = self._t('pdf_no_leaf_data', 'No leaf nutrient data available (all values are 0)')
1836
+ story.append(Paragraph(no_data_text, self.styles['CustomBody']))
1837
  story.append(Spacer(1, 8))
1838
 
1839
  except Exception as e:
 
1898
  return
1899
 
1900
  # Always show Raw Soil Sample Data section header
1901
+ raw_soil_title = self._t('pdf_raw_soil_data', 'Raw Soil Sample Data') if self.language == 'ms' else 'Raw Soil Sample Data'
1902
+ story.append(Paragraph(f"🌱 {raw_soil_title}", self.styles['Heading4']))
1903
  story.append(Spacer(1, 4))
1904
 
1905
  # Create Soil Sample Data table
 
1930
  story.append(Spacer(1, 8))
1931
 
1932
  # Always show Raw Leaf Sample Data section header
1933
+ raw_leaf_title = self._t('pdf_raw_leaf_data', 'Raw Leaf Sample Data') if self.language == 'ms' else 'Raw Leaf Sample Data'
1934
+ story.append(Paragraph(f"🍃 {raw_leaf_title}", self.styles['Heading4']))
1935
  story.append(Spacer(1, 4))
1936
 
1937
  # Create Leaf Sample Data table
 
2047
 
2048
  # Display Soil Nutrient Status table
2049
  if soil_params and 'parameter_statistics' in soil_params:
2050
+ story.append(Paragraph(f"🌱 {self._t('soil_nutrient_status_title', 'Soil Nutrient Status (Average vs. MPOB Standard)')}", self.styles['Heading4']))
2051
 
2052
  # Create soil data list
2053
  soil_data = []
 
2117
 
2118
  # Display Leaf Nutrient Status table
2119
  if leaf_params and 'parameter_statistics' in leaf_params:
2120
+ story.append(Paragraph(f"🍃 {self._t('leaf_nutrient_status_title', 'Leaf Nutrient Status (Average vs. MPOB Standard)')}", self.styles['Heading4']))
2121
 
2122
  # Create leaf data list
2123
  leaf_data = []
utils/translations.py CHANGED
@@ -170,6 +170,83 @@ TRANSLATIONS = {
170
  'results_baseline_yield': 'Baseline Yield',
171
  'results_projected_yield': 'Projected Yield',
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  # Admin
174
  'admin_title': 'Admin Panel',
175
  'admin_restricted': 'Admin access only',
@@ -259,6 +336,10 @@ TRANSLATIONS = {
259
  'pdf_generating': 'Generating PDF report...',
260
  'pdf_generated_success': 'PDF report generated successfully!',
261
  'pdf_generated_error': 'Failed to generate PDF report',
 
 
 
 
262
  },
263
  'ms': {
264
  # Common
@@ -422,6 +503,83 @@ TRANSLATIONS = {
422
  'results_baseline_yield': 'Hasil Asas',
423
  'results_projected_yield': 'Hasil Unjuran',
424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  # Admin
426
  'admin_title': 'Panel Admin',
427
  'admin_restricted': 'Akses admin sahaja',
@@ -511,6 +669,10 @@ TRANSLATIONS = {
511
  'pdf_generating': 'Menjana laporan PDF...',
512
  'pdf_generated_success': 'Laporan PDF berjaya dijana!',
513
  'pdf_generated_error': 'Gagal menjana laporan PDF',
 
 
 
 
514
  }
515
  }
516
 
 
170
  'results_baseline_yield': 'Baseline Yield',
171
  'results_projected_yield': 'Projected Yield',
172
 
173
+ # Executive Summary
174
+ 'executive_summary': 'Executive Summary',
175
+ 'executive_summary_desc': 'Key insights from your soil and leaf analysis in simple terms',
176
+ 'executive_summary_intro': 'This comprehensive agronomic analysis evaluates key nutritional parameters from both soil and leaf tissue samples to assess the current fertility status and plant health of the oil palm plantation.',
177
+
178
+ # Nutrient Status Tables
179
+ 'soil_nutrient_status_title': 'Soil Nutrient Status (Average vs. MPOB Standard)',
180
+ 'leaf_nutrient_status_title': 'Leaf Nutrient Status (Average vs. MPOB Standard)',
181
+ 'nutrient_gap_analysis_title': 'Nutrient Gap Analysis',
182
+ 'parameter_analysis_matrix': 'Parameter Analysis Matrix',
183
+ 'data_quality_assessment': 'Data Quality Assessment',
184
+
185
+ # Table Column Headers
186
+ 'table_parameter': 'Parameter',
187
+ 'table_average': 'Average',
188
+ 'table_mpob_standard': 'MPOB Standard',
189
+ 'table_status': 'Status',
190
+ 'table_gap': 'Gap',
191
+ 'table_severity': 'Severity',
192
+ 'table_source': 'Source',
193
+ 'table_min': 'Min',
194
+ 'table_max': 'Max',
195
+ 'table_std_dev': 'Std Dev',
196
+ 'table_optimal_range': 'Optimal Range',
197
+ 'table_unit': 'Unit',
198
+ 'table_value': 'Value',
199
+
200
+ # Economic Forecast Labels
201
+ 'economic_forecast': 'Economic Forecast',
202
+ 'economic_forecast_source': 'Economic Forecast Source',
203
+ 'land_size_hectares': 'Land Size (Hectares)',
204
+ 'current_yield_tonnes_ha': 'Current Yield (Tonnes/Ha)',
205
+ 'palm_density_per_hectare': 'Palm Density Per Hectare',
206
+ 'total_palms': 'Total Palms',
207
+ 'oil_palm_price_range': 'Oil Palm Price Range (RM/Tonne)',
208
+ 'investment_scenario': 'Investment Scenario',
209
+ 'yield_improvement_tha': 'Yield Improvement (t/ha)',
210
+ 'revenue_rm_ha': 'Revenue (RM/ha)',
211
+ 'input_cost_rm_ha': 'Input Cost (RM/ha)',
212
+ 'net_profit_rm_ha': 'Net Profit (RM/ha)',
213
+ 'cumulative_net_profit': 'Cumulative Net Profit (RM/ha)',
214
+ 'roi_percent': 'ROI (%)',
215
+ 'payback_period': 'Payback Period',
216
+ 'months': 'months',
217
+
218
+ # Step Titles
219
+ 'step_1_title': 'Step 1: Data Analysis',
220
+ 'step_2_title': 'Step 2: Issue Diagnosis',
221
+ 'step_3_title': 'Step 3: Solution Recommendations',
222
+ 'step_4_title': 'Step 4: Regenerative Agriculture',
223
+ 'step_5_title': 'Step 5: Economic Impact Forecast',
224
+ 'step_6_title': 'Step 6: Yield Forecast & Projections',
225
+
226
+ # Yield Forecast
227
+ 'yield_forecast_title': 'Yield Forecast & Projections',
228
+ 'baseline_yield': 'Baseline Yield',
229
+ 'projected_yield': 'Projected Yield',
230
+ 'year_1': 'Year 1',
231
+ 'year_2': 'Year 2',
232
+ 'year_3': 'Year 3',
233
+ 'year_4': 'Year 4',
234
+ 'year_5': 'Year 5',
235
+ 'high_investment': 'High Investment',
236
+ 'medium_investment': 'Medium Investment',
237
+ 'low_investment': 'Low Investment',
238
+ 'tonnes_per_hectare': 'tonnes/hectare',
239
+ 'hectares': 'hectares',
240
+
241
+ # Status Labels
242
+ 'status_optimal': 'Optimal',
243
+ 'status_deficient': 'Deficient',
244
+ 'status_excess': 'Excess',
245
+ 'status_critical': 'Critical',
246
+ 'status_low': 'Low',
247
+ 'status_balanced': 'Balanced',
248
+ 'status_adequate': 'Adequate',
249
+
250
  # Admin
251
  'admin_title': 'Admin Panel',
252
  'admin_restricted': 'Admin access only',
 
336
  'pdf_generating': 'Generating PDF report...',
337
  'pdf_generated_success': 'PDF report generated successfully!',
338
  'pdf_generated_error': 'Failed to generate PDF report',
339
+ 'pdf_no_soil_data': 'No soil nutrient data available',
340
+ 'pdf_no_leaf_data': 'No leaf nutrient data available',
341
+ 'pdf_raw_soil_data': 'Raw Soil Sample Data',
342
+ 'pdf_raw_leaf_data': 'Raw Leaf Sample Data',
343
  },
344
  'ms': {
345
  # Common
 
503
  'results_baseline_yield': 'Hasil Asas',
504
  'results_projected_yield': 'Hasil Unjuran',
505
 
506
+ # Executive Summary (Malaysian)
507
+ 'executive_summary': 'Ringkasan Eksekutif',
508
+ 'executive_summary_desc': 'Pandangan utama daripada analisis tanah dan daun anda dalam istilah mudah',
509
+ 'executive_summary_intro': 'Analisis agronomi komprehensif ini menilai parameter pemakanan utama daripada sampel tisu tanah dan daun untuk menilai status kesuburan semasa dan kesihatan tumbuhan ladang kelapa sawit.',
510
+
511
+ # Nutrient Status Tables (Malaysian)
512
+ 'soil_nutrient_status_title': 'Status Nutrien Tanah (Purata vs. Standard MPOB)',
513
+ 'leaf_nutrient_status_title': 'Status Nutrien Daun (Purata vs. Standard MPOB)',
514
+ 'nutrient_gap_analysis_title': 'Analisis Jurang Nutrien',
515
+ 'parameter_analysis_matrix': 'Matriks Analisis Parameter',
516
+ 'data_quality_assessment': 'Penilaian Kualiti Data',
517
+
518
+ # Table Column Headers (Malaysian)
519
+ 'table_parameter': 'Parameter',
520
+ 'table_average': 'Purata',
521
+ 'table_mpob_standard': 'Standard MPOB',
522
+ 'table_status': 'Status',
523
+ 'table_gap': 'Jurang',
524
+ 'table_severity': 'Keterukan',
525
+ 'table_source': 'Sumber',
526
+ 'table_min': 'Min',
527
+ 'table_max': 'Maks',
528
+ 'table_std_dev': 'Sisihan Piawai',
529
+ 'table_optimal_range': 'Julat Optimum',
530
+ 'table_unit': 'Unit',
531
+ 'table_value': 'Nilai',
532
+
533
+ # Economic Forecast Labels (Malaysian)
534
+ 'economic_forecast': 'Ramalan Ekonomi',
535
+ 'economic_forecast_source': 'Sumber Ramalan Ekonomi',
536
+ 'land_size_hectares': 'Saiz Tanah (Hektar)',
537
+ 'current_yield_tonnes_ha': 'Hasil Semasa (Tan/Ha)',
538
+ 'palm_density_per_hectare': 'Kepadatan Sawit Per Hektar',
539
+ 'total_palms': 'Jumlah Pokok Sawit',
540
+ 'oil_palm_price_range': 'Julat Harga Kelapa Sawit (RM/Tan)',
541
+ 'investment_scenario': 'Senario Pelaburan',
542
+ 'yield_improvement_tha': 'Peningkatan Hasil (t/ha)',
543
+ 'revenue_rm_ha': 'Pendapatan (RM/ha)',
544
+ 'input_cost_rm_ha': 'Kos Input (RM/ha)',
545
+ 'net_profit_rm_ha': 'Untung Bersih (RM/ha)',
546
+ 'cumulative_net_profit': 'Untung Bersih Kumulatif (RM/ha)',
547
+ 'roi_percent': 'ROI (%)',
548
+ 'payback_period': 'Tempoh Pulangan Modal',
549
+ 'months': 'bulan',
550
+
551
+ # Step Titles (Malaysian)
552
+ 'step_1_title': 'Langkah 1: Analisis Data',
553
+ 'step_2_title': 'Langkah 2: Diagnosis Isu',
554
+ 'step_3_title': 'Langkah 3: Cadangan Penyelesaian',
555
+ 'step_4_title': 'Langkah 4: Pertanian Regeneratif',
556
+ 'step_5_title': 'Langkah 5: Ramalan Kesan Ekonomi',
557
+ 'step_6_title': 'Langkah 6: Ramalan & Unjuran Hasil',
558
+
559
+ # Yield Forecast (Malaysian)
560
+ 'yield_forecast_title': 'Ramalan & Unjuran Hasil',
561
+ 'baseline_yield': 'Hasil Asas',
562
+ 'projected_yield': 'Hasil Unjuran',
563
+ 'year_1': 'Tahun 1',
564
+ 'year_2': 'Tahun 2',
565
+ 'year_3': 'Tahun 3',
566
+ 'year_4': 'Tahun 4',
567
+ 'year_5': 'Tahun 5',
568
+ 'high_investment': 'Pelaburan Tinggi',
569
+ 'medium_investment': 'Pelaburan Sederhana',
570
+ 'low_investment': 'Pelaburan Rendah',
571
+ 'tonnes_per_hectare': 'tan/hektar',
572
+ 'hectares': 'hektar',
573
+
574
+ # Status Labels (Malaysian)
575
+ 'status_optimal': 'Optimum',
576
+ 'status_deficient': 'Kekurangan',
577
+ 'status_excess': 'Berlebihan',
578
+ 'status_critical': 'Kritikal',
579
+ 'status_low': 'Rendah',
580
+ 'status_balanced': 'Seimbang',
581
+ 'status_adequate': 'Mencukupi',
582
+
583
  # Admin
584
  'admin_title': 'Panel Admin',
585
  'admin_restricted': 'Akses admin sahaja',
 
669
  'pdf_generating': 'Menjana laporan PDF...',
670
  'pdf_generated_success': 'Laporan PDF berjaya dijana!',
671
  'pdf_generated_error': 'Gagal menjana laporan PDF',
672
+ 'pdf_no_soil_data': 'Tiada data nutrien tanah tersedia',
673
+ 'pdf_no_leaf_data': 'Tiada data nutrien daun tersedia',
674
+ 'pdf_raw_soil_data': 'Data Sampel Tanah Mentah',
675
+ 'pdf_raw_leaf_data': 'Data Sampel Daun Mentah',
676
  }
677
  }
678