Almost every LED bulb light box sold in Pakistan prints a big number of hours. Facility managers read it as "these will last for years", and procurement reads it as "we don't need spares". Then a three-shift factory in Faisalabad finds a corridor full of dead bulbs in year three, and everyone is surprised.
The hours were never the problem. The calendar is. Most of our bulb range, from the 7W up to the 50W HLBL50N, lists a 25,000-hour life, and most models carry an 18-month warranty. This post builds a small Python model that turns those two numbers into dates for each zone of a building, and estimates how many failures to expect each year, including inside the warranty window.
It's standard library only. Use it for planning spares and maintenance budgets, not for arguing with a datasheet.
Reading the hours on an LED bulb light listing
A lifespan figure is a statement about a population of lamps, not a promise about the one in your hand. Datasheets usually mean one of two things. Either it's a lumen-maintenance figure, the hours until output drops to a set share of the original (often written L70), or it's a survival figure, the hours by which a set share of lamps have failed (written B50 or similar). Plenty of retail listings, ours included, just say "lifespan".
For this model we treat 25,000 hours as the point where half the bulbs in a batch have failed. That's an assumption. If your supplier can tell you what their figure actually means, get it in writing and change the code, because it moves the answer.
Burn hours, not switch hours
The second input is how long each zone actually runs per day. In a factory, corridors, stairwells and the guard post often run around the clock. A canteen might cover two shifts. A godown is lit while someone's working in it.
Here's the part that's specific to Pakistan. Load-shedding takes hours off the clock for any circuit that isn't on the generator or UPS. A zone switched on for 16 hours with 4 hours of outage only burns 12. The backed-up corridors never get that break, which is part of why they age first.
Heat would go here too if we had a number for it. An LED bulb light inside a closed glass dome on a top floor in Multan in June runs hotter than the same bulb in an open holder in an air-conditioned office, and heat shortens LED life. The bulb listings don't give a temperature derating curve, so the model leaves heat out rather than guessing.
The model
Failures across a batch of LED bulb lights are modelled with a Weibull distribution, which is the usual choice for lifetime data. Its shape parameter, k, controls how bunched the failures are. A high k means most bulbs die close together around rated life. A low k spreads them out, with more early failures. We don't have a datasheet value for k, so the script runs two placeholder shapes to show the range.
import math
from dataclasses import dataclass
RATED_HOURS = 25_000 # lifespan printed on the bulb listing
WARRANTY_MONTHS = 18 # check the listing for the model you buy
@dataclass
class Zone:
name: str
bulbs: int
hours_on: float # hours per day the switch is on
outage: float = 0.0 # load-shedding hours per day with no backup
backed_up: bool = False
def burn_hours_per_day(z):
"""Hours the bulbs actually run. Off-backup zones go dark in outages."""
if z.backed_up:
return z.hours_on
return max(z.hours_on - z.outage, 0)
def frac_failed(hours, k, rated=RATED_HOURS):
"""Weibull CDF, treating the rated life as the point where half have failed."""
eta = rated / math.log(2) ** (1 / k)
return 1 - math.exp(-((hours / eta) ** k))
def report(zones, k, years=6):
print(f"shape k = {k}")
print(f"{'zone':<13}{'h/day':>6}{'yrs to rated':>13}{'in warranty':>12}"
+ "".join(f"{'y' + str(y):>6}" for y in range(1, years + 1)))
for z in zones:
h = burn_hours_per_day(z)
to_rated = RATED_HOURS / (h * 365) if h else float("inf")
w = frac_failed(h * 30.4 * WARRANTY_MONTHS, k) * z.bulbs
yearly = [(frac_failed(h * 365 * y, k) - frac_failed(h * 365 * (y - 1), k)) * z.bulbs
for y in range(1, years + 1)]
print(f"{z.name:<13}{h:>6.0f}{to_rated:>13.1f}{w:>12.1f}"
+ "".join(f"{n:>6.1f}" for n in yearly))
print()
if __name__ == "__main__":
shed = 4 # hours a day, change to match your feeder
site = [
Zone("corridors", 40, 24, shed, backed_up=True),
Zone("canteen", 16, 16, shed),
Zone("godown", 30, 12, shed),
Zone("admin block", 24, 10, shed),
]
for k in (3.0, 1.5): # placeholder shapes, not datasheet values
report(site, k)
Four zones, two shapes
Here's the output for a site with 40 bulbs in backed-up corridors, 16 in the canteen, 30 in the godown and 24 in the admin block, with 4 hours of load-shedding a day:
shape k = 3.0
zone h/day yrs to rated in warranty y1 y2 y3 y4 y5 y6
corridors 24 2.9 3.8 1.2 7.3 13.6 11.9 5.0 0.9
canteen 12 5.7 0.2 0.1 0.4 1.1 1.9 2.6 2.9
godown 8 8.6 0.1 0.0 0.2 0.6 1.2 1.8 2.5
admin block 6 11.4 0.0 0.0 0.1 0.2 0.4 0.7 0.9
shape k = 1.5
zone h/day yrs to rated in warranty y1 y2 y3 y4 y5 y6
corridors 24 2.9 9.3 5.4 8.0 7.7 6.3 4.6 3.2
canteen 12 5.7 1.4 0.8 1.3 1.6 1.6 1.6 1.5
godown 8 8.6 1.5 0.8 1.4 1.8 1.9 2.0 2.0
admin block 6 11.4 0.8 0.4 0.8 1.0 1.1 1.2 1.2
Read the corridor row first. Forty bulbs on 24 hours with backup reach 25,000 hours in under three years. With k at 3.0, expect about four failures inside the 18-month warranty, then a wave of around 14 in year three and 12 in year four. With k at 1.5 the failures come earlier and flatter: about nine inside warranty, then somewhere between three and eight a year.
The admin block is the opposite case. Six burn hours a day puts rated life more than 11 years out, and either shape gives about one failure a year at most. A small box of spares covers it.
Notice what the shape does and doesn't change. Over six years, the corridor total is close to the full 40 either way. What k really moves is the warranty column, and that's the number you need when you're deciding how many spares to hold and how firmly to track warranty claims.
Group re-lamping or replacing as they fail
The corridor row is the classic case for group re-lamping. If most failures bunch into years three and four, it can be cheaper in labour to change every bulb in the zone on a planned Sunday than to send someone up a ladder every few days. That's even more true where a high stairwell needs a scaffold every time.
The godown and admin rows aren't worth it. Replace those as they go.
Bulbs pulled out in a group re-lamp aren't all dead, either. Working ones can go into a low-hours store room, as long as you keep colour temperatures matched within each room.
Where the numbers can mislead you
- Replacements restart the clock. The script only follows the original batch. After the first wave, a replaced bulb has its own life, so read the later years as "original bulbs still failing", not total call-outs.
- Supply problems kill bulbs in clusters. Surges, a loose neutral or a feeder that swings badly at night can take out drivers far faster than any lifetime curve. If one zone burns through bulbs much faster than the model says, check the supply before blaming the bulbs.
- The rated-life definition. If the 25,000 hours turns out to be a lumen-maintenance figure rather than a failure figure, bulbs will mostly still be working at that point, just dimmer.
Plugging in your own building
Swap in your zones, your feeder's typical outage hours, and the rated life and warranty of whatever LED bulb light you're quoting. The real upgrade is a failure log. Note the date and zone every time someone changes a bulb, and after a year you can fit k from your own data instead of guessing it.
If you'd like help setting the inputs for a real site, Hyundai LED's technical sales team can share the datasheet details we have for each model.

Top comments (0)