This commit is contained in:
2026-04-13 13:41:28 +02:00
parent e1da4fa041
commit cfbb5c2fa2
7 changed files with 465 additions and 403 deletions
+175 -62
View File
@@ -13,8 +13,37 @@ from django.db.models import Sum
from django.db.models.functions import Coalesce
from django.db.models import DecimalField, Value
HALFYEAR_CLIENTS = ["AG Vogel", "AG Halfm", "IKP"]
TR_RUECKF_FLUESSIG_ROW = 2 # confirmed by your March value 172.840560
TR_RUECKF_FLUESSIG_ROW = 2
TR_BESTAND_KANNEN_ROW = 5
GASBESTAND_ROW_INDEX = 9
GASBESTAND_COL_NM3 = 3
# In top_left / top_right, "Bestand in Kannen-1 (Lit. L-He)" is row_index 5
BESTAND_KANNEN_ROW_INDEX = 5
HALFYEAR_RIGHT_CLIENTS = [
"Dr. Fohrer",
"AG Buntk.",
"AG Alff",
"AG Gutfl.",
"M3 Thiele",
"M3 Buntkowsky",
"M3 Gutfleisch",
"Merck"
]
BOTTOM1_COL_VOLUME = 0
BOTTOM1_COL_BAR = 1
BOTTOM1_COL_KORR = 2
BOTTOM1_COL_NM3 = 3
BOTTOM1_COL_LHE = 4
BOTTOM2_ROW_ANLAGE = 0
BOTTOM2_COL_G39 = 0 # "Gefäss 2,5" (cell id shows column_index=0)
BOTTOM2_COL_I39 = 1 # "Gefäss 1,0" (cell id shows column_index=1)
BOTTOM2_ROW_INPUTS = {
"g39": (0, 0), # row_index=0, column_index=0 (your G39)
"i39": (0, 1), # row_index=0, column_index=1 (your I39)
}
FACTOR_NM3_TO_LHE = Decimal("0.75")
RIGHT_CLIENT_INDEX = {name: idx for idx, name in enumerate(HALFYEAR_RIGHT_CLIENTS)}
def get_top_right_value(sheet, client_name: str, row_index: int) -> Decimal:
"""
Read a numeric value from the top_right table of a MonthlySheet for
@@ -128,34 +157,7 @@ def pick_sheet_by_gasbestand(window, sheets_by_ym, prev_sheet):
return sheet
return prev_sheet
# NEW: clients for the top-right half-year table
GASBESTAND_ROW_INDEX = 9 # <-- adjust if your bottom_1 has a different row index
GASBESTAND_COL_NM3 = 3 # <-- adjust to the column index for Nm³ in bottom_1
# In top_left / top_right, "Bestand in Kannen-1 (Lit. L-He)" is row_index 5
BESTAND_KANNEN_ROW_INDEX = 5
HALFYEAR_RIGHT_CLIENTS = [
"Dr. Fohrer",
"AG Buntk.",
"AG Alff",
"AG Gutfl.",
"M3 Thiele",
"M3 Buntkowsky",
"M3 Gutfleisch",
]
BOTTOM1_COL_VOLUME = 0
BOTTOM1_COL_BAR = 1
BOTTOM1_COL_KORR = 2
BOTTOM1_COL_NM3 = 3
BOTTOM1_COL_LHE = 4
BOTTOM2_ROW_ANLAGE = 0
BOTTOM2_COL_G39 = 0 # "Gefäss 2,5" (cell id shows column_index=0)
BOTTOM2_COL_I39 = 1 # "Gefäss 1,0" (cell id shows column_index=1)
BOTTOM2_ROW_INPUTS = {
"g39": (0, 0), # row_index=0, column_index=0 (your G39)
"i39": (0, 1), # row_index=0, column_index=1 (your I39)
}
FACTOR_NM3_TO_LHE = Decimal("0.75")
RIGHT_CLIENT_INDEX = {name: idx for idx, name in enumerate(HALFYEAR_RIGHT_CLIENTS)}
def build_halfyear_window(interval_year: int, start_month: int):
"""
@@ -171,7 +173,53 @@ def build_halfyear_window(interval_year: int, start_month: int):
return window
# Import ONLY models + pure helpers here
from sheets.models import MonthlySheet, Cell, Client, SecondTableEntry, BetriebskostenSummary
def sum_right_row_without_duplicates(label: str, clients_right: list[str], values: list):
"""
For specific rows, clients are logically merged, so we must only count one copy.
"""
# rows where the PAIRS are merged:
# - Bestand in Kannen-1
# - Summe Bestand
# - Best. in Kannen Vormonat
merged_pair_labels = {
"Bestand in Kannen-1 (Lit. L-He)",
"Summe Bestand (Lit. L-He)",
"Best. in Kannen Vormonat (Lit. L-He)",
"Verbraucherverluste (Liter L-He)",
# ✅ Sammel is also duplicated for the merged PAIRS (Fohrer+Buntk, Alff+Gutfl)
"Sammelrückführungen (Lit. L-He)",
"Sammelrückführung (Lit. L-He)", # safety (singular)
}
merged_m3_labels = {
# ✅ Sammel is duplicated for the merged M3 triple
"Sammelrückführungen (Lit. L-He)",
"Sammelrückführung (Lit. L-He)", # safety (singular)
}
skip_indices = set()
# skip second column of each merged PAIR
if label in merged_pair_labels:
for right_name in ("AG Buntk.", "AG Gutfl."):
if right_name in clients_right:
skip_indices.add(clients_right.index(right_name))
# skip 2nd+3rd of the merged TRIPLE
if label in merged_m3_labels:
for name in ("M3 Buntkowsky", "M3 Gutfleisch"):
if name in clients_right:
skip_indices.add(clients_right.index(name))
total = Decimal("0")
for i, v in enumerate(values):
if i in skip_indices:
continue
if v in (None, ""):
continue
total += Decimal(str(v).replace(",", "."))
return total
def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[str, Any]:
"""
Returns a context dict with the SAME keys your current halfyear_balance.html expects.
@@ -227,8 +275,8 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
# ----------------------------
chosen_sheet_bottom1 = pick_sheet_by_gasbestand(window, sheets_by_ym, prev_sheet)
# IMPORTANT: define which bottom_1 row_index corresponds to Excel rows 27..35
# If your bottom_1 starts at Excel row 27 => row_index 0 == Excel 27
# define which bottom_1 row_index corresponds to Excel rows 27..35
# If bottom_1 starts at Excel row 27 => row_index 0 == Excel 27
# then row_index = excel_row - 27
BOTTOM1_EXCEL_START_ROW = 27
@@ -671,6 +719,20 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
right_data["M3 Gutfleisch"]['sammel'] = group3_total
def safe_div(a: Decimal, b: Decimal) -> Decimal:
return (a / b) if b != 0 else Decimal("0")
# Merck: Sammelrückführung = total helium input (ExcelEntry.lhe_ges) over the 6-month window
merck_sammel_total = Decimal("0")
for (y, m) in window:
qs = ExcelEntry.objects.filter(
client__name="Merck",
date__year=y,
date__month=m,
).aggregate(
total=Coalesce(Sum("lhe_ges"), Value(0, output_field=DecimalField()))
)
merck_sammel_total += Decimal(str(qs["total"]))
right_data["Merck"]["sammel"] = merck_sammel_total
# --- Rückführung flüssig (Lit. L-He) for Halbjahres-Bilanz top-right ---
# Uses your exact formulas.
@@ -741,6 +803,9 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
right_data["M3 Thiele"]["bestand_kannen"] = pick_bestand_top_right("M3 Thiele")
right_data["M3 Buntkowsky"]["bestand_kannen"] = pick_bestand_top_right("M3 Buntkowsky")
right_data["M3 Gutfleisch"]["bestand_kannen"] = pick_bestand_top_right("M3 Gutfleisch")
# Merck should stay empty in Bestand in Kannen-1
right_data["Merck"]["bestand_kannen"] = None
right_data["Merck"]["best_kannen_vormonat"] = None
# Summe Bestand = same as previous row
for cname in RIGHT_CLIENTS:
@@ -807,9 +872,23 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
b11 = right_data[cname]['summe_bestand']
# Excel: P13+P12-P11 etc.
right_data[cname]['rueckf_soll'] = b13 + b12 - b11
# Merck: only selected rows should have values in Top Right Halbjahresbilanz
right_data["Merck"]["stand_prev_share"] = None
right_data["Merck"]["rueckf_fluessig"] = None
right_data["Merck"]["sonder"] = None
right_data["Merck"]["bestand_kannen"] = None
right_data["Merck"]["summe_bestand"] = None
right_data["Merck"]["best_kannen_vormonat"] = None
right_data["Merck"]["rueckf_soll"] = None
right_data["Merck"]["verluste"] = None
right_data["Merck"]["fuellungen_warm"] = None
right_data["Merck"]["kaltgas_rueckgabe"] = None
# --- Verluste (Soll-Rückf.) (Lit. L-He) = B14 - B6 - B7 ---
for cname in RIGHT_CLIENTS:
if cname == "Merck":
right_data[cname]['verluste'] = None
continue
b14 = right_data[cname]['rueckf_soll']
b6 = right_data[cname]['rueckf_fluessig']
b7 = right_data[cname]['sonder']
@@ -829,17 +908,22 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
b13 = right_data[cname]['bezug']
right_data[cname]['kaltgas_rueckgabe'] = b13 * factor
# --- Verbraucherverluste (Liter L-He) = Verluste - Kaltgas Rückgabe ---
# --- Verbraucherverluste (Liter L-He) ---
for cname in RIGHT_CLIENTS:
b15 = right_data[cname]['verluste']
b17 = right_data[cname]['kaltgas_rueckgabe']
right_data[cname]['verbraucherverluste'] = b15 - b17
if cname == "Merck":
bezug = right_data[cname].get("bezug") or Decimal("0")
sammel = right_data[cname].get("sammel") or Decimal("0")
right_data[cname]["verbraucherverluste"] = bezug - sammel
else:
b15 = right_data[cname]['verluste']
b17 = right_data[cname]['kaltgas_rueckgabe']
right_data[cname]['verbraucherverluste'] = b15 - b17
# --- % = Verbraucherverluste / Bezug ---
for cname in RIGHT_CLIENTS:
bezug = right_data[cname]['bezug']
verb = right_data[cname]['verbraucherverluste']
if bezug != 0:
bezug = right_data[cname].get('bezug') or Decimal("0")
verb = right_data[cname].get('verbraucherverluste')
if bezug != 0 and verb is not None:
right_data[cname]['percent'] = verb / bezug
else:
right_data[cname]['percent'] = None
@@ -910,50 +994,78 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
def safe_pct(verb, bez):
return (verb / bez) if bez != 0 else None
def sum_right_key(label_for_merge: str, clients: list[str], key: str) -> Decimal:
vals = [right_data[c].get(key) for c in clients]
return sum_right_row_without_duplicates(label_for_merge, clients, vals)
rows_sum = []
def d(x):
return x if isinstance(x, Decimal) else Decimal("0")
for label, key in SUM_TABLE_ROWS:
for row_index, (label, key) in enumerate(SUM_TABLE_ROWS):
if key == "factor_row":
lichtwiese = chemie = mawi = m3 = total = Decimal("0.06")
elif key == "percent":
# Right totals
rw_bez = sum(d(right_data[c].get("bezug")) for c in RIGHT_ALL)
rw_verb = sum(d(right_data[c].get("verbraucherverluste")) for c in RIGHT_ALL)
# We want % = Verbraucherverluste / Bezug (for each group)
# LEFT totals (no merge on left)
left_bez = sum(d(client_data_left[c].get("bezug")) for c in LEFT_ALL)
left_verb = sum(d(client_data_left[c].get("verbraucherverluste")) for c in LEFT_ALL)
# RIGHT totals (must skip merged duplicates!)
rw_bez = sum(d(right_data[c].get("bezug")) for c in RIGHT_ALL)
rw_verb = sum_right_key("Verbraucherverluste (Liter L-He)", RIGHT_ALL, "verbraucherverluste")
lichtwiese = safe_pct(rw_verb, rw_bez)
# Chemie
ch_bez = sum(d(right_data[c].get("bezug")) for c in RIGHT_GROUPS["chemie"])
ch_verb = sum(d(right_data[c].get("verbraucherverluste")) for c in RIGHT_GROUPS["chemie"])
# Chemie group (Fohrer+Buntk)
ch_clients = RIGHT_GROUPS["chemie"]
ch_bez = sum_right_key("Bezug (Liter L-He)", ch_clients, "bezug")
ch_verb = sum_right_key("Verbraucherverluste (Liter L-He)", ch_clients, "verbraucherverluste")
chemie = safe_pct(ch_verb, ch_bez)
# MaWi
mw_bez = sum(d(right_data[c].get("bezug")) for c in RIGHT_GROUPS["mawi"])
mw_verb = sum(d(right_data[c].get("verbraucherverluste")) for c in RIGHT_GROUPS["mawi"])
# MaWi group (Alff+Gutfl)
mw_clients = RIGHT_GROUPS["mawi"]
mw_bez = sum_right_key("Bezug (Liter L-He)", mw_clients, "bezug")
mw_verb = sum_right_key("Verbraucherverluste (Liter L-He)", mw_clients, "verbraucherverluste")
mawi = safe_pct(mw_verb, mw_bez)
# M3
m3_bez = sum(d(right_data[c].get("bezug")) for c in RIGHT_GROUPS["m3"])
m3_verb = sum(d(right_data[c].get("verbraucherverluste")) for c in RIGHT_GROUPS["m3"])
# M3 group (triple)
m3_clients = RIGHT_GROUPS["m3"]
m3_bez = sum_right_key("Bezug (Liter L-He)", m3_clients, "bezug")
m3_verb = sum_right_key("Verbraucherverluste (Liter L-He)", m3_clients, "verbraucherverluste")
m3 = safe_pct(m3_verb, m3_bez)
# Σ column = (left verb + right verb) / (left bez + right bez)
left_bez = sum(d(client_data_left[c].get("bezug")) for c in LEFT_ALL)
left_verb = sum(d(client_data_left[c].get("verbraucherverluste")) for c in LEFT_ALL)
# Overall Σ (%)
total = safe_pct(left_verb + rw_verb, left_bez + rw_bez)
else:
# normal rows = sums
lichtwiese = sum(d(right_data[c].get(key)) for c in RIGHT_ALL)
chemie = sum(d(right_data[c].get(key)) for c in RIGHT_GROUPS["chemie"])
mawi = sum(d(right_data[c].get(key)) for c in RIGHT_GROUPS["mawi"])
m3 = sum(d(right_data[c].get(key)) for c in RIGHT_GROUPS["m3"])
# normal rows = sums (BUT skip duplicates for merged logical cells)
rw_values = [right_data[c].get(key) for c in RIGHT_ALL]
lichtwiese = sum_right_row_without_duplicates(label, RIGHT_ALL, rw_values)
ch_clients = RIGHT_GROUPS["chemie"]
ch_values = [right_data[c].get(key) for c in ch_clients]
chemie = sum_right_row_without_duplicates(label, ch_clients, ch_values)
mw_clients = RIGHT_GROUPS["mawi"]
mw_values = [right_data[c].get(key) for c in mw_clients]
mawi = sum_right_row_without_duplicates(label, mw_clients, mw_values)
m3_clients = RIGHT_GROUPS["m3"]
m3_values = [right_data[c].get(key) for c in m3_clients]
m3 = sum_right_row_without_duplicates(label, m3_clients, m3_values)
left_total = sum(d(client_data_left[c].get(key)) for c in LEFT_ALL)
total = left_total + lichtwiese
# Merck should count ONLY in the overall total, not in Lichtwiese
merck_val = d(right_data["Merck"].get(key))
total = left_total + lichtwiese + merck_val
# ✅ THIS MUST BE OUTSIDE THE IF/ELIF/ELSE
rows_sum.append({
"row_index": row_index,
"label": label,
@@ -964,6 +1076,7 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
"m3": m3,
"is_percent": (key == "percent"),
})
def find_sum_row(rows, label_startswith: str):
for r in rows:
if str(r.get("label", "")).strip().startswith(label_startswith):
@@ -999,8 +1112,8 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
bottom2["k44"] = k44
bottom2["j44"] = j44
def d(x):
return x if isinstance(x, Decimal) else Decimal("0")
def d_or_none(x):
return x if isinstance(x, Decimal) else None
# ---- Bottom2: J38/K38 depend on rows_sum (overall summary), so do it HERE ----
k38 = Decimal("0")