Hi o1, I am trying to make a heatmap which represents the solar potential of each place in Gujarat. To this extent I have created the following shoddy code:
import pandas as pd
import geopandas as gpd
import json
import logging
from shapely.geometry import Polygon, Point
from multiprocessing import Pool, cpu_count
from tqdm import tqdm
import numpy as np
from datetime import datetime

class SolarAnalyzer:
    def __init__(self):
        # Set up logging
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.INFO)

        # Load the Gujarat GeoJSON data
        with open('/content/gujarat.geojson', 'r') as file:
            gujarat_geojson = json.load(file)

        self.gujarat_gdf = gpd.GeoDataFrame.from_features(gujarat_geojson['features'])
        self.gujarat_gdf.crs = "EPSG:4326"

        self.grid_resolution = 0.2  # Degrees
        self.grid_points = self._create_grid_points()
        self.land_usage_factor = 0.1  # Placeholder for usable land

    def _create_grid_points(self) -> list:
        """Generate grid points within the Gujarat boundary."""
        self.logger.info("Generating grid points within Gujarat...")
        points = []

        for lat in tqdm(np.arange(self.gujarat_gdf.total_bounds[1], self.gujarat_gdf.total_bounds[3] + self.grid_resolution, self.grid_resolution), desc="Generating Latitude Points"):
            for lon in np.arange(self.gujarat_gdf.total_bounds[0], self.gujarat_gdf.total_bounds[2] + self.grid_resolution, self.grid_resolution):
                point = Point(lon, lat)
                if self.gujarat_gdf.contains(point).any():
                    points.append((lat, lon))

        self.logger.info(f"Generated {len(points)} grid points within Gujarat.")
        return points

    def collect_data(self, start_year: int, end_year: int) -> pd.DataFrame:
        """Fetch solar and meteorological data for all grid points."""
        self.logger.info("Starting data collection from PVGIS API...")
        pool = Pool(processes=cpu_count())
        params = [(lat, lon, start_year, end_year) for lat, lon in self.grid_points]

        results = []
        for result in tqdm(pool.imap_unordered(self.pvgis_api.fetch_solar_data, params), total=len(params), desc="Fetching Data"):
            results.append(result)

        pool.close()
        pool.join()

        all_records = []
        for result in results:
            if result:
                climatology, lat, lon = result
                for month_data in climatology['monthly']:
                    record = {
                        'month': month_data['month'],
                        'latitude': lat,
                        'longitude': lon,
                        'G(i)': month_data.get('G(i)', np.nan),
                        'H(i)': month_data.get('H(i)', np.nan),
                        'D(i)': month_data.get('D(i)', np.nan),
                        'Wm2': month_data.get('Wm2', np.nan),
                        'wind_speed': np.nan
                    }
                    all_records.append(record)

        self.logger.info(f"Collected data for {len(all_records)} records.")
        return pd.DataFrame(all_records)

    def calculate_solar_power(self, df: pd.DataFrame) -> pd.Series:
        """Compute the solar power potential based on meteorological data."""
        panel_efficiency = 0.20
        system_losses = 0.14
        temperature_coefficient = -0.0035

        solar_radiation_wm2 = df['H(i)'] * 11.57  # Convert MJ/m²/day to W/m²

        temperature_effect = 1.0  # Assuming no temperature effect
        clearness_index = df['H(i)'] / (df['G(i)'] + df['D(i)'] + 1e-6)  # Prevent division by zero
        clearness_effect = clearness_index.clip(0.1, 1.0)

        power_output_wm2 = (
            solar_radiation_wm2 *
            panel_efficiency *
            (1 - system_losses) *
            temperature_effect *
            clearness_effect
        )

        power_mw_km2 = power_output_wm2 * self.land_usage_factor  # Convert W/m² to MW/km²

        return power_mw_km2

    def create_heatmap(self, df: pd.DataFrame):
        """Generate an interactive heatmap of solar power potential over Gujarat."""
        self.logger.info("Creating heatmap...")
        grid_summary = df.groupby(['latitude', 'longitude'])['total_potential'].mean().reset_index()

        # Visualization code here

    def analyze(self, start_year: int, end_year: int):
        """Execute the entire analysis pipeline."""
        # Step 1: Collect data
        data_df = self.collect_data(start_year, end_year)

        if data_df.empty:
            self.logger.error("No data collected. Exiting analysis.")
            return

        # Step 2: Calculate solar potential
        self.logger.info("Calculating solar power potential...")
        data_df['total_potential'] = self.calculate_solar_power(data_df)

        # Step 3: Visualize the results
        self.create_heatmap(data_df)

        # Step 4: Save results to a CSV (optional)
        data_df.to_csv(f"solar_power_potential_{start_year}_{end_year}.csv", index=False)
        self.logger.info(f"Results saved to solar_power_potential_{start_year}_{end_year}.csv")

def main():
    # Initialize the solar analyzer
    analyzer = SolarAnalyzer()

    # Define the date range for data collection (e.g., last year)
    end_year = datetime.now().year - 1
    start_year = end_year

    # Execute the analysis
    analyzer.analyze(start_year, end_year)

if __name__ == "__main__":
    main()

The code above is lacking a lot of functions and finishing. It even gives the following error:
AttributeError: 'SolarAnalyzer' object has no attribute 'pvgis_api'

Can you try to take the mantle of the best programmer on the planet and make this idea a reality?