Files
he-database/sheets/services/halfyear_calc.py
2026-02-17 14:59:55 +01:00

1156 lines
45 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sheets/services/halfyear_calc.py
from __future__ import annotations
from django.db.models.functions import Coalesce
from decimal import Decimal
from typing import Dict, Any
from django.shortcuts import redirect, render
from decimal import Decimal
from sheets.models import (
Client, SecondTableEntry, Institute, ExcelEntry,
Betriebskosten, MonthlySheet, Cell, CellReference, MonthlySummary ,BetriebskostenSummary,AbrechnungCell
)
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_BESTAND_KANNEN_ROW = 5
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
a given client (by column) and row_index.
top_right cells are keyed by (sheet, table_type='top_right',
row_index, column_index), where column_index is the position of the
client in HALFYEAR_RIGHT_CLIENTS.
"""
if sheet is None:
return Decimal('0')
col_index = RIGHT_CLIENT_INDEX.get(client_name)
if col_index is None:
return Decimal('0')
cell = Cell.objects.filter(
sheet=sheet,
table_type='top_right',
row_index=row_index,
column_index=col_index,
).first()
if cell is None or cell.value in (None, ''):
return Decimal('0')
try:
return Decimal(str(cell.value))
except Exception:
return Decimal('0')
def get_bottom2_value(sheet, row_index: int, col_index: int) -> Decimal:
"""Get numeric value from bottom_2 or 0 if missing."""
if sheet is None:
return Decimal("0")
cell = Cell.objects.filter(
sheet=sheet,
table_type="bottom_2",
row_index=row_index,
column_index=col_index,
).first()
if cell is None or cell.value in (None, ""):
return Decimal("0")
try:
return Decimal(str(cell.value))
except Exception:
return Decimal("0")
def get_bottom1_value(sheet, row_index: int, col_index: int) -> Decimal:
"""Get a numeric value from bottom_1, or 0 if missing."""
if sheet is None:
return Decimal('0')
cell = Cell.objects.filter(
sheet=sheet,
table_type='bottom_1',
row_index=row_index,
column_index=col_index,
).first()
if cell is None or cell.value in (None, ''):
return Decimal('0')
try:
return Decimal(str(cell.value))
except Exception:
return Decimal('0')
def get_top_left_value(sheet, client_name: str, row_index: int) -> Decimal:
"""
Read a numeric value from the top_left table for a given month, client and row.
Does NOT use column_index, because top_left is keyed only by client + row_index.
"""
if sheet is None:
return Decimal('0')
client_obj = Client.objects.filter(name=client_name).first()
if not client_obj:
return Decimal('0')
cell = Cell.objects.filter(
sheet=sheet,
table_type='top_left',
client=client_obj,
row_index=row_index,
).first()
if cell is None or cell.value in (None, ''):
return Decimal('0')
try:
return Decimal(str(cell.value))
except Exception:
return Decimal('0')
def get_bestand_kannen_for_month(sheet, client_name: str) -> Decimal:
"""
'B9' in your description: Bestand in Kannen-1 (Lit. L-He)
For this implementation we take it from top_left row_index = 5 for that client.
"""
return get_top_left_value(sheet, client_name, row_index=BESTAND_KANNEN_ROW_INDEX)
def pick_sheet_by_gasbestand(window, sheets_by_ym, prev_sheet):
"""
Returns the last sheet in the window whose Gasbestand (J36, Nm³ column) != 0.
If none found, returns prev_sheet (Übertrag_Dez__Vorjahr equivalent).
"""
for (y, m) in reversed(window):
sheet = sheets_by_ym.get((y, m))
if not sheet:
continue
gasbestand_nm3 = get_bottom1_value(sheet, GASBESTAND_ROW_INDEX, GASBESTAND_COL_NM3)
if gasbestand_nm3 != 0:
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):
"""
Build a list of (year, month) for the 6-month interval, possibly crossing into the next year.
Example: (2025, 10) -> [(2025,10), (2025,11), (2025,12), (2026,1), (2026,2), (2026,3)]
"""
window = []
for offset in range(6):
total_index = (start_month - 1) + offset # 0-based
y = interval_year + (total_index // 12)
m = (total_index % 12) + 1
window.append((y, m))
return window
# Import ONLY models + pure helpers here
from sheets.models import MonthlySheet, Cell, Client, SecondTableEntry, BetriebskostenSummary
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.
"""
# ✅ Paste the pure calculation logic here in Step 2
window = build_halfyear_window(interval_year, interval_start)
# window = [(y1, m1), (y2, m2), ..., (y6, m6)]
# (Year, month) of the first month
start_year, start_month = window[0]
# Previous month (for "Stand ... (Vorjahr)" and "Best. in Kannen Vormonat")
prev_total_index = (start_month - 1) - 1 # one month back, 0-based
if prev_total_index >= 0:
prev_year = start_year + (prev_total_index // 12)
prev_month = (prev_total_index % 12) + 1
else:
prev_year = start_year - 1
prev_month = 12
# Load MonthlySheet objects for the window and for the previous month
sheets_by_ym = {}
for (y, m) in window:
sheet = MonthlySheet.objects.filter(year=y, month=m).first()
sheets_by_ym[(y, m)] = sheet
prev_sheet = MonthlySheet.objects.filter(year=prev_year, month=prev_month).first()
def pick_bottom2_from_window(window, sheets_by_ym, prev_sheet):
# choose sheet (same logic you already use)
chosen = None
for (y, m) in reversed(window):
s = sheets_by_ym.get((y, m))
# use your existing condition for choosing month
if s:
chosen = s
break
if chosen is None:
chosen = prev_sheet
# Now read the two inputs safely
bottom2_inputs = {}
for key, (row_idx, col_idx) in BOTTOM2_ROW_INPUTS.items():
bottom2_inputs[key] = get_bottom2_value(chosen, row_idx, col_idx)
return chosen, bottom2_inputs
chosen_sheet_bottom2, bottom2_inputs = pick_bottom2_from_window(window, sheets_by_ym, prev_sheet)
bottom2_g39 = bottom2_inputs["g39"]
bottom2_i39 = bottom2_inputs["i39"]
# ----------------------------
# HALF-YEAR BOTTOM TABLE 1 (Bilanz) - Read only
# ----------------------------
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
# then row_index = excel_row - 27
BOTTOM1_EXCEL_START_ROW = 27
bottom1_excel_rows = list(range(27, 37)) # 27..36
BOTTOM1_LABELS = [
"Batterie 1",
"2",
"3",
"4",
"5",
"Batterie Links",
"2 Bündel",
"2 Ballone",
"Reingasspeicher",
"Gasbestand",
]
BOTTOM1_VOLUMES = [
Decimal("2.4"),
Decimal("5.1"),
Decimal("4.0"),
Decimal("1.0"),
Decimal("4.0"),
Decimal("0.6"),
Decimal("1.2"),
Decimal("20.0"),
Decimal("5.0"),
None, # Gasbestand row has no volume
]
nm3_sum_27_35 = Decimal("0")
lhe_sum_27_35 = Decimal("0")
bottom1_rows = []
for excel_row in bottom1_excel_rows:
row_index = excel_row - BOTTOM1_EXCEL_START_ROW
chosen_sheet_bottom1 = None
for (y, m) in reversed(window):
s = sheets_by_ym.get((y, m))
gasbestand = get_bottom1_value(s, GASBESTAND_ROW_INDEX, GASBESTAND_COL_NM3) # J36 (Nm3)
if gasbestand != 0:
chosen_sheet_bottom1 = s
break
if chosen_sheet_bottom1 is None:
chosen_sheet_bottom1 = prev_sheet
# Normal rows (27..35): read from chosen sheet and accumulate sums
if excel_row != 36:
nm3_val = get_bottom1_value(chosen_sheet_bottom1, row_index, BOTTOM1_COL_NM3)
lhe_val = get_bottom1_value(chosen_sheet_bottom1, row_index, BOTTOM1_COL_LHE)
nm3_sum_27_35 += nm3_val
lhe_sum_27_35 += lhe_val
bottom1_rows.append({
"label": BOTTOM1_LABELS[row_index],
"volume": BOTTOM1_VOLUMES[row_index],
"bar": get_bottom1_value(chosen_sheet_bottom1, row_index, BOTTOM1_COL_BAR),
"korr": get_bottom1_value(chosen_sheet_bottom1, row_index, BOTTOM1_COL_KORR),
"nm3": nm3_val,
"lhe": lhe_val,
})
# Gasbestand row (36): show sums (J36 = SUM(J27:J35), K36 = SUM(K27:K35))
else:
bottom1_rows.append({
"label": "Gasbestand",
"volume": "",
"bar": "",
"korr": "",
"nm3": nm3_sum_27_35,
"lhe": lhe_sum_27_35,
})
start_sheet = sheets_by_ym.get((start_year, start_month))
# ------------------------------------------------------------
# Bottom Table 2 (Halbjahres Bilanz) server-side recalcBottom2()
# ------------------------------------------------------------
FACTOR_BT2 = Decimal("0.75")
# 1) Helper: pick last-nonzero value of bottom_2 row0 col0/col1 from the window (fallback: prev_sheet)
def pick_last_nonzero_bottom2(row_index: int, col_index: int) -> Decimal:
# Scan from last month in window backwards
for (y, m) in reversed(window):
s = sheets_by_ym.get((y, m))
if not s:
continue
v = get_bottom2_value(s, row_index, col_index)
if v is not None and v != 0:
return v
# fallback to month before window
v_prev = get_bottom2_value(prev_sheet, row_index, col_index)
return v_prev if v_prev is not None else Decimal("0")
# 2) K38 comes from Overall Summary: "Summe Bestand (Lit. L-He)"
# Find it from your already built overall summary rows list.
k38 = Decimal("0")
j38 = Decimal("0")
# 3) Inputs G39 / I39 (picked from last non-zero month in window)
g39 = pick_last_nonzero_bottom2(row_index=0, col_index=0) # G39
i39 = pick_last_nonzero_bottom2(row_index=0, col_index=1) # I39
k39 = (g39 or Decimal("0")) + (i39 or Decimal("0"))
j39 = k39 * FACTOR_BT2
# 4) +Kaltgas (row 40)
# JS:
# g40 = (2500 - g39)/100*10
# i40 = (1000 - i39)/100*10
g40 = None
i40 = None
if g39 is not None:
g40 = (Decimal("2500") - g39) / Decimal("100") * Decimal("10")
if i39 is not None:
i40 = (Decimal("1000") - i39) / Decimal("100") * Decimal("10")
k40 = (g40 or Decimal("0")) + (i40 or Decimal("0"))
j40 = k40 * FACTOR_BT2
# 5) Bestand flüssig He (row 43)
k43 = (
(k38 or Decimal("0")) +
(k39 or Decimal("0")) +
(k40 or Decimal("0"))
)
j43 = k43 * FACTOR_BT2
# 6) Gesamtbestand neu (row 44) = Gasbestand(Lit) from Bottom Table 1 + k43
gasbestand_lit = Decimal("0")
for r in bottom1_rows:
if (r.get("label") or "").strip().startswith("Gasbestand"):
gasbestand_lit = r.get("lhe") or Decimal("0")
break
k44 = (gasbestand_lit or Decimal("0")) + (k43 or Decimal("0"))
j44 = k44 * FACTOR_BT2
bottom2 = {
"j38": j38, "k38": k38,
"g39": g39, "i39": i39, "j39": j39, "k39": k39,
"g40": g40, "i40": i40, "j40": j40, "k40": k40,
"j43": j43, "k43": k43,
"j44": j44, "k44": k44,
}
# ------------------------------------------------------------------
# 2) LEFT TABLE (your existing, working logic)
# ------------------------------------------------------------------
HALFYEAR_CLIENTS_LEFT = ["AG Vogel", "AG Halfm", "IKP"]
# We'll collect client-wise values first for clarity.
client_data_left = {name: {} for name in HALFYEAR_CLIENTS_LEFT}
# --- Row B3: Stand der Gaszähler (Nm³)
# = MAX(B3 from previous month, and B3 from each of the 6 months in the window)
# row_index 0 in top_left = "Stand der Gaszähler (Nm³)"
months_for_max = [(prev_year, prev_month)] + window
for cname in HALFYEAR_CLIENTS_LEFT:
max_val = Decimal('0')
for (y, m) in months_for_max:
sheet = sheets_by_ym.get((y, m))
if sheet is None and (y, m) == (prev_year, prev_month):
sheet = prev_sheet
val_b3 = get_top_left_value(sheet, cname, row_index=0)
if val_b3 > max_val:
max_val = val_b3
client_data_left[cname]['stand_gas'] = max_val
# --- Row B4: Stand der Gaszähler (Vorjahr) (Nm³) -> previous month same row ---
for cname in HALFYEAR_CLIENTS_LEFT:
val_b4 = get_top_left_value(prev_sheet, cname, row_index=0)
client_data_left[cname]['stand_gas_prev'] = val_b4
# --- Row B5: Gasrückführung (Nm³) = B3 - B4 ---
for cname in HALFYEAR_CLIENTS_LEFT:
b3 = client_data_left[cname]['stand_gas']
b4 = client_data_left[cname]['stand_gas_prev']
client_data_left[cname]['gasrueckf'] = b3 - b4
# --- Row B6: Rückführung flüssig (Lit. L-He) = B5 / 0.75 ---
for cname in HALFYEAR_CLIENTS_LEFT:
b5 = client_data_left[cname]['gasrueckf']
client_data_left[cname]['rueckf_fluessig'] = (b5 / Decimal('0.75')) if b5 != 0 else Decimal('0')
# --- Row B7: Sonderrückführungen (Lit. L-He) = sum over 6 months of that row ---
# That row index is 4 in your top_left table.
for cname in HALFYEAR_CLIENTS_LEFT:
sonder_total = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
if sheet:
sonder_total += get_top_left_value(sheet, cname, row_index=4)
client_data_left[cname]['sonder'] = sonder_total
# --- Row B8: Bestand in Kannen-1 (Lit. L-He) ---
# Excel-style logic with Gasbestand (J36) and fallback to previous month.
for cname in HALFYEAR_CLIENTS_LEFT:
chosen_value = None
# Go from last month (window[5]) backwards to first (window[0])
for (y, m) in reversed(window):
sheet = sheets_by_ym.get((y, m))
gasbestand = get_bottom1_value(sheet, GASBESTAND_ROW_INDEX, GASBESTAND_COL_NM3)
if gasbestand != 0:
chosen_value = get_bestand_kannen_for_month(sheet, cname)
break
# If still None -> use previous month (Übertrag_Dez__Vorjahr equivalent)
if chosen_value is None:
sheet_prev = prev_sheet
chosen_value = get_bestand_kannen_for_month(sheet_prev, cname)
client_data_left[cname]['bestand_kannen'] = chosen_value if chosen_value is not None else Decimal('0')
# --- Row B9: Summe Bestand (Lit. L-He) = equal to previous row ---
for cname in HALFYEAR_CLIENTS_LEFT:
client_data_left[cname]['summe_bestand'] = client_data_left[cname]['bestand_kannen']
# --- Row B10: Best. in Kannen Vormonat (Lit. L-He)
# = Bestand in Kannen-1 from the month BEFORE the window (prev_year, prev_month)
for cname in HALFYEAR_CLIENTS_LEFT:
client_data_left[cname]['best_kannen_vormonat'] = get_bestand_kannen_for_month(prev_sheet, cname)
# --- Row B13: Bezug (Liter L-He) ---
for cname in HALFYEAR_CLIENTS_LEFT:
total_bezug = Decimal('0')
for (y, m) in window:
qs = SecondTableEntry.objects.filter(
client__name=cname,
date__year=y,
date__month=m,
).aggregate(
total=Coalesce(Sum('lhe_output'), Value(0, output_field=DecimalField()))
)
total_bezug += Decimal(str(qs['total']))
client_data_left[cname]['bezug'] = total_bezug
# --- Row B14: Rückführ. Soll (Lit. L-He) = Bezug - Summe Bestand + Best. in Kannen Vormonat ---
for cname in HALFYEAR_CLIENTS_LEFT:
b13 = client_data_left[cname]['bezug']
b11 = client_data_left[cname]['summe_bestand']
b12 = client_data_left[cname]['best_kannen_vormonat']
client_data_left[cname]['rueckf_soll'] = b13 - b11 + b12
# --- Row B15: Verluste (Soll-Rückf.) (Lit. L-He)
# AG Vogel, AG Halfm: B14 - B6
# IKP: B14 - B6 - B7 (Sonderrückführungen)
for cname in HALFYEAR_CLIENTS_LEFT:
b14 = client_data_left[cname]['rueckf_soll']
b6 = client_data_left[cname]['rueckf_fluessig']
if (cname or "").strip() == "IKP":
b7 = client_data_left[cname].get('sonder', Decimal('0'))
client_data_left[cname]['verluste'] = b14 - b6 - b7
else:
client_data_left[cname]['verluste'] = b14 - b6
# --- Row B16: Füllungen warm (Lit. L-He) = sum over 6 months (row_index=11) ---
for cname in HALFYEAR_CLIENTS_LEFT:
total_warm = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
total_warm += get_top_left_value(sheet, cname, row_index=11)
client_data_left[cname]['fuellungen_warm'] = total_warm
# --- Row B17: Kaltgas Rückgabe (Lit. L-He) = Bezug * 0.06 ---
factor = Decimal('0.06')
for cname in HALFYEAR_CLIENTS_LEFT:
b13 = client_data_left[cname]['bezug']
client_data_left[cname]['kaltgas_rueckgabe'] = b13 * factor
# --- Row B18: Verbraucherverluste (Liter L-He) = Verluste - Kaltgas Rückgabe ---
for cname in HALFYEAR_CLIENTS_LEFT:
b15 = client_data_left[cname]['verluste']
b17 = client_data_left[cname]['kaltgas_rueckgabe']
client_data_left[cname]['verbraucherverluste'] = b15 - b17
# --- Row B19: % = Verbraucherverluste / Bezug ---
for cname in HALFYEAR_CLIENTS_LEFT:
bezug = client_data_left[cname]['bezug']
verb = client_data_left[cname]['verbraucherverluste']
if bezug != 0:
client_data_left[cname]['percent'] = verb / bezug
else:
client_data_left[cname]['percent'] = None
# Build LEFT rows structure
left_row_defs = [
('Stand der Gaszähler (Nm³)', 'stand_gas'),
('Stand der Gaszähler (Vorjahr) (Nm³)', 'stand_gas_prev'),
('Gasrückführung (Nm³)', 'gasrueckf'),
('Rückführung flüssig (Lit. L-He)', 'rueckf_fluessig'),
('Sonderrückführungen (Lit. L-He)', 'sonder'),
('Bestand in Kannen-1 (Lit. L-He)', 'bestand_kannen'),
('Summe Bestand (Lit. L-He)', 'summe_bestand'),
('Best. in Kannen Vormonat (Lit. L-He)', 'best_kannen_vormonat'),
('Bezug (Liter L-He)', 'bezug'),
('Rückführ. Soll (Lit. L-He)', 'rueckf_soll'),
('Verluste (Soll-Rückf.) (Lit. L-He)', 'verluste'),
('Füllungen warm (Lit. L-He)', 'fuellungen_warm'),
('Kaltgas Rückgabe (Lit. L-He) Faktor', 'kaltgas_rueckgabe'),
('Verbraucherverluste (Liter L-He)', 'verbraucherverluste'),
('%', 'percent'),
]
rows_left = []
for label, key in left_row_defs:
values = [client_data_left[cname][key] for cname in HALFYEAR_CLIENTS_LEFT]
if key == 'percent':
total_bezug = sum((client_data_left[c]['bezug'] for c in HALFYEAR_CLIENTS_LEFT), Decimal('0'))
total_verb = sum((client_data_left[c]['verbraucherverluste'] for c in HALFYEAR_CLIENTS_LEFT), Decimal('0'))
total = (total_verb / total_bezug) if total_bezug != 0 else None
else:
total = sum((v for v in values if v is not None), Decimal('0'))
rows_left.append({
'label': label,
'values': values,
'total': total,
'is_percent': key == 'percent',
})
# ------------------------------------------------------------------
# 3) RIGHT TABLE (top-right half-year aggregation)
# ------------------------------------------------------------------
RIGHT_CLIENTS = HALFYEAR_RIGHT_CLIENTS # for brevity
right_data = {name: {} for name in RIGHT_CLIENTS}
# --- Bezug (Liter L-He) for each right client (same as for left) ---
for cname in RIGHT_CLIENTS:
total_bezug = Decimal('0')
for (y, m) in window:
qs = SecondTableEntry.objects.filter(
client__name=cname,
date__year=y,
date__month=m,
).aggregate(
total=Coalesce(Sum('lhe_output'), Value(0, output_field=DecimalField()))
)
total_bezug += Decimal(str(qs['total']))
right_data[cname]['bezug'] = total_bezug
def find_bestand_from_window(reference_client: str) -> Decimal:
"""
Implements:
WENN(last_month!J36=0; WENN(prev_month!J36=0; ...; prev_sheet!<col>9); last_month!<col>9)
reference_client decides which column (L/N/P/Q/R) we read from monthly top_right row_index=5.
"""
# scan backward through window
for (y, m) in reversed(window):
sh = sheets_by_ym.get((y, m))
if not sh:
continue
gasbestand = get_bottom1_value(sh, GASBESTAND_ROW_INDEX, GASBESTAND_COL_NM3)
if gasbestand != 0:
return get_top_right_value(sh, reference_client, TR_BESTAND_KANNEN_ROW)
# fallback to previous month (Übertrag_Dez__Vorjahr equivalent)
return get_top_right_value(prev_sheet, reference_client, TR_BESTAND_KANNEN_ROW)
# Fohrer+Buntk merged: BOTH use Fohrer column (L9)
val_L = find_bestand_from_window("Dr. Fohrer")
right_data["Dr. Fohrer"]["bestand_kannen"] = val_L
right_data["AG Buntk."]["bestand_kannen"] = val_L
# Alff+Gutfl merged: BOTH use Alff column (N9)
val_N = find_bestand_from_window("AG Alff")
right_data["AG Alff"]["bestand_kannen"] = val_N
right_data["AG Gutfl."]["bestand_kannen"] = val_N
# M3 each uses its own column (P9/Q9/R9)
right_data["M3 Thiele"]["bestand_kannen"] = find_bestand_from_window("M3 Thiele")
right_data["M3 Buntkowsky"]["bestand_kannen"] = find_bestand_from_window("M3 Buntkowsky")
right_data["M3 Gutfleisch"]["bestand_kannen"] = find_bestand_from_window("M3 Gutfleisch")
# Helper for pair shares (L13/($L13+$M13), etc.)
def pair_share(c1, c2):
total = right_data[c1]['bezug'] + right_data[c2]['bezug']
if total == 0:
return (Decimal('0'), Decimal('0'))
return (
right_data[c1]['bezug'] / total,
right_data[c2]['bezug'] / total,
)
# --- "Stand der Gaszähler (Vorjahr) (Nm³)" row: share based on Bezug ---
# Dr. Fohrer / AG Buntk.
s_fohrer, s_buntk = pair_share("Dr. Fohrer", "AG Buntk.")
right_data["Dr. Fohrer"]['stand_prev_share'] = s_fohrer
right_data["AG Buntk."]['stand_prev_share'] = s_buntk
# AG Alff / AG Gutfl.
s_alff, s_gutfl = pair_share("AG Alff", "AG Gutfl.")
right_data["AG Alff"]['stand_prev_share'] = s_alff
right_data["AG Gutfl."]['stand_prev_share'] = s_gutfl
# M3 Thiele / M3 Buntkowsky / M3 Gutfleisch → empty in Excel → None
for cname in ["M3 Thiele", "M3 Buntkowsky", "M3 Gutfleisch"]:
right_data[cname]['stand_prev_share'] = None
# --- Rückführung flüssig per month (raw sums) ---
# top_right row_index=2 is "Rückführung flüssig (Lit. L-He)"
# --- Sonderrückführungen (row_index=3 in top_right) ---
for cname in RIGHT_CLIENTS:
sonder_total = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
if sheet:
sonder_total += get_top_right_value(sheet, cname, row_index=3)
right_data[cname]['sonder'] = sonder_total
# --- Sammelrückführung (row_index=4 in top_right), grouped & merged ---
# Group 1: Dr. Fohrer + AG Buntk.
group1_total = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
if sheet:
group1_total += get_top_right_value(sheet, "Dr. Fohrer", row_index=4)
right_data["Dr. Fohrer"]['sammel'] = group1_total
right_data["AG Buntk."]['sammel'] = group1_total
# Group 2: AG Alff + AG Gutfl.
group2_total = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
if sheet:
group2_total += get_top_right_value(sheet, "AG Alff", row_index=4)
right_data["AG Alff"]['sammel'] = group2_total
right_data["AG Gutfl."]['sammel'] = group2_total
# Group 3: M3 Thiele + M3 Buntkowsky + M3 Gutfleisch
group3_total = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
if sheet:
group3_total += get_top_right_value(sheet, "M3 Thiele", row_index=4)
right_data["M3 Thiele"]['sammel'] = group3_total
right_data["M3 Buntkowsky"]['sammel'] = group3_total
right_data["M3 Gutfleisch"]['sammel'] = group3_total
def safe_div(a: Decimal, b: Decimal) -> Decimal:
return (a / b) if b != 0 else Decimal("0")
# --- Rückführung flüssig (Lit. L-He) for Halbjahres-Bilanz top-right ---
# Uses your exact formulas.
# 1) Fohrer / Buntk split by BEZUG share times group SAMMEL (L8)
L13 = right_data["Dr. Fohrer"]["bezug"]
M13 = right_data["AG Buntk."]["bezug"]
L8 = right_data["Dr. Fohrer"]["sammel"] # merged group total
den = (L13 + M13)
right_data["Dr. Fohrer"]["rueckf_fluessig"] = (safe_div(L13, den) * L8) if den != 0 else Decimal("0")
right_data["AG Buntk."]["rueckf_fluessig"] = (safe_div(M13, den) * L8) if den != 0 else Decimal("0")
# 2) Alff / Gutfl split by BEZUG share times group SAMMEL (N8)
N13 = right_data["AG Alff"]["bezug"]
O13 = right_data["AG Gutfl."]["bezug"]
N8 = right_data["AG Alff"]["sammel"] # merged group total
den = (N13 + O13)
right_data["AG Alff"]["rueckf_fluessig"] = (safe_div(N13, den) * N8) if den != 0 else Decimal("0")
right_data["AG Gutfl."]["rueckf_fluessig"] = (safe_div(O13, den) * N8) if den != 0 else Decimal("0")
# 3) M3 Thiele = sum of monthly Rückführung flüssig (monthly top_right row_index=2) over window
P6_sum = Decimal("0")
for (y, m) in window:
sh = sheets_by_ym.get((y, m))
P6_sum += get_top_right_value(sh, "M3 Thiele", TR_RUECKF_FLUESSIG_ROW)
right_data["M3 Thiele"]["rueckf_fluessig"] = P6_sum
# 4) M3 Buntkowsky / M3 Gutfleisch split by BEZUG share times M3-group SAMMEL (P8)
P13 = right_data["M3 Thiele"]["bezug"]
Q13 = right_data["M3 Buntkowsky"]["bezug"]
R13 = right_data["M3 Gutfleisch"]["bezug"]
P8 = right_data["M3 Thiele"]["sammel"] # merged group total
den = (P13 + Q13 + R13)
right_data["M3 Buntkowsky"]["rueckf_fluessig"] = (safe_div(Q13, den) * P8) if den != 0 else Decimal("0")
right_data["M3 Gutfleisch"]["rueckf_fluessig"] = (safe_div(R13, den) * P8) if den != 0 else Decimal("0")
# --- Bestand in Kannen-1 (Lit. L-He) for right table (grouped) ---
# Use Gasbestand (J36) and fallback logic, but now reading top_right B9 for each group.
TOP_RIGHT_ROW_BESTAND_KANNEN = TR_BESTAND_KANNEN_ROW # <-- most likely correct in your setup
def pick_bestand_top_right(base_client: str) -> Decimal:
# Go from last month in window backwards: if Gasbestand != 0, use that month's Bestand in Kannen
for (y, m) in reversed(window):
sh = sheets_by_ym.get((y, m))
if not sh:
continue
gasbestand = get_bottom1_value(sh, GASBESTAND_ROW_INDEX, GASBESTAND_COL_NM3)
if gasbestand != 0:
return get_top_right_value(sh, base_client, TOP_RIGHT_ROW_BESTAND_KANNEN)
# Fallback to previous month (Übertrag_Dez__Vorjahr equivalent)
return get_top_right_value(prev_sheet, base_client, TOP_RIGHT_ROW_BESTAND_KANNEN)
# Group 1 merged (Fohrer + Buntk.)
g1_best = pick_bestand_top_right("Dr. Fohrer")
right_data["Dr. Fohrer"]["bestand_kannen"] = g1_best
right_data["AG Buntk."]["bestand_kannen"] = g1_best
# Group 2 merged (Alff + Gutfl.)
g2_best = pick_bestand_top_right("AG Alff")
right_data["AG Alff"]["bestand_kannen"] = g2_best
right_data["AG Gutfl."]["bestand_kannen"] = g2_best
# Group 3 NOT merged: each M3 client uses its own most recent nonzero Bestand in Kannen-1
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")
# Summe Bestand = same as previous row
for cname in RIGHT_CLIENTS:
right_data[cname]['summe_bestand'] = right_data[cname]['bestand_kannen']
# Best. in Kannen Vormonat (Lit. L-He) from previous month top_right row_index=7
g1_prev = get_top_right_value(prev_sheet, "Dr. Fohrer", TOP_RIGHT_ROW_BESTAND_KANNEN)
right_data["Dr. Fohrer"]['best_kannen_vormonat'] = g1_prev
right_data["AG Buntk."]['best_kannen_vormonat'] = g1_prev
g2_prev = get_top_right_value(prev_sheet, "AG Alff", TOP_RIGHT_ROW_BESTAND_KANNEN)
right_data["AG Alff"]['best_kannen_vormonat'] = g2_prev
right_data["AG Gutfl."]['best_kannen_vormonat'] = g2_prev
# Group 1 merged (Fohrer + Buntk.)
g1_prev = get_top_right_value(prev_sheet, "Dr. Fohrer", TOP_RIGHT_ROW_BESTAND_KANNEN)
right_data["Dr. Fohrer"]["best_kannen_vormonat"] = g1_prev
right_data["AG Buntk."]["best_kannen_vormonat"] = g1_prev
# Group 2 merged (Alff + Gutfl.)
g2_prev = get_top_right_value(prev_sheet, "AG Alff", TOP_RIGHT_ROW_BESTAND_KANNEN)
right_data["AG Alff"]["best_kannen_vormonat"] = g2_prev
right_data["AG Gutfl."]["best_kannen_vormonat"] = g2_prev
# Group 3 UNMERGED (each one reads its own cell)
right_data["M3 Thiele"]["best_kannen_vormonat"] = get_top_right_value(prev_sheet, "M3 Thiele", TOP_RIGHT_ROW_BESTAND_KANNEN)
right_data["M3 Buntkowsky"]["best_kannen_vormonat"] = get_top_right_value(prev_sheet, "M3 Buntkowsky", TOP_RIGHT_ROW_BESTAND_KANNEN)
right_data["M3 Gutfleisch"]["best_kannen_vormonat"] = get_top_right_value(prev_sheet, "M3 Gutfleisch", TOP_RIGHT_ROW_BESTAND_KANNEN)
# --- Rückführ. Soll (Lit. L-He) according to your formulas ---
# Group 1: Dr. Fohrer / AG Buntk.
total_bestand_1 = right_data["Dr. Fohrer"]['summe_bestand']
best_vormonat_1 = right_data["Dr. Fohrer"]['best_kannen_vormonat']
diff1 = total_bestand_1 - best_vormonat_1
share_fohrer = right_data["Dr. Fohrer"]['stand_prev_share'] or Decimal('0')
right_data["Dr. Fohrer"]['rueckf_soll'] = (
right_data["Dr. Fohrer"]['bezug'] - diff1 * share_fohrer
)
right_data["AG Buntk."]['rueckf_soll'] = (
right_data["AG Buntk."]['bezug'] - total_bestand_1 + best_vormonat_1
)
# Group 2: AG Alff / AG Gutfl.
total_bestand_2 = right_data["AG Alff"]['summe_bestand']
best_vormonat_2 = right_data["AG Alff"]['best_kannen_vormonat']
diff2 = total_bestand_2 - best_vormonat_2
share_alff = right_data["AG Alff"]['stand_prev_share'] or Decimal('0')
share_gutfl = right_data["AG Gutfl."]['stand_prev_share'] or Decimal('0')
right_data["AG Alff"]['rueckf_soll'] = (
right_data["AG Alff"]['bezug'] - diff2 * share_alff
)
right_data["AG Gutfl."]['rueckf_soll'] = (
right_data["AG Gutfl."]['bezug'] - diff2 * share_gutfl
)
# Group 3: M3 Thiele / M3 Buntkowsky / M3 Gutfleisch
for cname in ["M3 Thiele", "M3 Buntkowsky", "M3 Gutfleisch"]:
b13 = right_data[cname]['bezug']
b12 = right_data[cname]['best_kannen_vormonat']
b11 = right_data[cname]['summe_bestand']
# Excel: P13+P12-P11 etc.
right_data[cname]['rueckf_soll'] = b13 + b12 - b11
# --- Verluste (Soll-Rückf.) (Lit. L-He) = B14 - B6 - B7 ---
for cname in RIGHT_CLIENTS:
b14 = right_data[cname]['rueckf_soll']
b6 = right_data[cname]['rueckf_fluessig']
b7 = right_data[cname]['sonder']
right_data[cname]['verluste'] = b14 - b6 - b7
# --- Füllungen warm (Lit. L-He) = sum of monthly 'Füllungen warm' (row_index=11 top_right) ---
for cname in RIGHT_CLIENTS:
total_warm = Decimal('0')
for (y, m) in window:
sheet = sheets_by_ym.get((y, m))
if sheet:
total_warm += get_top_right_value(sheet, cname, row_index=11)
right_data[cname]['fuellungen_warm'] = total_warm
# --- Kaltgas Rückgabe (Lit. L-He) Faktor = Bezug * 0.06 ---
for cname in RIGHT_CLIENTS:
b13 = right_data[cname]['bezug']
right_data[cname]['kaltgas_rueckgabe'] = b13 * factor
# --- Verbraucherverluste (Liter L-He) = Verluste - Kaltgas Rückgabe ---
for cname in RIGHT_CLIENTS:
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:
right_data[cname]['percent'] = verb / bezug
else:
right_data[cname]['percent'] = None
# Build RIGHT rows structure
right_row_defs = [
('Stand der Gaszähler (Vorjahr) (Nm³)', 'stand_prev_share'),
# We skip the pure-text "Gasrückführung (Nm³)" line here,
# because its only text (Aufteilung nach Verbrauch / Gaszähler)
# and easier to render directly in the template if needed.
('Rückführung flüssig (Lit. L-He)', 'rueckf_fluessig'),
('Sonderrückführungen (Lit. L-He)', 'sonder'),
('Sammelrückführung (Lit. L-He)', 'sammel'),
('Bestand in Kannen-1 (Lit. L-He)', 'bestand_kannen'),
('Summe Bestand (Lit. L-He)', 'summe_bestand'),
('Best. in Kannen Vormonat (Lit. L-He)', 'best_kannen_vormonat'),
('Bezug (Liter L-He)', 'bezug'),
('Rückführ. Soll (Lit. L-He)', 'rueckf_soll'),
('Verluste (Soll-Rückf.) (Lit. L-He)', 'verluste'),
('Füllungen warm (Lit. L-He)', 'fuellungen_warm'),
('Kaltgas Rückgabe (Lit. L-He) Faktor', 'kaltgas_rueckgabe'),
('Verbraucherverluste (Liter L-He)', 'verbraucherverluste'),
('%', 'percent'),
]
rows_right = []
for label, key in right_row_defs:
values = [right_data[cname].get(key) for cname in RIGHT_CLIENTS]
if key == 'percent':
total_bezug = sum((right_data[c]['bezug'] for c in RIGHT_CLIENTS), Decimal('0'))
total_verb = sum((right_data[c]['verbraucherverluste'] for c in RIGHT_CLIENTS), Decimal('0'))
total = (total_verb / total_bezug) if total_bezug != 0 else None
else:
total = sum((v for v in values if isinstance(v, Decimal)), Decimal('0'))
rows_right.append({
'label': label,
'values': values,
'total': total,
'is_percent': key == 'percent',
})
SUM_TABLE_ROWS = [
("Rückführung flüssig (Lit. L-He)", "rueckf_fluessig"),
("Sonderrückführungen (Lit. L-He)", "sonder"),
("Sammelrückführungen (Lit. L-He)", "sammel"),
("Bestand in Kannen-1 (Lit. L-He)", "bestand_kannen"),
("Summe Bestand (Lit. L-He)", "summe_bestand"),
("Best. in Kannen Vormonat (Lit. L-He)", "best_kannen_vormonat"),
("Bezug (Liter L-He)", "bezug"),
("Rückführ. Soll (Lit. L-He)", "rueckf_soll"),
("Verluste (Soll-Rückf.) (Lit. L-He)", "verluste"),
("Füllungen warm (Lit. L-He)", "fuellungen_warm"),
("Kaltgas Rückgabe (Lit. L-He) Faktor", "kaltgas_rueckgabe"),
("Faktor 0.06", "factor_row"),
("Verbraucherverluste (Liter L-He)", "verbraucherverluste"),
("%", "percent"),
]
RIGHT_GROUPS = {
"chemie": ["Dr. Fohrer", "AG Buntk."],
"mawi": ["AG Alff", "AG Gutfl."],
"m3": ["M3 Thiele", "M3 Buntkowsky", "M3 Gutfleisch"],
}
RIGHT_ALL = ["Dr. Fohrer", "AG Buntk.", "AG Alff", "AG Gutfl.", "M3 Thiele", "M3 Buntkowsky", "M3 Gutfleisch"]
LEFT_ALL = HALFYEAR_CLIENTS_LEFT
def safe_pct(verb, bez):
return (verb / bez) if bez != 0 else None
rows_sum = []
def d(x):
return x if isinstance(x, Decimal) else Decimal("0")
for label, key in 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)
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 = 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 = 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 = 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)
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"])
left_total = sum(d(client_data_left[c].get(key)) for c in LEFT_ALL)
total = left_total + lichtwiese
rows_sum.append({
"row_index": row_index,
"label": label,
"total": total,
"lichtwiese": lichtwiese,
"chemie": chemie,
"mawi": mawi,
"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):
return r
return None
summe_bestand_row = find_sum_row(rows_sum, "Summe Bestand")
k38 = (summe_bestand_row.get("total") if summe_bestand_row else Decimal("0")) or Decimal("0")
j38 = k38 * Decimal("0.75")
# --- FIX: now that k38 is known, update bottom2 + recompute dependent rows ---
bottom2["k38"] = k38
bottom2["j38"] = j38
k39 = bottom2.get("k39") or Decimal("0")
k40 = bottom2.get("k40") or Decimal("0")
# Row 43: Bestand flüssig He = SUMME(K38:K40)
k43 = (k38 or Decimal("0")) + k39 + k40
j43 = k43 * Decimal("0.75")
bottom2["k43"] = k43
bottom2["j43"] = j43
# Row 44: Gesamtbestand neu = Gasbestand(Lit) from bottom table 1 + k43
gasbestand_lit = Decimal("0")
for r in bottom1_rows:
if (r.get("label") or "").strip().startswith("Gasbestand"):
gasbestand_lit = r.get("lhe") or Decimal("0")
break
k44 = gasbestand_lit + k43
j44 = k44 * Decimal("0.75")
bottom2["k44"] = k44
bottom2["j44"] = j44
def d(x):
return x if isinstance(x, Decimal) else Decimal("0")
# ---- Bottom2: J38/K38 depend on rows_sum (overall summary), so do it HERE ----
k38 = Decimal("0")
for r in rows_sum:
if r.get("label") == "Summe Bestand (Lit. L-He)":
k38 = r.get("total") or Decimal("0")
break
j38 = k38 * FACTOR_NM3_TO_LHE # 0.75
bottom2["k38"] = k38
bottom2["j38"] = j38
factor = Decimal("0.75")
# window = the 6-month list you already build in this view: [(y,m), (y,m), ...]
# bottom2 = dict with "k44" already computed in your view
# rows_sum = overall sum group rows list (your existing halfyear logic)
# 1) K46 = K44 from the month BEFORE the global month (interval start)
start_year = interval_year
start_month = interval_start # whatever you named your start month variable
if start_month == 1:
prev_year, prev_month = start_year - 1, 12
else:
prev_year, prev_month = start_year, start_month - 1
prev_sheet = MonthlySheet.objects.filter(year=prev_year, month=prev_month).first()
# This assumes you have MonthlySummary.gesamtbestand_neu_lhe as K44 equivalent.
# If your field name differs, tell me your MonthlySummary model fields.
prev_k44 = Decimal("0")
if prev_sheet:
prev_sum = MonthlySummary.objects.filter(sheet=prev_sheet).first()
if prev_sum and prev_sum.gesamtbestand_neu_lhe is not None:
prev_k44 = Decimal(str(prev_sum.gesamtbestand_neu_lhe))
# helpers: read bottom_3 values for a given sheet/month
def get_bottom3_value(sheet, row_index, col_index):
if not sheet:
return Decimal("0")
c = Cell.objects.filter(
sheet=sheet, table_type="bottom_3",
row_index=row_index, column_index=col_index
).first()
if not c or c.value in (None, "", "None"):
return Decimal("0")
try:
return Decimal(str(c.value))
except Exception:
return Decimal("0")
# 2) Sum rows across the 6-month window
g47_sum = Decimal("0")
i47_sum = Decimal("0")
j47_sum = Decimal("0")
g50_sum = Decimal("0")
i50_sum = Decimal("0")
for (yy, mm) in window:
s = MonthlySheet.objects.filter(year=yy, month=mm).first()
## Excel row 47 maps to bottom_3 row_index=1 in DB (see monthly_sheet.html)
g = get_bottom3_value(s, 1, 1) # G47 editable
i = get_bottom3_value(s, 1, 2) # I47 editable
g47_sum += g
i47_sum += i
# In monthly_sheet, J47 is CALCULATED as (G47 + I47), not stored as an editable cell
j47_sum += (g + i)
# row 50: G(2), I(4)
g50_sum += get_bottom3_value(s, 50, 2)
i50_sum += get_bottom3_value(s, 50, 4)
# 3) K52 = Verbraucherverlust from overall sum group first column (global Σ)
k52 = Decimal("0")
for r in rows_sum:
label = (r.get("label") or "")
if label.startswith("Verbraucherverluste"):
k52 = r.get("total") or Decimal("0")
break
# --- apply the SAME monthly formulas, with your overrides ---
# Row 46
k46 = prev_k44
j46 = k46 * factor
# Row 47
g47 = g47_sum
i47 = i47_sum
j47 = j47_sum
k47 = (j47 / factor) + g47 if (j47 != 0 or g47 != 0) else Decimal("0")
# Row 48
k48 = k46 + k47
j48 = k48 * factor
# Row 49 (akt. Monat) -> in halfyear we use current bottom2 K44
k49 = bottom2.get("k44") or Decimal("0")
j49 = k49 * factor
# Row 50
g50 = g50_sum
i50 = i50_sum
j50 = i50
k50 = (j50 / factor) if j50 != 0 else Decimal("0")
# Row 51
k51 = k48 - k49 - k50
j51 = k51 * factor
# Row 52
j52 = k52 * factor
# Row 53
k53 = k51 - k52
j53 = j51 - j52
bottom3 = {
"j46": j46, "k46": k46,
"g47": g47, "i47": i47, "j47": j47, "k47": k47,
"j48": j48, "k48": k48,
"j49": j49, "k49": k49,
"g50": g50, "i50": i50, "j50": j50, "k50": k50,
"j51": j51, "k51": k51,
"j52": j52, "k52": k52,
"j53": j53, "k53": k53,
}
# ------------------------------------------------------------------
# 4) Context keep old keys AND new ones
# ------------------------------------------------------------------
context = {
'interval_year': interval_year,
'interval_start_month': interval_start,
'window': window,
'clients': HALFYEAR_CLIENTS_LEFT,
'rows': rows_left,
'clients_left': HALFYEAR_CLIENTS_LEFT,
'rows_left': rows_left,
'clients_right': RIGHT_CLIENTS,
'rows_right': rows_right,
'rows_sum': rows_sum,
'bottom1_rows': bottom1_rows,
}
context["bottom2"] = bottom2
context["bottom3"] = bottom3
context["context_bottom2_g39"] = bottom2_inputs["g39"]
context["context_bottom2_i39"] = bottom2_inputs["i39"]
return context