Spaces:
Running
Running
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
|