
Renewable Energy
- 50 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with ai & agent building tasks during AI-assisted development.
About
renewable-energy is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- renewable-energy
- AI & Agent Building
- AI-coding skill
Renewable Energy by the numbers
- 50 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #7,298 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill renewable-energyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 50 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Renewable Energy
Identity
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Renewable Energy Systems
Patterns
Solar Pv Modeling
Description
Photovoltaic system modeling and simulation
Example
import numpy as np from dataclasses import dataclass from typing import Tuple, Optional from datetime import datetime
@dataclass class PVModule: """PV module specifications.""" name: str p_stc: float # Rated power at STC (W) v_mp: float # Voltage at max power (V) i_mp: float # Current at max power (A) v_oc: float # Open circuit voltage (V) i_sc: float # Short circuit current (A) temp_coeff_pmax: float # Power temp coefficient (%/°C) temp_coeff_voc: float # Voc temp coefficient (%/°C) noct: float = 45.0 # Nominal Operating Cell Temperature (°C) area: float = 1.7 # Module area (m²)
@dataclass class PVSystem: """PV system configuration.""" module: PVModule n_modules: int tilt: float # Tilt angle (degrees) azimuth: float # Azimuth angle (degrees from north) albedo: float = 0.2 # Ground reflectance losses: float = 0.14 # System losses (soiling, wiring, etc.)
class SolarResourceCalculator: """ Calculate solar irradiance on tilted surfaces. """
@staticmethod def solar_position( latitude: float, longitude: float, timestamp: datetime ) -> Tuple[float, float]: """ Calculate solar zenith and azimuth angles.
Returns (zenith, azimuth) in degrees. """ import math
Day of year
n = timestamp.timetuple().tm_yday
Declination angle
declination = 23.45 math.sin(math.radians(360/365 (284 + n)))
Hour angle
hour = timestamp.hour + timestamp.minute/60 hour_angle = 15 * (hour - 12) # 15 degrees per hour
Solar altitude
lat_rad = math.radians(latitude) dec_rad = math.radians(declination) ha_rad = math.radians(hour_angle)
sin_alt = (math.sin(lat_rad) math.sin(dec_rad) + math.cos(lat_rad) math.cos(dec_rad) * math.cos(ha_rad)) altitude = math.degrees(math.asin(sin_alt)) zenith = 90 - altitude
Solar azimuth
cos_az = ((math.sin(dec_rad) - math.sin(lat_rad) sin_alt) / (math.cos(lat_rad) math.cos(math.radians(altitude)))) azimuth = math.degrees(math.acos(max(-1, min(1, cos_az)))) if hour_angle > 0: azimuth = 360 - azimuth
return zenith, azimuth
@staticmethod def poa_irradiance( ghi: float, # Global Horizontal Irradiance (W/m²) dni: float, # Direct Normal Irradiance (W/m²) dhi: float, # Diffuse Horizontal Irradiance (W/m²) solar_zenith: float, # Solar zenith angle (degrees) solar_azimuth: float, # Solar azimuth angle (degrees) tilt: float, # Panel tilt (degrees) azimuth: float, # Panel azimuth (degrees) albedo: float = 0.2 ) -> float: """ Calculate Plane of Array irradiance.
Uses isotropic sky model for diffuse component. """ import math
Convert to radians
zenith_rad = math.radians(solar_zenith) azimuth_rad = math.radians(solar_azimuth) tilt_rad = math.radians(tilt) panel_azimuth_rad = math.radians(azimuth)
Angle of incidence
cos_aoi = (math.sin(zenith_rad) math.sin(tilt_rad) math.cos(azimuth_rad - panel_azimuth_rad) + math.cos(zenith_rad) * math.cos(tilt_rad)) cos_aoi = max(0, cos_aoi)
Beam component
poa_beam = dni * cos_aoi
Diffuse component (isotropic sky model)
poa_diffuse = dhi * (1 + math.cos(tilt_rad)) / 2
Ground reflected
poa_ground = ghi albedo (1 - math.cos(tilt_rad)) / 2
return poa_beam + poa_diffuse + poa_ground
class PVPerformanceModel: """ Model PV system power output. """
def __init__(self, system: PVSystem): self.system = system self.module = system.module
def cell_temperature( self, poa_irradiance: float, ambient_temp: float, wind_speed: float = 1.0 ) -> float: """ Calculate cell temperature using NOCT model. """ noct = self.module.noct
Simplified model
t_cell = ambient_temp + (noct - 20) * (poa_irradiance / 800)
Wind correction
t_cell -= 0.5 * (wind_speed - 1) return t_cell
def dc_power( self, poa_irradiance: float, cell_temp: float ) -> float: """ Calculate DC power output. """ if poa_irradiance <= 0: return 0
Power at STC
p_stc = self.module.p_stc
Irradiance effect (linear)
g_ratio = poa_irradiance / 1000 # STC is 1000 W/m²
Temperature effect
temp_coeff = self.module.temp_coeff_pmax / 100 # Convert to decimal temp_diff = cell_temp - 25 # STC is 25°C temp_factor = 1 + temp_coeff * temp_diff
Module power
p_module = p_stc g_ratio temp_factor
System power
p_system = p_module self.system.n_modules (1 - self.system.losses)
return max(0, p_system)
def simulate_year( self, weather_data: 'pd.DataFrame' ) -> 'pd.DataFrame': """ Simulate annual PV production.
weather_data columns: ghi, dni, dhi, temp_air, wind_speed """ import pandas as pd
results = [] for idx, row in weather_data.iterrows():
Solar position
zenith, azimuth = SolarResourceCalculator.solar_position( latitude=self.system.latitude, longitude=self.system.longitude, timestamp=idx )
POA irradiance
poa = SolarResourceCalculator.poa_irradiance( ghi=row['ghi'], dni=row['dni'], dhi=row['dhi'], solar_zenith=zenith, solar_azimuth=azimuth, tilt=self.system.tilt, azimuth=self.system.azimuth, albedo=self.system.albedo )
Cell temperature
t_cell = self.cell_temperature( poa, row['temp_air'], row.get('wind_speed', 1) )
DC power
p_dc = self.dc_power(poa, t_cell)
results.append({ 'poa_irradiance': poa, 'cell_temp': t_cell, 'power_dc': p_dc })
return pd.DataFrame(results, index=weather_data.index)
Wind Energy Modeling
Description
Wind turbine power curve and energy yield
Example
import numpy as np from dataclasses import dataclass from typing import List, Callable from scipy.interpolate import interp1d
@dataclass class WindTurbine: """Wind turbine specifications.""" name: str rated_power: float # kW rotor_diameter: float # m hub_height: float # m cut_in_speed: float # m/s cut_out_speed: float # m/s rated_speed: float # m/s power_curve: List[Tuple[float, float]] # [(wind_speed, power), ...]
class WindResourceCalculator: """ Wind resource assessment and power calculations. """
@staticmethod def extrapolate_wind_speed( measured_speed: float, measured_height: float, target_height: float, roughness_length: float = 0.03 # Open terrain ) -> float: """ Extrapolate wind speed to hub height using log law. """ import math z0 = roughness_length v_hub = measured_speed * ( math.log(target_height / z0) / math.log(measured_height / z0) ) return v_hub
@staticmethod def weibull_parameters( wind_speeds: np.ndarray ) -> Tuple[float, float]: """ Fit Weibull distribution to wind speed data.
Returns (scale parameter k, shape parameter c). """ from scipy.stats import weibull_min
Fit Weibull distribution
c, loc, scale = weibull_min.fit(wind_speeds[wind_speeds > 0], floc=0)
return scale, c # A (scale) and k (shape)
@staticmethod def capacity_factor( turbine: WindTurbine, wind_speeds: np.ndarray ) -> float: """ Calculate capacity factor from wind speed distribution. """
Create power curve interpolator
speeds = [p[0] for p in turbine.power_curve] powers = [p[1] for p in turbine.power_curve] power_func = interp1d( speeds, powers, kind='linear', bounds_error=False, fill_value=0 )
Calculate power for each wind speed
power_output = power_func(wind_speeds)
Capacity factor
cf = np.mean(power_output) / turbine.rated_power
return cf
class WindFarmModel: """ Wind farm energy production model. """
def __init__( self, turbines: List[WindTurbine], positions: np.ndarray, # (n_turbines, 2) x,y positions wind_data: 'pd.DataFrame' ): self.turbines = turbines self.positions = positions self.wind_data = wind_data
def wake_deficit( self, upstream_idx: int, downstream_idx: int, wind_direction: float ) -> float: """ Calculate wake deficit using Jensen model. """ import math
Distance and angle between turbines
dx = self.positions[downstream_idx, 0] - self.positions[upstream_idx, 0] dy = self.positions[downstream_idx, 1] - self.positions[upstream_idx, 1] distance = math.sqrt(dx2 + dy2)
Check if downstream turbine is in wake
turbine_angle = math.degrees(math.atan2(dy, dx)) angle_diff = abs(turbine_angle - wind_direction) if angle_diff > 180: angle_diff = 360 - angle_diff
d_rotor = self.turbines[upstream_idx].rotor_diameter wake_width = d_rotor + 2 0.04 distance # Wake expansion
if angle_diff > 30 or distance < d_rotor: return 0
Jensen wake model
ct = 0.8 # Thrust coefficient (simplified) deficit = (1 - math.sqrt(1 - ct)) (d_rotor / wake_width) * 2
return deficit
def simulate(self) -> 'pd.DataFrame': """Simulate wind farm production.""" import pandas as pd
results = [] for idx, row in self.wind_data.iterrows(): ws = row['wind_speed'] wd = row['wind_direction']
total_power = 0 for i, turbine in enumerate(self.turbines):
Calculate wake losses from upstream turbines
wake_loss = 0 for j in range(len(self.turbines)): if i != j: wake_loss += self.wake_deficit(j, i, wd) ** 2 wake_loss = np.sqrt(wake_loss)
Effective wind speed
ws_eff = ws * (1 - wake_loss)
Power output
power = self.turbine_power(turbine, ws_eff) total_power += power
results.append({'power': total_power})
return pd.DataFrame(results, index=self.wind_data.index)
def turbine_power(self, turbine: WindTurbine, wind_speed: float) -> float: """Calculate single turbine power output.""" if wind_speed < turbine.cut_in_speed: return 0 if wind_speed > turbine.cut_out_speed: return 0 if wind_speed >= turbine.rated_speed: return turbine.rated_power
Interpolate power curve
speeds = [p[0] for p in turbine.power_curve] powers = [p[1] for p in turbine.power_curve] power_func = interp1d(speeds, powers, kind='linear') return float(power_func(wind_speed))
Energy Storage
Description
Battery storage modeling and optimization
Example
import numpy as np from dataclasses import dataclass from typing import List, Tuple
@dataclass class BatterySystem: """Battery energy storage system specifications.""" capacity_kwh: float # Energy capacity power_kw: float # Max charge/discharge power efficiency_rt: float # Round-trip efficiency min_soc: float = 0.1 # Minimum state of charge max_soc: float = 0.9 # Maximum state of charge degradation_per_cycle: float = 0.0001 # Capacity loss per full cycle
class BatteryDispatch: """ Battery dispatch and state tracking. """
def __init__(self, battery: BatterySystem): self.battery = battery self.soc = 0.5 # Initial state of charge (fraction) self.cumulative_throughput = 0.0 self.capacity_remaining = 1.0 # Fraction of original capacity
@property def usable_capacity(self) -> float: """Usable capacity considering degradation and SOC limits.""" return (self.battery.capacity_kwh self.capacity_remaining (self.battery.max_soc - self.battery.min_soc))
def dispatch( self, power_request: float, # Positive = discharge, negative = charge duration_hours: float = 1.0 ) -> Tuple[float, float]: """ Dispatch battery for given power and duration.
Returns (actual_power, energy_transferred). """ efficiency = np.sqrt(self.battery.efficiency_rt)
Available energy
available_kwh = (self.soc - self.battery.min_soc) self.battery.capacity_kwh space_kwh = (self.battery.max_soc - self.soc) self.battery.capacity_kwh
if power_request > 0: # Discharge
Limit by power rating
power_actual = min(power_request, self.battery.power_kw)
Limit by available energy
max_energy = available_kwh efficiency energy_out = min(power_actual duration_hours, max_energy) power_actual = energy_out / duration_hours
Update SOC
energy_from_battery = energy_out / efficiency self.soc -= energy_from_battery / self.battery.capacity_kwh
else: # Charge power_actual = max(power_request, -self.battery.power_kw)
Limit by available space
max_energy = space_kwh / efficiency energy_in = min(-power_actual * duration_hours, max_energy) power_actual = -energy_in / duration_hours
Update SOC
energy_to_battery = energy_in * efficiency self.soc += energy_to_battery / self.battery.capacity_kwh
Track degradation
self.cumulative_throughput += abs(power_actual) duration_hours cycles = self.cumulative_throughput / (2 self.battery.capacity_kwh) self.capacity_remaining = 1 - cycles * self.battery.degradation_per_cycle
return power_actual, abs(power_actual) * duration_hours
def optimize_storage_size( load_profile: np.ndarray, generation_profile: np.ndarray, storage_costs: dict, electricity_prices: np.ndarray ) -> Tuple[float, float]: """ Optimize battery size for given load and generation profiles.
Returns (optimal_capacity_kwh, optimal_power_kw). """ from scipy.optimize import minimize
def objective(params): capacity, power = params battery = BatterySystem( capacity_kwh=capacity, power_kw=power, efficiency_rt=0.9 )
Simulate operation
dispatch = BatteryDispatch(battery) total_cost = capacity storage_costs['per_kwh'] + power storage_costs['per_kw']
for t in range(len(load_profile)): net_load = load_profile[t] - generation_profile[t]
if net_load > 0:
Need power, discharge battery
actual, _ = dispatch.dispatch(net_load, 1.0) grid_import = net_load - actual total_cost += grid_import * electricity_prices[t] else:
Excess power, charge battery
dispatch.dispatch(net_load, 1.0)
return total_cost
result = minimize( objective, x0=[100, 50], bounds=[(10, 1000), (5, 500)] )
return result.x[0], result.x[1]
Lcoe Analysis
Description
Levelized Cost of Energy calculations
Example
from dataclasses import dataclass from typing import List
@dataclass class ProjectFinancials: """Project financial parameters.""" capex: float # Total capital cost ($) opex_annual: float # Annual O&M cost ($/year) lifetime_years: int # Project lifetime discount_rate: float # Nominal discount rate degradation: float # Annual degradation rate
def calculate_lcoe( financials: ProjectFinancials, year1_generation_kwh: float ) -> float: """ Calculate Levelized Cost of Energy.
LCOE = (Sum of costs) / (Sum of energy) Both discounted to present value. """ total_cost_pv = financials.capex total_energy_pv = 0.0
for year in range(1, financials.lifetime_years + 1):
Discount factor
df = 1 / (1 + financials.discount_rate) ** year
Annual cost
annual_cost = financials.opex_annual total_cost_pv += annual_cost * df
Annual generation (with degradation)
generation = year1_generation_kwh (1 - financials.degradation) (year - 1) total_energy_pv += generation df
lcoe = total_cost_pv / total_energy_pv return lcoe
def compare_technologies( technologies: List[dict], location_params: dict ) -> 'pd.DataFrame': """ Compare LCOE of different renewable technologies. """ import pandas as pd
results = [] for tech in technologies:
Simulate generation
if tech['type'] == 'solar': generation = simulate_solar(location_params, tech) elif tech['type'] == 'wind': generation = simulate_wind(location_params, tech)
Calculate LCOE
financials = ProjectFinancials( capex=tech['capex'], opex_annual=tech['opex'], lifetime_years=tech['lifetime'], discount_rate=tech['discount_rate'], degradation=tech['degradation'] )
lcoe = calculate_lcoe(financials, generation) cf = generation / (tech['capacity'] * 8760)
results.append({ 'technology': tech['name'], 'capacity_factor': cf, 'lcoe_per_kwh': lcoe, 'annual_generation': generation })
return pd.DataFrame(results)
Anti-Patterns
---
Pattern
Single year for resource assessment
Problem
Year-to-year variability not captured
Solution
Use 10+ years of data, report P50/P90 exceedance
---
Pattern
Ignoring temperature effects on PV
Problem
Hot climates: PV output 10-20% lower than STC
Solution
Model cell temperature and derating
---
Pattern
Hub height wind speed from 10m data
Problem
Wind shear significantly affects energy yield
Solution
Extrapolate using log law or power law
---
Pattern
100% inverter efficiency assumed
Problem
Inverter losses vary with loading, 2-5% typical
Solution
Use inverter efficiency curve
---
Pattern
No wake losses in wind farms
Problem
Wake losses 10-20% of potential production
Solution
Model wake effects with Jensen or advanced models
Renewable Energy - Sharp Edges
Resource Assessment Based on Single Year
Id
single-year-resource
Severity
high
Summary
Year-to-year variability makes single year unreliable
Symptoms
- Production differs 20-30% from estimate
- Bank won't finance based on limited data
- P90 estimate is too optimistic
Why
Solar and wind resources vary year-to-year. Solar: 5-10% interannual variability Wind: 10-15% interannual variability
Single year might be high or low. Financial models need P50, P90, P99.
10+ years needed for reliable statistics. Shorter periods need measure-correlate-predict (MCP).
Gotcha
1 year of wind data
wind_data = load_data('2023_wind_speeds.csv') avg_speed = wind_data.mean() capacity_factor = estimate_cf(avg_speed)
Project financed on this estimate
2024 is 15% lower wind year - project underperforms
Debt service coverage ratio violated
Solution
1. Use long-term dataset
wind_data = load_data('2010-2023_wind_speeds.csv')
2. Compute P50, P90 production
annual_production = compute_annual_production(wind_data) p50 = np.percentile(annual_production, 50) p90 = np.percentile(annual_production, 10) # Exceedance probability
3. For short campaigns, use MCP
def mcp_adjustment(short_term, reference):
Correlate with long-term reference
Adjust short-term to long-term basis
correlation = np.corrcoef(short_term, reference)[0, 1] if correlation < 0.8: raise Warning("Poor correlation, MCP unreliable")
Regression and adjustment
return adjusted_long_term_estimate
4. Report uncertainty
print(f"P50: {p50:.0f} MWh/year") print(f"P90: {p90:.0f} MWh/year") print(f"P99: {p99:.0f} MWh/year")
PV Output Assumed at STC Conditions
Id
stc-output-assumed
Severity
high
Summary
Real conditions differ from Standard Test Conditions
Symptoms
- Actual output 10-25% less than nameplate
- Hot climate systems underperform
- Customers disappointed in production
Why
STC: 1000 W/m² irradiance, 25°C cell temperature, AM 1.5 Real conditions: rarely at STC.
Cell temperature in field: 40-70°C typical. Temperature coefficient: -0.35 to -0.45%/°C typical. At 55°C: 10-15% power loss.
Also: soiling, shading, wiring, inverter losses. Total DC-to-AC derate: 14-25%.
Gotcha
Nameplate calculation
system_size_kw = 10 # 10 kW STC hours_sun = 5 # "5 sun hours" per day daily_production = system_size_kw * hours_sun # 50 kWh
Reality: cell temp 55°C, soiling, losses
Actual: ~35-40 kWh/day
Customer: "I was promised 50 kWh!"
Solution
1. Model cell temperature
def cell_temperature(poa, ambient, wind=1.0): noct = 45 # Typical NOCT return ambient + (noct - 20) * (poa / 800)
2. Apply temperature derating
def derate_power(p_stc, t_cell, coeff=-0.004): return p_stc (1 + coeff (t_cell - 25))
3. Apply all loss factors
losses = { 'soiling': 0.02, 'shading': 0.03, 'mismatch': 0.02, 'wiring_dc': 0.02, 'inverter': 0.04, 'wiring_ac': 0.01, 'availability': 0.02 } total_derate = 1 - sum(losses.values()) # ~0.84
4. Use hourly simulation for accurate estimate
Don't use simple "peak sun hours" approximation
Wind Farm Without Wake Loss Modeling
Id
no-wake-losses
Severity
high
Summary
Turbine spacing ignored, production overestimated
Symptoms
- Farm produces 15-25% less than sum of turbines
- Downwind turbines underperform
- Annual production falls short of predictions
Why
Upwind turbines extract energy, create wake. Wake: reduced wind speed, increased turbulence. 10-15% loss typical for well-spaced farms. Poorly spaced: 25%+ losses possible.
Wake depends on:
- Turbine spacing (5-10 rotor diameters typical)
- Wind direction distribution
- Atmospheric stability
Gotcha
Simple sum of turbine production
n_turbines = 10 single_turbine_cf = 0.35 rated_power = 3000 # kW farm_production = n_turbines rated_power 8760 * single_turbine_cf
Reality: wake losses reduce to ~30% CF
15% less production than predicted
Solution
1. Model wake effects
from windpowerlib import wake_losses
def jensen_wake(d_rotor, distance, ct=0.8, k=0.04): """Jensen/Park wake model.""" if distance <= 0: return 0 wake_radius = d_rotor + 2 k distance deficit = (1 - np.sqrt(1 - ct)) (d_rotor / wake_radius) * 2 return deficit
2. Account for all wind directions
Wake loss varies with direction
3. Use industry tools
OpenWind, WindPRO, WAsP for detailed modeling
4. Apply wake loss factor to simple estimates
wake_loss_factor = 0.85 # 15% typical adjusted_production = farm_production * wake_loss_factor
100% Battery Efficiency Assumed
Id
battery-round-trip
Severity
medium
Summary
Round-trip losses not accounted for
Symptoms
- Less energy out than expected
- Economics don't match projections
- Storage value overestimated
Why
Lithium-ion round-trip efficiency: 85-95% Meaning: store 100 kWh, get 85-95 kWh back.
Losses in:
- Battery internal resistance
- Inverter conversion (DC-AC-DC)
- Battery management system
- Thermal management
Over time: degradation further reduces efficiency.
Gotcha
Arbitrage calculation
cheap_energy = 100 # kWh bought at $0.05 expensive_sell = 100 * 0.15 # Sell at $0.15
profit = expensive_sell - (100 * 0.05) # $10 profit?
Reality at 90% efficiency:
actual_sell = 90 * 0.15 # Only 90 kWh out actual_profit = 13.5 - 5 = 8.5 # 15% less profit
Solution
1. Use one-way efficiency for calculations
efficiency_oneway = np.sqrt(0.90) # ~0.95 each way
energy_stored = charge_energy efficiency_oneway energy_out = energy_stored efficiency_oneway
Total: 90% of input
2. Model efficiency vs power
def inverter_efficiency(power, rated_power): loading = power / rated_power
Efficiency curve (typical)
return 0.98 - 0.05 (1 - loading) * 2
3. Include auxiliary loads
BMS, cooling consume 1-3% of capacity per day
4. Model degradation over time
Year 10: capacity at 80%, efficiency slightly lower
No Consideration of Grid Curtailment
Id
grid-curtailment
Severity
medium
Summary
Assuming all produced energy is sold
Symptoms
- Revenue less than production × price
- PPA shortfall penalties
- Capacity factor drops as more renewables added
Why
Grid can't always accept all renewable output. Causes:
- Transmission constraints
- Minimum conventional generation
- Negative prices
- Frequency/voltage issues
High renewable grids: 5-15% curtailment common. Project economics must account for this.
Gotcha
Simple revenue calculation
annual_production = 100000 # MWh ppa_price = 50 # $/MWh expected_revenue = annual_production * ppa_price # $5M
Reality: 10% curtailed
actual_revenue = 90000 * 50 # $4.5M
Or negative pricing: paid to produce less
Solution
1. Model curtailment risk
def estimate_curtailment(penetration_level):
Simplified: curtailment increases with penetration
if penetration_level < 0.2: return 0.02 elif penetration_level < 0.4: return 0.05 + 0.15 (penetration_level - 0.2) else: return 0.10 + 0.25 (penetration_level - 0.4)
2. Value curtailed energy at zero or negative
revenue = sum( min(price, 0) curtailed + price (1 - curtailed) * production for price, production in zip(prices, productions) )
3. Consider storage to capture curtailed energy
4. PPA structure: take-or-pay vs merchant risk
Renewable Energy - Validations
Using STC Power Without Temperature Correction
Id
stc-power-only
Severity
warning
Type
regex
Pattern
- power\s=.rated_power\s\\sirradiance(?!.temp)
- p_stc\s\\sghi(?!.cell_temp|derate)
Message
Apply temperature derating to PV power calculations.
Fix Action
Add: power = (1 + temp_coeff (cell_temp - 25))
Applies To
- */.py
Wind Resource From Single Year
Id
single-year-wind
Severity
warning
Type
regex
Pattern
- wind.=.load.20\d{2}(?!.20\d{2}|range|multi)
Message
Use 10+ years of wind data for reliable resource assessment.
Fix Action
Load multi-year: wind_data = load_data('2010-2023_wind.csv')
Applies To
- */.py
Wind Farm Without Wake Losses
Id
no-wake-model
Severity
warning
Type
regex
Pattern
- farm_power\s=\sn_turbines\s\\sturbine_power(?!.wake)
- total.=.sum.turbine(?!.wake|deficit)
Message
Model wake losses for wind farms (10-20% typical).
Fix Action
Apply wake model: farm_power *= (1 - wake_loss_factor)
Applies To
- */.py
Battery Assumed 100% Efficient
Id
battery-100-efficiency
Severity
info
Type
regex
Pattern
- energy_out\s=\senergy_in(?!.*eff)
- storage.charge.discharge(?!.*loss|eff)
Message
Battery round-trip efficiency is 85-95%, not 100%.
Fix Action
Apply efficiency: energy_out = energy_in * efficiency_rt
Applies To
- */.py
PV System Without Loss Factors
Id
no-losses-pv
Severity
info
Type
regex
Pattern
- ac_power\s=\sdc_power(?!.*loss|derate|eff)
- production\s=.irradiance.area(?!.loss)
Message
Apply PV system losses (soiling, wiring, inverter, etc.).
Fix Action
Apply losses: ac_power = dc_power * (1 - total_losses)
Applies To
- */.py
Wind Speed Not Extrapolated to Hub Height
Id
hub-height-not-adjusted
Severity
warning
Type
regex
Pattern
- wind_speed.10m|10.meter.wind(?!.extrap|hub|height)
Message
Extrapolate wind speed from measurement height to hub height.
Fix Action
Use log law: v_hub = v_meas * log(hub_height/z0) / log(meas_height/z0)
Applies To
- */.py
Capacity Factor Assumed Not Calculated
Id
capacity-factor-assumed
Severity
info
Type
regex
Pattern
- capacity_factor\s=\s0\.\d+(?!.*calculat|simulat)
Message
Calculate capacity factor from site-specific resource data.
Fix Action
Simulate: cf = sum(hourly_power) / (rated_power * 8760)
Applies To
- */.py
Revenue Without Curtailment Consideration
Id
no-curtailment
Severity
info
Type
regex
Pattern
- revenue\s=.production\s\\sprice(?!.curtail)
Message
Consider curtailment risk in high renewable grids.
Fix Action
Adjust: revenue = production (1 - curtailment_rate) price
Applies To
- */.py
Fixed Inverter Efficiency
Id
fixed-inverter-efficiency
Severity
info
Type
regex
Pattern
- inverter_eff\s=\s0\.9\d(?!.*curve|loading)
Message
Inverter efficiency varies with loading. Use efficiency curve.
Fix Action
Model curve: eff = f(loading) where loading = power / rated_power
Applies To
- */.py