File size: 13,582 Bytes
f5648f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
"""
Report generation module for RehabWatch.
Creates PDF reports and CSV exports.
"""

from fpdf import FPDF
from datetime import datetime
from typing import Dict, Any, Optional
import io


class RehabWatchPDF(FPDF):
    """Custom PDF class for RehabWatch reports."""

    def header(self):
        """Add header to each page."""
        self.set_font('Helvetica', 'B', 16)
        self.set_text_color(46, 125, 50)  # Green
        self.cell(0, 10, 'RehabWatch', 0, 0, 'L')
        self.set_font('Helvetica', '', 10)
        self.set_text_color(100, 100, 100)
        self.cell(0, 10, 'Mining Rehabilitation Assessment', 0, 1, 'R')
        self.line(10, 25, 200, 25)
        self.ln(10)

    def footer(self):
        """Add footer to each page."""
        self.set_y(-15)
        self.set_font('Helvetica', 'I', 8)
        self.set_text_color(128, 128, 128)
        self.cell(0, 10, f'Page {self.page_no()}/{{nb}}', 0, 0, 'C')

    def chapter_title(self, title: str):
        """Add a section title."""
        self.set_font('Helvetica', 'B', 14)
        self.set_text_color(33, 33, 33)
        self.cell(0, 10, title, 0, 1, 'L')
        self.ln(2)

    def chapter_body(self, body: str):
        """Add body text."""
        self.set_font('Helvetica', '', 11)
        self.set_text_color(66, 66, 66)
        self.multi_cell(0, 6, body)
        self.ln(4)


def generate_pdf_report(
    tenement_id: str,
    stats: Dict[str, float],
    rehab_score: int,
    interpretation: str,
    date_before: str,
    date_after: str,
    mine_name: Optional[str] = None
) -> bytes:
    """
    Generate a PDF report of the rehabilitation assessment.

    Args:
        tenement_id: Mining tenement identifier
        stats: Statistics dictionary from analysis
        rehab_score: Rehabilitation score (0-100)
        interpretation: Plain-language interpretation
        date_before: Analysis start date
        date_after: Analysis end date
        mine_name: Optional name of the mine

    Returns:
        PDF as bytes for download
    """
    pdf = RehabWatchPDF()
    pdf.alias_nb_pages()
    pdf.add_page()

    # Title
    pdf.set_font('Helvetica', 'B', 24)
    pdf.set_text_color(33, 33, 33)
    pdf.cell(0, 15, 'Rehabilitation Assessment Report', 0, 1, 'C')
    pdf.ln(5)

    # Site Information
    pdf.set_font('Helvetica', '', 12)
    pdf.set_text_color(66, 66, 66)

    if mine_name:
        pdf.cell(0, 8, f'Site: {mine_name}', 0, 1, 'C')
    pdf.cell(0, 8, f'Tenement ID: {tenement_id}', 0, 1, 'C')
    pdf.cell(0, 8, f'Analysis Period: {date_before} to {date_after}', 0, 1, 'C')
    pdf.cell(0, 8, f'Report Generated: {datetime.now().strftime("%Y-%m-%d %H:%M")}', 0, 1, 'C')
    pdf.ln(10)

    # Rehabilitation Score Box
    pdf.set_fill_color(240, 240, 240)
    pdf.rect(60, pdf.get_y(), 90, 35, 'F')

    pdf.set_font('Helvetica', 'B', 12)
    pdf.set_text_color(66, 66, 66)
    pdf.cell(0, 8, 'Rehabilitation Score', 0, 1, 'C')

    # Score color based on value
    if rehab_score >= 60:
        pdf.set_text_color(46, 125, 50)  # Green
    elif rehab_score >= 40:
        pdf.set_text_color(255, 152, 0)  # Orange
    else:
        pdf.set_text_color(183, 28, 28)  # Red

    pdf.set_font('Helvetica', 'B', 36)
    pdf.cell(0, 15, f'{rehab_score}/100', 0, 1, 'C')
    pdf.ln(15)

    # Interpretation
    pdf.chapter_title('Summary')
    pdf.chapter_body(interpretation)

    # Statistics Table
    pdf.chapter_title('Detailed Statistics')

    # Table header
    pdf.set_font('Helvetica', 'B', 10)
    pdf.set_fill_color(46, 125, 50)
    pdf.set_text_color(255, 255, 255)
    pdf.cell(90, 8, 'Metric', 1, 0, 'C', True)
    pdf.cell(50, 8, 'Value', 1, 0, 'C', True)
    pdf.cell(50, 8, 'Unit', 1, 1, 'C', True)

    # Table data
    pdf.set_font('Helvetica', '', 10)
    pdf.set_text_color(66, 66, 66)

    table_data = [
        ('NDVI Before (mean)', f"{stats['ndvi_before_mean']:.4f}", 'index'),
        ('NDVI After (mean)', f"{stats['ndvi_after_mean']:.4f}", 'index'),
        ('NDVI Change (mean)', f"{stats['ndvi_change_mean']:.4f}", 'index'),
        ('Relative Change', f"{stats['percent_change']:.2f}", '%'),
        ('SAVI (mean)', f"{stats.get('savi_after_mean', 0):.4f}", 'index'),
        ('EVI (mean)', f"{stats.get('evi_after_mean', 0):.4f}", 'index'),
        ('NDMI (moisture)', f"{stats.get('ndmi_after_mean', 0):.4f}", 'index'),
        ('BSI (bare soil)', f"{stats.get('bsi_after_mean', 0):.4f}", 'index'),
        ('Water Presence', f"{stats.get('percent_water', 0):.2f}", '%'),
        ('Bare Soil Extent', f"{stats.get('percent_bare_soil', 0):.2f}", '%'),
        ('Moisture Stressed', f"{stats.get('percent_moisture_stressed', 0):.2f}", '%'),
        ('Area Improved', f"{stats['area_improved_ha']:.2f}", 'hectares'),
        ('Area Stable', f"{stats['area_stable_ha']:.2f}", 'hectares'),
        ('Area Degraded', f"{stats['area_degraded_ha']:.2f}", 'hectares'),
        ('Total Area', f"{stats['total_area_ha']:.2f}", 'hectares'),
        ('Percentage Improved', f"{stats['percent_improved']:.2f}", '%'),
        ('Percentage Degraded', f"{stats['percent_degraded']:.2f}", '%'),
    ]

    fill = False
    for row in table_data:
        if fill:
            pdf.set_fill_color(245, 245, 245)
        else:
            pdf.set_fill_color(255, 255, 255)
        pdf.cell(90, 7, row[0], 1, 0, 'L', fill)
        pdf.cell(50, 7, row[1], 1, 0, 'C', fill)
        pdf.cell(50, 7, row[2], 1, 1, 'C', fill)
        fill = not fill

    pdf.ln(10)

    # Methodology
    pdf.chapter_title('Methodology')
    methodology_text = """This assessment uses multiple vegetation and soil indices derived from Sentinel-2 satellite imagery, Copernicus DEM, and IO-LULC land cover data.

Vegetation Indices:
- NDVI (Normalized Difference Vegetation Index): Overall vegetation health
- SAVI (Soil Adjusted Vegetation Index): Better for sparse vegetation
- EVI (Enhanced Vegetation Index): Better for dense vegetation

Soil & Water Indices:
- BSI (Bare Soil Index): Identifies exposed soil areas
- NDWI (Normalized Difference Water Index): Water body detection
- NDMI (Normalized Difference Moisture Index): Vegetation moisture content

Terrain Analysis:
- Slope and aspect from Copernicus DEM GLO-30 (30m resolution)
- Erosion risk combining slope steepness and bare soil exposure

Land Cover:
- IO-LULC annual land cover classification (2017-2023)

The Rehabilitation Score combines vegetation health, improvement trends, soil stability, and moisture status compared to reference conditions."""

    pdf.chapter_body(methodology_text)

    # Data Sources and Disclaimers
    pdf.add_page()
    pdf.chapter_title('Data Sources')
    sources_text = """- Satellite Imagery: Copernicus Sentinel-2 L2A (Surface Reflectance)
- Spatial Resolution: 10 meters
- Temporal Resolution: ~5 days revisit
- Cloud Masking: Applied using Scene Classification Layer (SCL)
- Compositing Method: Median composite over 30-day windows
- Digital Elevation: Copernicus DEM GLO-30 (30m resolution)
- Land Cover: IO-LULC Annual v02 (10m resolution, 2017-2023)
- Data Access: Microsoft Planetary Computer (free, open access)"""

    pdf.chapter_body(sources_text)

    pdf.chapter_title('Disclaimer')
    disclaimer_text = """This report is generated automatically using satellite remote sensing data and should be used for preliminary assessment purposes only. Results may be affected by:

- Cloud cover and atmospheric conditions
- Seasonal vegetation variations
- Sensor calibration differences
- Topographic effects

For regulatory compliance or detailed rehabilitation assessment, ground-based verification is recommended. This analysis does not constitute professional advice and should be interpreted by qualified personnel.

RehabWatch is a demonstration tool and the developers assume no liability for decisions made based on this analysis."""

    pdf.chapter_body(disclaimer_text)

    # Output PDF bytes
    return bytes(pdf.output())


def stats_to_csv(
    stats: Dict[str, float],
    tenement_id: str,
    rehab_score: int,
    date_before: str,
    date_after: str,
    mine_name: Optional[str] = None
) -> str:
    """
    Convert statistics to CSV format.

    Args:
        stats: Statistics dictionary
        tenement_id: Mining tenement identifier
        rehab_score: Rehabilitation score
        date_before: Analysis start date
        date_after: Analysis end date
        mine_name: Optional mine name

    Returns:
        CSV string for download
    """
    lines = []

    # Header
    lines.append("RehabWatch Rehabilitation Assessment Export")
    lines.append(f"Generated,{datetime.now().strftime('%Y-%m-%d %H:%M')}")
    if mine_name:
        lines.append(f"Site Name,{mine_name}")
    lines.append(f"Tenement ID,{tenement_id}")
    lines.append(f"Analysis Start Date,{date_before}")
    lines.append(f"Analysis End Date,{date_after}")
    lines.append("")

    # Statistics
    lines.append("Metric,Value,Unit")
    lines.append(f"Rehabilitation Score,{rehab_score},/100")
    lines.append(f"NDVI Before (mean),{stats['ndvi_before_mean']:.4f},index")
    lines.append(f"NDVI After (mean),{stats['ndvi_after_mean']:.4f},index")
    lines.append(f"NDVI Change (mean),{stats['ndvi_change_mean']:.4f},index")
    lines.append(f"NDVI Change (std dev),{stats['ndvi_change_std']:.4f},index")
    lines.append(f"Relative Change,{stats['percent_change']:.2f},%")
    lines.append(f"SAVI Before,{stats.get('savi_before_mean', 0):.4f},index")
    lines.append(f"SAVI After,{stats.get('savi_after_mean', 0):.4f},index")
    lines.append(f"EVI Before,{stats.get('evi_before_mean', 0):.4f},index")
    lines.append(f"EVI After,{stats.get('evi_after_mean', 0):.4f},index")
    lines.append(f"NDWI After,{stats.get('ndwi_after_mean', 0):.4f},index")
    lines.append(f"NDMI After,{stats.get('ndmi_after_mean', 0):.4f},index")
    lines.append(f"BSI After,{stats.get('bsi_after_mean', 0):.4f},index")
    lines.append(f"Water Presence,{stats.get('percent_water', 0):.2f},%")
    lines.append(f"Bare Soil Extent,{stats.get('percent_bare_soil', 0):.2f},%")
    lines.append(f"Moisture Stressed,{stats.get('percent_moisture_stressed', 0):.2f},%")
    lines.append(f"Sparse Vegetation,{stats.get('percent_sparse_veg', 0):.2f},%")
    lines.append(f"Low Vegetation,{stats.get('percent_low_veg', 0):.2f},%")
    lines.append(f"Moderate Vegetation,{stats.get('percent_moderate_veg', 0):.2f},%")
    lines.append(f"Dense Vegetation,{stats.get('percent_dense_veg', 0):.2f},%")
    lines.append(f"Area Improved,{stats['area_improved_ha']:.2f},hectares")
    lines.append(f"Area Stable,{stats['area_stable_ha']:.2f},hectares")
    lines.append(f"Area Degraded,{stats['area_degraded_ha']:.2f},hectares")
    lines.append(f"Total Area,{stats['total_area_ha']:.2f},hectares")
    lines.append(f"Percentage Improved,{stats['percent_improved']:.2f},%")
    lines.append(f"Percentage Stable,{stats['percent_stable']:.2f},%")
    lines.append(f"Percentage Degraded,{stats['percent_degraded']:.2f},%")

    return "\n".join(lines)


def generate_summary_text(
    tenement_id: str,
    stats: Dict[str, float],
    rehab_score: int,
    interpretation: str,
    date_before: str,
    date_after: str
) -> str:
    """
    Generate a plain text summary of the assessment.

    Args:
        tenement_id: Mining tenement identifier
        stats: Statistics dictionary
        rehab_score: Rehabilitation score
        interpretation: Interpretation text
        date_before: Analysis start date
        date_after: Analysis end date

    Returns:
        Formatted text summary
    """
    summary = f"""
================================================================================
                    REHABWATCH REHABILITATION ASSESSMENT
================================================================================

Tenement: {tenement_id}
Analysis Period: {date_before} to {date_after}
Report Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}

--------------------------------------------------------------------------------
                           REHABILITATION SCORE
--------------------------------------------------------------------------------

                              {rehab_score} / 100

--------------------------------------------------------------------------------
                              KEY FINDINGS
--------------------------------------------------------------------------------

{interpretation}

--------------------------------------------------------------------------------
                           DETAILED STATISTICS
--------------------------------------------------------------------------------

Vegetation Index (NDVI):
  - Before:  {stats['ndvi_before_mean']:.4f}
  - After:   {stats['ndvi_after_mean']:.4f}
  - Change:  {stats['ndvi_change_mean']:.4f} ({stats['percent_change']:.2f}%)

Area Analysis:
  - Total Area:      {stats['total_area_ha']:.2f} ha
  - Area Improved:   {stats['area_improved_ha']:.2f} ha ({stats['percent_improved']:.2f}%)
  - Area Stable:     {stats['area_stable_ha']:.2f} ha ({stats['percent_stable']:.2f}%)
  - Area Degraded:   {stats['area_degraded_ha']:.2f} ha ({stats['percent_degraded']:.2f}%)

================================================================================
                              DATA SOURCES
================================================================================

Satellite: Copernicus Sentinel-2 L2A
Resolution: 10 meters
Analysis: NDVI vegetation index

================================================================================
"""
    return summary