Detecting a Single Failed String From Your Generation Curve
“Solar panels not producing enough” is a symptom, the way “my car is making a noise” is a symptom. It tells a busy installer almost nothing, which is why the standard reply is a site visit you pay for and a shrug about the weather. But your inverter has been logging at five-minute resolution for years, and the shape of that log is diagnostic. A dead string, a failed optimiser, an MPPT that tripped and stayed down, and a newly grown sycamore all produce visibly different curves. Learn the four shapes and you can name the fault before you pick up the phone.
Throughout, I’ll use one real-ish system as the worked example: 5.4 kWp (12 × 450 W panels), a Solis S6 hybrid with two MPPT inputs, six panels per string, single south-west roof at 35°, Leicestershire. PVGIS-SARAH3 puts it at about 4,930 kWh a year, or 913 kWh/kWp.
Get the data at a resolution that can show a shape
Daily totals will tell you that something is wrong. Only sub-hourly data tells you what. Before anything else, pull the finest granularity your kit will give you:
- SolarEdge: the monitoring portal exports 15-minute
energyDetails, and the API (QUARTER_OF_AN_HOUR) gives the same. Crucially, the Layout view gives per-module power, which is the whole ballgame for optimiser faults. - Solis / Growatt / Sofar: SolisCloud and ShinePhone expose per-MPPT DC voltage, current and power. Ugly APIs, good data.
- GivEnergy: the REST API does 5-minute
data-pointswith separate PV1/PV2 power. - Fronius: Solar.web plus the local
GetArchiveData.cgiendpoint on the inverter itself, 5-minute logging, no cloud round trip. - Enphase: Enlighten gives per-microinverter production, which makes string diagnosis moot and panel diagnosis trivial.
- Anything with a Modbus port: Solar Assistant or Home Assistant’s Modbus integration into InfluxDB, then Grafana. This is the setup worth building if you plan to do this more than once.
Whatever you use, log DC per string as well as AC total. An AC-only feed can tell you the system is down 45%, but it cannot tell you which half went missing.
The one transform that makes faults visible: divide out the weather
The reason most people give up is that no two days are alike, so a 40% drop hides inside cloud noise. Two fixes, in order of how much work they are.
String-ratio normalisation. If you have two strings of equal size and orientation, compute P_dc2 / P_dc1 for every timestamp where P_dc1 > 200 W. On a healthy system this sits between 0.95 and 1.05 all day, in bright sun and in drizzle, because cloud affects both strings identically. Weather cancels out. Any persistent departure from 1.0 is hardware.
Clear-sky normalisation. With one string, or to catch a fault affecting both, you need an external reference. Open-Meteo’s forecast API serves historical and current global_tilted_irradiance for your lat/long and panel tilt/azimuth, free and keyless. Solcast’s hobbyist tier does two rooftop sites. Divide measured AC power by modelled irradiance and you get a performance index that should be roughly flat across the middle of the day. pvlib in Python will do the Ineichen clear-sky model locally if you’d rather not call anything.
Pillar reading on building this into something that watches itself lives at Monitoring and Anomaly Detection; what follows is the interpretation layer.
Signature 1: the dead string
A dead string does something no weather event does. It preserves the shape of the curve exactly and scales the amplitude by a constant.
Sunrise and sunset times don’t move. The morning ramp has the same curvature. The peak is at the same clock minute. Everything is simply shorter, by the fraction of your panels that went dark. And it happened between one day and the next, with no transition.
Here is the raw five-minute log from the example system on a clear September morning after the fault:
time P_dc1 V_dc1 I_dc1 P_dc2 V_dc2 I_dc2 P_ac
08:30 421 186.2 2.26 0 247.9 0.00 408
09:00 705 195.0 3.62 0 248.4 0.00 689
10:00 1384 203.1 6.81 0 248.8 0.00 1351
11:30 2098 206.4 10.16 0 249.1 0.00 2052
13:00 2310 207.0 11.16 0 249.3 0.00 2261
String 2 is sitting at 249 V with zero current, and that detail is worth more than the missing kilowatts. Six × 450 W panels at Voc 41.5 V is 249 V, so the panels are alive and generating voltage; the current path is broken somewhere downstream. Blown string fuse, failed MC4 connector, corroded isolator terminal. Compare that to a reading of 0.0 V on the input, which means the break is upstream of the panels or the string is disconnected at the inverter entirely.
The month arithmetic seals it. September should deliver about 420 kWh on this array. The actual figure was 297 kWh. Daily totals show 14 days running at forecast, then a step: 19.4, 18.1, 20.2, then 9.8, 9.3, 10.1. The ratio of after to before is 0.51, and six of twelve panels is 0.50. You have not just detected a fault, you’ve counted the panels involved.
Asymmetric arrays give you a better fingerprint still. A chimney-split roof with 7 panels on MPPT1 and 5 on MPPT2 loses 41.7% if string 2 dies and 58.3% if string 1 does. Measure the shortfall to the nearest percent and you know which string to isolate before you go up the ladder.
East/west splits behave differently again, and this trips people up. Lose the east string and mornings collapse while afternoons are untouched: the curve doesn’t scale, it goes lopsided, peak time shifting an hour or more later. If your loss is time-of-day dependent and your array has two orientations, think string, not shading.
Signature 2: the failed optimiser or one bad module
Optimiser and microinverter faults are undramatic, which is why they go unnoticed for years. On a SolarEdge SE3680H with 10 × 405 W and P401 optimisers, one dead optimiser costs 405 W of 4,050 W: about a 10% clip off the top of the curve, shape intact, string voltage still pinned at exactly 380 V because that’s what the architecture regulates to. A 10% loss looks like a mediocre summer.
What gives it away is per-module data. Open the Layout view in the SolarEdge portal, set the date to a clear midday, and look for the module reporting 0 W (or one reporting 180 W while its neighbours report 340 W, which is a bypass diode conducting, not a dead optimiser). Tigo’s TS4 units and the Enphase Enlighten per-microinverter view do the same job.
Without module-level electronics, one failing panel in a series string is a different and worse story. Series current is limited by the weakest module, so a panel whose cells have cracked can drag the entire six-panel string down by 30% or more. String power will be low, string voltage will be near normal, and the string-ratio plot will show a flat offset like 0.68 that doesn’t move with time of day. Flat and fractional means degraded; flat and zero means disconnected.
Signature 3: the tripped MPPT or inverter shutdown
Curve shapes that involve a cliff edge at a time the sun didn’t do anything are electronic, not optical.
Three variants worth knowing:
Grid over-voltage trip. G99 requires disconnection above 253 V RMS. On a weak rural feeder with several neighbours exporting, voltage climbs on sunny middays, and your inverter drops out at 12:40 and comes back at 12:47, repeatedly. The curve shows square notches with vertical sides, clustered in the brightest hours, and correlated with grid voltage in your own logs. Count them: 18 dropouts on a June day at an average of 4 minutes each is 72 minutes of lost peak production, roughly 4.5 kWh, and it’s your DNO’s problem to fix.
Clipping plateau. A flat top at exactly 3.68 kW (the G98 single-phase limit) or exactly your inverter’s AC rating is not a fault. It’s the inverter doing its job. It’s also a real loss worth quantifying if you’re sizing a battery.
Over-temperature derating. A flat top that arrives only on hot days, sits slightly below the rating, and dips in the middle instead of holding level. Cross-reference inverter internal temperature against the flat section. If the plateau appears at 4.6 kW in April and 4.1 kW in July, it’s thermal.
An MPPT that has tripped and stayed down looks exactly like a dead string in the AC data. The tell is in the DC: an MPPT fault often leaves the input sitting at full open-circuit voltage with the other input carrying normal current, and the inverter’s own event log will have logged a fault code at the timestamp of the step. Pull the event log before you assume a wiring fault, because one is a firmware reset and the other is a roof job.
Signature 4: new shading
Shading is the one fault that moves.
A new obstruction (a neighbour’s extension, a leafy branch, a TV aerial installed in March) casts its shadow at a fixed solar azimuth and elevation. As the season progresses the sun’s path changes, so the clock time of the notch drifts, typically one to three minutes a day around the equinoxes, and the notch deepens and widens as the sun gets lower through autumn. Overlay fourteen clear days and you’ll see the dip walking across the afternoon.
A notch that appears at precisely 16:30 every single day, same width, same depth, regardless of month, is not shading. That’s a schedule: a battery charge window, an export limit, a myenergi eddi diverting to your immersion, or an EV charger ramping. And if you’re reading grid export rather than PV generation, most “dips” turn out to be your own consumption. Measure at the inverter’s PV meter, not the grid clamp.
On a string without optimisers, partial shading is also brutally non-linear. One shaded panel in six can cost 40% of that string until bypass diodes engage, so a thin aerial shadow across one module produces a dip far larger than its physical coverage. Don’t reason from shadow area.
The signature table
| What you see | Onset | String ratio | Likely cause | Check first |
|---|---|---|---|---|
| Same shape, amplitude × 0.5 | Overnight step | 0.00 | Dead string, blown fuse, open connector | DC voltage: Voc with 0 A vs 0 V |
| Same shape, amplitude × 0.90 | Gradual or step | 0.90 flat | Failed optimiser / one module out | Module-level layout view |
| Same shape, amplitude × 0.7 | Weeks | 0.70 flat | Cracked or degraded module in string | String voltage vs current split |
| Square notches, vertical sides | Sunny middays only | Both strings drop | Grid over-voltage trip (>253 V) | Inverter event log, grid voltage |
| Flat top at rated power | Bright days | 1.00 | Clipping (normal) | Compare plateau to AC rating |
| Notch that drifts with season | Gradual over weeks | Dips only during notch | New shading | Overlay 14 clear days |
| Mornings only collapse | Overnight step | Time-dependent | East string down on split array | Per-MPPT power by hour |
Handing it to an AI without getting nonsense back
Pasting a CSV into Claude or ChatGPT and asking “why is my solar low” produces horoscopes. Pasting it with the physical facts produces a diagnosis, because the arithmetic that identifies a string is arithmetic about panel counts. A prompt that actually works:
Attached: 5-minute data, 1 to 30 September, columns time, P_dc1, V_dc1, I_dc1, P_dc2, V_dc2, I_dc2, P_ac. Array: 12 × 450 W, 6 per string, both strings south-west at 35°, Leicester. Panel Voc 41.5 V, Vmp 34.5 V, Imp 13.0 A. Inverter 5.0 kW AC, 2 MPPTs. For each day compute total kWh, peak AC, and the median of P_dc2/P_dc1 restricted to samples where P_dc1 > 200 W. Find the date where that median steps by more than 0.2 and give me the value either side. Then tell me what string voltage and current on the affected input imply about where the break is.
Ask for the computation, not the conclusion. You want the step date and the ratio; you can read the table above yourself.
When you do ring the installer, open with the specifics: “String 2 went open-circuit on the 15th of September, the input reads 249 volts at zero amps, the AC total halved from 19 kWh to 9.8 kWh on comparable clear days, and I’d like the string fuse and roof connectors checked.” That conversation costs one visit. The other one costs three.