# ---------------------------------------------------------------------------
# electricity_analysis.py
#
# A year of meter readings for one housing society, taken from the register
# the secretary keeps, and the five questions the committee argues about at
# every annual meeting:
#
#   1. Which months does the society actually spend its money in?
#   2. Which block uses the most, and is that only because it is bigger?
#   3. Which flats are the heaviest users?
#   4. Does a bigger family really use more, or is that just assumed?
#   5. What does a typical flat pay, once the slab rates are applied?
#
# The tariff is the domestic slab rate; change SLABS to your own state's.
# ---------------------------------------------------------------------------

import pandas as pd
import matplotlib.pyplot as plt

# (up to this many units, rate per unit). The last slab has no upper limit,
# so it is written as a very large number rather than as a special case.
SLABS = [(100, 4.50), (200, 6.00), (400, 7.75), (10 ** 9, 9.20)]
FIXED_CHARGE = 90.00          # a fixed monthly charge, whatever the usage


def bill_for(units):
    """The rupee bill for one month's units, applying each slab in turn."""
    total = FIXED_CHARGE
    used = 0                            # units already charged in a lower slab
    for limit, rate in SLABS:
        if units <= used:               # nothing left to charge
            break
        # Only the part of the usage that falls inside this slab is charged
        # at this slab's rate. This is the point students most often get
        # wrong: the whole bill is NOT charged at the top rate.
        in_slab = min(units, limit) - used
        total = total + in_slab * rate
        used = limit
    return round(total, 2)


# --- 1. Read and clean ---------------------------------------------------
df = pd.read_csv("readings.csv")
print("Readings in the register :", len(df))

before = len(df)
df = df.drop_duplicates()
print("Duplicate readings removed:", before - len(df))

df["block"] = df["block"].str.strip().str.upper()   # "a" and "A" are one block

# A blank means the meter could not be read that month. It is left out rather
# than filled with a guess, and the count is printed so the secretary knows
# how many meters were missed.
missed = df["units"].isnull().sum()
df = df.dropna(subset=["units"])
df["units"] = df["units"].astype(int)
print("Meters not read          :", missed)
print("Readings used            :", len(df))
print("Flats                    :", df["flat_no"].nunique())
print()

# --- 2. Derive: what each reading costs ----------------------------------
# apply() sends every value of the units column through bill_for().
df["bill"] = df["units"].apply(bill_for)

print("Units for the year   :", df["units"].sum())
print("Billed for the year  : Rs", round(df["bill"].sum(), 2))
print("Average monthly bill : Rs", round(df["bill"].mean(), 2))
print()

# --- 3. Question 1: which months? ----------------------------------------
monthly = df.groupby("month")["units"].sum()

print("--- Units used, month by month ---")
print(monthly)
print()
print("Highest month:", monthly.idxmax(), "at", monthly.max(), "units")
print("Lowest  month:", monthly.idxmin(), "at", monthly.min(), "units")
# The guard is not decoration. A month in which nothing was drawn is unlikely
# in a whole society and perfectly possible in a small one, and without this
# line the program stops with ZeroDivisionError at the last hurdle.
if monthly.min() > 0:
    print("The peak month is", round(monthly.max() / monthly.min(), 2), "times the lowest.")
else:
    print("One month drew nothing at all, so there is no ratio to give.")
print()

plt.figure(figsize=(9, 4.5))
plt.plot(monthly.index, monthly.values, marker="o", color="#e07b39")
plt.title("Units used by the society, month by month")
plt.xlabel("Month")
plt.ylabel("Units (kWh)")
plt.xticks(rotation=45)
plt.grid(True, linestyle="--", alpha=0.5)
plt.tight_layout()
plt.savefig("chart1_monthly_units.png")
plt.close()

# --- 4. Question 2: which block? -----------------------------------------
# Two figures, because they answer different questions. The total says where
# the money goes; the average per flat says whether a block is really heavier
# or simply larger.
block_total = df.groupby("block")["units"].sum()
block_avg = df.groupby("block")["units"].mean().round(1)

print("--- Block by block ---")
print(pd.DataFrame({"total_units": block_total, "average_per_flat_month": block_avg}))
print()

plt.figure(figsize=(8, 4.5))
plt.bar(block_avg.index, block_avg.values, color="#4c9f70")
plt.title("Average monthly units per flat, by block")
plt.xlabel("Block")
plt.ylabel("Average units per flat per month")
plt.tight_layout()
plt.savefig("chart2_block_average.png")
plt.close()

# --- 5. Question 3: which flats are heaviest? ----------------------------
per_flat = df.groupby("flat_no")["units"].sum().sort_values(ascending=False)

print("--- Ten heaviest flats for the year ---")
print(per_flat.head(10))
print()

top10 = per_flat.head(10)[::-1]
plt.figure(figsize=(8, 5))
plt.barh(top10.index, top10.values, color="#3b7dd8")
plt.title("Ten heaviest flats, units for the year")
plt.xlabel("Units (kWh)")
plt.tight_layout()
plt.savefig("chart3_top_flats.png")
plt.close()

# --- 6. Question 4: does family size explain it? -------------------------
by_size = df.groupby("occupants")["units"].mean().round(1)
print("--- Average monthly units by number of occupants ---")
print(by_size)
print()
# corr() gives a number between -1 and 1. Near 1 means the two rise together.
print("Correlation between occupants and units:",
      round(df["occupants"].corr(df["units"]), 3))
print()

plt.figure(figsize=(8, 4.5))
plt.bar(by_size.index.astype(str), by_size.values, color="#c9772f")
plt.title("Average monthly units by size of the family")
plt.xlabel("Number of occupants")
plt.ylabel("Average units per month")
plt.tight_layout()
plt.savefig("chart4_occupants.png")
plt.close()

# --- 7. Question 5: what does a typical bill look like? ------------------
print("--- Monthly bills ---")
print(df["bill"].describe().round(2))
print()

plt.figure(figsize=(8, 4.5))
plt.hist(df["bill"].values, bins=12, color="#a05fc0", edgecolor="white")
plt.title("Spread of monthly bills across the society")
plt.xlabel("Bill for one flat for one month (Rs)")
plt.ylabel("Number of bills")
plt.tight_layout()
plt.savefig("chart5_bill_spread.png")
plt.close()

# --- 8. The sheet the secretary puts on the notice board -----------------
summary = df.groupby("month").agg(units=("units", "sum"),
                                  billed=("bill", "sum")).round(2)
summary.to_csv("month_summary.csv")

print("Charts saved : chart1_monthly_units.png .. chart5_bill_spread.png")
print("Summary saved: month_summary.csv")
