chellow 1750597108.0.0__py3-none-any.whl → 1751036864.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of chellow might be problematic. Click here for more details.

@@ -4,15 +4,8 @@ from decimal import Decimal
4
4
 
5
5
  from werkzeug.exceptions import BadRequest
6
6
 
7
- from chellow.edi_lib import (
8
- ct_datetime,
9
- parse_edi,
10
- to_date,
11
- to_decimal,
12
- to_finish_date,
13
- to_utc,
14
- )
15
- from chellow.utils import HH
7
+ from chellow.edi_lib import parse_edi, to_date, to_decimal, to_finish_date
8
+ from chellow.utils import HH, parse_mpan_core
16
9
 
17
10
 
18
11
  read_type_map = {
@@ -28,127 +21,136 @@ read_type_map = {
28
21
  "12": "IF",
29
22
  }
30
23
 
31
-
32
- TMOD_MAP = {
33
- "139039": ("aahedc-gbp", "aahedc-rate", "aahedc-kwh"),
24
+ ELEMENT_MAP = {
25
+ "AAH": {
26
+ "AAHEDC": {
27
+ "139039": ("aahedc-gbp", "aahedc-rate", "aahedc-kwh"),
28
+ },
29
+ },
30
+ "ADH": {
31
+ "ADHOC": {
32
+ "020330": ("eii-gbp", None, None),
33
+ "637050": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
34
+ },
35
+ },
36
+ "BUS": {
37
+ "BSUOS": {
38
+ "269100": ("bsuos-gbp", "bsuos-rate", "bsuos-kwh"),
39
+ },
40
+ },
41
+ "CCL": {
42
+ "CCL": {
43
+ "422733": ("ccl-gbp", "ccl-rate", "ccl-kwh"),
44
+ },
45
+ },
46
+ "CFD": {
47
+ "CFD001": {
48
+ "273237": (
49
+ "cfd-operational-gbp",
50
+ "cfd-operational-rate",
51
+ "cfd-operational-kwh",
52
+ ),
53
+ "954379": ("cfd-interim-gbp", "cfd-interim-rate", "cfd-interim-kwh"),
54
+ "538249": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
55
+ "568307": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
56
+ },
57
+ },
58
+ "DCA": {
59
+ "DCDA": {
60
+ "095469": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
61
+ },
62
+ },
63
+ "DUS": {
64
+ "DUS001": {
65
+ "794486": (
66
+ "duos-availability-gbp",
67
+ "duos-availability-rate",
68
+ "duos-availability-kva",
69
+ ),
70
+ "644819": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
71
+ "797790": (
72
+ "duos-reactive-gbp",
73
+ "duos-reactive-rate",
74
+ "duos-reactive-kvarh",
75
+ ),
76
+ "806318": ("duos-green-gbp", "duos-green-rate", "duos-green-kwh"),
77
+ "716514": ("duos-amber-gbp", "duos-amber-rate", "duos-amber-kwh"),
78
+ "769979": ("duos-red-gbp", "duos-red-rate", "duos-red-kwh"),
79
+ "709522": (
80
+ "duos-excess-availability-gbp",
81
+ "duos-excess-availability-rate",
82
+ "duos-excess-availability-kva",
83
+ ),
84
+ "209269": ("tnuos-gbp", "tnuos-rate", "tnuos-days"),
85
+ },
86
+ "DUS002": {
87
+ "797790": (
88
+ "duos-reactive-gbp",
89
+ "duos-reactive-rate",
90
+ "duos-reactive-kvarh",
91
+ ),
92
+ "806318": ("duos-green-gbp", "duos-green-rate", "duos-green-kwh"),
93
+ "716514": ("duos-amber-gbp", "duos-amber-rate", "duos-amber-kwh"),
94
+ "709522": (
95
+ "duos-excess-availability-gbp",
96
+ "duos-excess-availability-rate",
97
+ "duos-excess-availability-kva",
98
+ ),
99
+ "769979": ("duos-red-gbp", "duos-red-rate", "duos-red-kwh"),
100
+ "644819": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
101
+ "794486": (
102
+ "duos-availability-gbp",
103
+ "duos-availability-rate",
104
+ "duos-availability-kva",
105
+ ),
106
+ "209269": ("tnuos-gbp", "tnuos-rate", "tnuos-days"),
107
+ },
108
+ "DUSDIS": {
109
+ "122568": ("nrg-gsp-losses-gbp", "nrg-rate", "nrg-gsp-losses-kwh"),
110
+ },
111
+ "DUSTRN": {
112
+ "122568": ("nrg-nbp-losses-gbp", "nrg-rate", "nrg-bp-losses-kwh"),
113
+ },
114
+ },
115
+ "ELX": {
116
+ "ELEXON": {
117
+ "489920": ("elexon-gbp", "elexon-rate", "elexon-nbp-kwh"),
118
+ },
119
+ },
120
+ "FIT": {
121
+ "FIT_LV": {
122
+ "704107": ("fit-gbp", "fit-rate", "fit-kwh"),
123
+ },
124
+ },
125
+ "NRG": {
126
+ "HH0002": {
127
+ "033667": ("management-gbp", "management-rate", "management-kwh"),
128
+ "091890": ("shape-gbp", "shape-rate", "shape-kwh"),
129
+ "122568": ("nrg-msp-gbp", "nrg-rate", "nrg-msp-kwh"),
130
+ },
131
+ },
132
+ "REN": {
133
+ "REN001": {
134
+ "229128": ("ro-gbp", "ro-rate", "ro-kwh"),
135
+ },
136
+ "REN002": {
137
+ "019090": ("rego-gbp", "rego-rate", "rego-kwh"),
138
+ },
139
+ },
140
+ "TUS": {
141
+ "TNUOS": {
142
+ "012069": ("tnuos-gbp", "tnuos-rate", None),
143
+ },
144
+ },
34
145
  "064305": ("fit-gbp", None, None),
35
146
  "590346": ("cfd-operational-gbp", None, None),
36
- "269100": ("bsuos-gbp", "bsuos-rate", "bsuos-kwh"),
37
- "422733": ("ccl-gbp", "ccl-rate", "ccl-kwh"),
38
- "273237": ("cfd-operational-gbp", "cfd-operational-rate", "cfd-operational-kwh"),
39
- "954379": ("cfd-interim-gbp", "cfd-interim-rate", "cfd-interim-kwh"),
40
- "538249": ("capaity-gbp", None, None),
41
- "568307": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
42
147
  "439724": ("eii-gbp", None, None),
43
148
  "247610": ("eii-gbp", None, None),
44
149
  "930504": ("eii-gbp", None, None),
45
150
  "331201": ("eii-gbp", None, None),
46
151
  "307253": ("eii-gbp", "eii-rate", "eii-kwh"),
47
152
  "065950": ("eii-gbp", "eii-rate", "eii-kwh"),
48
- "095469": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
49
- "489920": ("elexon-gbp", "elexon-rate", "elexon-nbp-kwh"),
50
- "704107": ("fit-gbp", None, None),
51
- "019090": ("rego-gbp", "rego-rate", "rego-kwh"),
52
- "033667": ("management-gbp", "management-rate", "management-kwh"),
53
- "091890": ("shape-gbp", "shape-rate", "shape-kwh"),
54
- "122568": ("nrg-msp-gbp", "nrg-rate", "nrg-msp-kwh"),
55
- "716514": ("duos-amber-gbp", "duos-amber-rate", "duos-amber-kwh"),
56
- "769979": ("duos-red-gbp", "duos-red-rate", "duos-red-kwh"),
57
- "794486": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
58
- "797790": ("duos-reactive-gbp", "duos-reactive-rate", "duos-reactive-kvarh"),
59
- "709522": (
60
- "duos-excess-availability-gbp",
61
- "duos-excess-availability-rate",
62
- "duos-excess-availability-kva",
63
- ),
64
- "644819": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
65
- "806318": ("duos-green-gbp", "duos-green-rate", "duos-green-kwh"),
66
- "209269": ("tnuos-gbp", "tnuos-rate", "tnuos-days"),
67
- "229128": ("ro-gbp", "ro-rate", "ro-kwh"),
68
- "012069": ("tnuos-gbp", None, None),
69
153
  }
70
- """
71
- "140114": ("reconciliation-gbp", None, None),
72
- "255204": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
73
- "345065": ("op-weekend-gbp", "op-weekend-rate", "op-weekend-kwh"),
74
- "350293": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
75
- "425779": ("ro-gbp", "ro-rate", "ro-kwh"),
76
- "534342": ("reconciliation-gbp", None, None),
77
- "583174": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
78
- "584867": ("aahedc-gbp", "aahedc-rate", "aahedc-kwh"),
79
- "946827": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
80
- "989534": ("bsuos-gbp", "bsuos-rate", "bsuos-kwh"),
81
- "117220": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
82
- "579387": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
83
- "558147": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
84
- "030025": ("ccl-gbp", "ccl-rate", "ccl-kwh"),
85
- "066540": ("ccl-gbp", "ccl-rate", "ccl-kwh"),
86
- "154164": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
87
- "281170": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
88
- "342094": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
89
- "378809": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
90
- "574015": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
91
- "810016": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
92
- "839829": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
93
- "649282": ("cfd-fit-gbp", "cfd-fit-rate", "cfd-fit-kwh"),
94
- "068476": ("day-gbp", "day-rate", "day-kwh"),
95
- "133186": ("day-gbp", "day-rate", "day-kwh"),
96
- "400434": ("day-gbp", "day-rate", "day-kwh"),
97
- "219182": (
98
- "duos-availability-gbp",
99
- "duos-availability-rate",
100
- "duos-availability-kva",
101
- ),
102
- "144424": (
103
- "duos-excess-availability-gbp",
104
- "duos-excess-availability-rate",
105
- "duos-excess-availability-kva",
106
- ),
107
- "301541": ("duos-fixed-gbp", None, None),
108
- "099335": ("duos-fixed-gbp", None, None),
109
- "873562": ("duos-fixed-gbp", None, None),
110
- "986159": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
111
- "838286": ("duos-reactive-gbp", "duos-reactive-rate", "duos-reactive-kvarh"),
112
- "242643": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
113
- "257304": ("duos-amber-gbp", "duos-amber-rate", "duos-amber-kwh"),
114
- "661440": ("duos-amber-gbp", "duos-amber-rate", "duos-amber-kwh"),
115
- "257305": ("duos-green-gbp", "duos-green-rate", "duos-green-kwh"),
116
- "661441": ("duos-green-gbp", "duos-green-rate", "duos-green-kwh"),
117
- "257303": ("duos-red-gbp", "duos-red-rate", "duos-red-kwh"),
118
- "661439": ("duos-red-gbp", "duos-red-rate", "duos-red-kwh"),
119
- "504364": ("ebrs-gbp", None, "ebrs-kwh"),
120
- "563023": ("ebrs-gbp", None, "ebrs-kwh"),
121
- "823408": ("ebrs-gbp", None, "ebrs-kwh"),
122
- "871593": ("ebrs-gbp", "ebrs-rate", "ebrs-kwh"),
123
- "873894": ("ebrs-gbp", "ebrs-rate", "ebrs-kwh"),
124
- "309707": ("fit-gbp", "fit-rate", "fit-kwh"),
125
- "310129": ("meter-rental-gbp", None, None),
126
- "452415": ("meter-rental-gbp", None, None),
127
- "371265": ("meter-rental-gbp", None, None),
128
- "544936": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
129
- "265091": ("night-gbp", "night-rate", "night-kwh"),
130
- "483457": ("peak-gbp", "peak-rate", "peak-kwh"),
131
- "975901": ("peak-shoulder-gbp", "peak-shoulder-rate", "peak-shoulder-kwh"),
132
- "994483": ("reconciliation-gbp", None, None),
133
- "637176": ("reconciliation-gbp", None, None),
134
- "913821": ("reconciliation-gbp", None, None),
135
- "307660": ("ro-gbp", "ro-rate", "ro-kwh"),
136
- "364252": ("ro-gbp", "ro-rate", "ro-kwh"),
137
- "378246": ("ro-gbp", "ro-rate", "ro-kwh"),
138
- "708848": ("ro-gbp", None, None),
139
- "632209": ("summer-night-gbp", "summer-night-rate", "summer-night-kwh"),
140
- "663682": ("summer-weekday-gbp", "summer-weekday-rate", "summer-weekday-kwh"),
141
- "299992": ("summer-weekend-gbp", "summer-weekend-rate", "summer-weekend-kwh"),
142
- "211000": ("tnuos-gbp", "tnuos-rate", "tnuos-days"),
143
- "790618": ("tnuos-gbp", None, None),
144
- "447769": ("triad-gbp", "triad-rate", "triad-kw"),
145
- "647721": ("triad-gbp", "triad-rate", "triad-kw"),
146
- "276631": ("triad-gbp", "triad-rate", "triad-kw"),
147
- "220894": ("winter-night-gbp", "winter-night-rate", "winter-night-kwh"),
148
- "264929": ("winter-weekday-gbp", "winter-weekday-rate", "winter-weekday-kwh"),
149
- "638187": ("winter-weekend-gbp", "winter-weekend-rate", "winter-weekend-kwh"),
150
- "700285": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
151
- """
152
154
 
153
155
  TPR_LOOKUP = {
154
156
  "Day": "00043",
@@ -163,12 +165,37 @@ def _process_BCD(elements, headers):
163
165
  issue_date = to_date(elements["IVDT"][0])
164
166
  reference = elements["INVN"][0]
165
167
  bill_type_code = elements["BTCD"][0]
168
+ sumo = elements["SUMO"]
169
+ headers["start_date"] = to_date(sumo[0])
170
+ headers["finish_date"] = to_date(sumo[1]) - HH
166
171
 
167
172
  headers["issue_date"] = issue_date
168
173
  headers["bill_type_code"] = bill_type_code
169
174
  headers["reference"] = reference
170
175
 
171
176
 
177
+ def _process_BTL(elements, headers):
178
+ uvlt = elements["UVLT"]
179
+ utva = elements["UTVA"]
180
+ tbtl = elements["TBTL"]
181
+
182
+ return {
183
+ "mpan_core": headers["mpan_core"],
184
+ "account": headers["account"],
185
+ "bill_type_code": headers["bill_type_code"],
186
+ "reference": headers["reference"],
187
+ "issue_date": headers["issue_date"],
188
+ "start_date": headers["start_date"],
189
+ "finish_date": headers["finish_date"],
190
+ "kwh": headers["kwh"],
191
+ "net": Decimal("0.00") + to_decimal(uvlt) / Decimal("100"),
192
+ "vat": Decimal("0.00") + to_decimal(utva) / Decimal("100"),
193
+ "gross": Decimal("0.00") + to_decimal(tbtl) / Decimal("100"),
194
+ "breakdown": headers["breakdown"],
195
+ "reads": headers["reads"],
196
+ }
197
+
198
+
172
199
  def _process_CCD1(elements, headers):
173
200
  tcod = elements["TCOD"]
174
201
  pres_read_date = to_finish_date(elements["PRDT"][0])
@@ -227,12 +254,11 @@ def _process_CCD2(elements, headers):
227
254
  element_code = elements["TMOD"][0]
228
255
  headers["element_code"] = element_code
229
256
  try:
230
- eln_gbp, eln_rate, eln_cons = TMOD_MAP[element_code]
257
+ eln_gbp, eln_rate, eln_cons = ELEMENT_MAP[element_code]
231
258
  except KeyError:
232
- raise BadRequest(f"Can't find the element code {element_code} in the TMOD_MAP.")
233
-
234
- m = elements["MLOC"][0]
235
- mpan_core = " ".join((m[:2], m[2:6], m[6:10], m[10:]))
259
+ raise BadRequest(
260
+ f"Can't find the element code {element_code} in the ELEMENT_MAP."
261
+ )
236
262
 
237
263
  cons = elements["CONS"]
238
264
  kwh = Decimal("0")
@@ -259,8 +285,7 @@ def _process_CCD2(elements, headers):
259
285
  net = Decimal("0.00")
260
286
 
261
287
  breakdown[eln_gbp] = net
262
-
263
- headers["mpan_core"] = mpan_core
288
+ breakdown["raw-lines"] = [headers["line"]]
264
289
 
265
290
  try:
266
291
  reads = headers["reads"]
@@ -268,12 +293,10 @@ def _process_CCD2(elements, headers):
268
293
  except KeyError:
269
294
  reads = []
270
295
 
271
- return {
296
+ bill = {
272
297
  "bill_type_code": headers["bill_type_code"],
273
298
  "reference": headers["reference"] + "_" + eln_gbp[:-4],
274
299
  "issue_date": headers["issue_date"],
275
- "mpan_core": mpan_core,
276
- "account": mpan_core,
277
300
  "start_date": start_date,
278
301
  "finish_date": finish_date,
279
302
  "kwh": kwh if eln_gbp == "ro-gbp" else Decimal("0"),
@@ -283,83 +306,59 @@ def _process_CCD2(elements, headers):
283
306
  "breakdown": breakdown,
284
307
  "reads": reads,
285
308
  }
309
+ headers["bills"].append(bill)
286
310
 
287
311
 
288
312
  def _process_CCD3(elements, headers):
289
- breakdown = defaultdict(int)
313
+ breakdown = headers["breakdown"]
290
314
 
291
- element_code = elements["TMOD"][0]
292
- headers["element_code"] = element_code
315
+ supplier_code = elements["CCDE"][2]
316
+ tariff_code = elements["TCOD"][0]
317
+ mod_code = elements["TMOD"][0]
293
318
  try:
294
- eln_gbp, eln_rate, eln_cons = TMOD_MAP[element_code]
319
+ eln_gbp, eln_rate, eln_cons = ELEMENT_MAP[supplier_code][tariff_code][mod_code]
295
320
  except KeyError:
296
- raise BadRequest(f"Can't find the element code {element_code} in the TMOD_MAP.")
297
-
298
- m = elements["MLOC"][0]
299
- mpan_core = " ".join((m[:2], m[2:6], m[6:10], m[10:]))
321
+ raise BadRequest(
322
+ f"Can't find the element key {supplier_code} -> {tariff_code} -> "
323
+ f"{mod_code} in the ELEMENT_MAP."
324
+ )
300
325
 
301
326
  cons = elements["CONS"]
302
- if eln_cons is not None and len(cons[0]) > 0:
327
+ kwh = Decimal("0")
328
+ if len(cons[0]) > 0:
303
329
  el_cons = to_decimal(cons) / Decimal("1000")
304
- breakdown[eln_cons] = kwh = el_cons
305
- else:
306
- kwh = Decimal("0")
307
-
308
- if eln_rate is not None:
309
- rate = to_decimal(elements["BPRI"]) / Decimal("100000")
310
- breakdown[eln_rate] = [rate]
311
-
312
- start_date = to_date(elements["CSDT"][0])
313
- headers["bill_start_date"] = start_date
314
-
315
- finish_date = to_date(elements["CEDT"][0]) - HH
316
- headers["bill_finish_date"] = finish_date
330
+ kwh = el_cons
331
+ breakdown[eln_cons] += kwh
332
+
333
+ bpri = elements["BPRI"]
334
+ if len(bpri[0]) > 0:
335
+ rate = to_decimal(bpri) / Decimal("100000")
336
+ if eln_rate in breakdown:
337
+ breakdown[eln_rate].add(rate)
338
+ else:
339
+ breakdown[eln_rate] = {rate}
317
340
 
318
341
  if "CTOT" in elements:
319
342
  net = Decimal("0.00") + to_decimal(elements["CTOT"]) / Decimal("100")
320
343
  else:
321
344
  net = Decimal("0.00")
322
345
 
323
- breakdown[eln_gbp] = net
324
-
325
- headers["mpan_core"] = mpan_core
326
-
327
- try:
328
- reads = headers["reads"]
329
- headers["reads"] = []
330
- except KeyError:
331
- reads = []
346
+ breakdown[eln_gbp] += net
332
347
 
333
- return {
334
- "bill_type_code": headers["bill_type_code"],
335
- "issue_date": headers["issue_date"],
336
- "reference": headers["reference"] + "_" + eln_gbp[:-4],
337
- "mpan_core": mpan_core,
338
- "account": mpan_core,
339
- "start_date": start_date,
340
- "finish_date": finish_date,
341
- "net": net,
342
- "kwh": kwh if eln_gbp == "ro-gbp" else Decimal("0"),
343
- "vat": Decimal("0.00"),
344
- "gross": net,
345
- "breakdown": breakdown,
346
- "reads": reads,
347
- }
348
+ if eln_gbp == "nrg-msp-gbp":
349
+ headers["kwh"] += kwh
348
350
 
349
351
 
350
352
  def _process_CCD4(elements, headers):
351
- breakdown = defaultdict(int)
353
+ breakdown = headers["breakdown"]
352
354
 
353
355
  element_code = elements["TMOD"][0]
354
356
  headers["element_code"] = element_code
355
357
  try:
356
- eln_gbp, eln_rate, eln_cons = TMOD_MAP[element_code]
358
+ eln_gbp, eln_rate, eln_cons = ELEMENT_MAP[element_code]
357
359
  except KeyError:
358
360
  raise BadRequest(f"Can't find the element code {element_code} in the TMOD_MAP.")
359
361
 
360
- m = elements["MLOC"][0]
361
- mpan_core = " ".join((m[:2], m[2:6], m[6:10], m[10:]))
362
-
363
362
  cons = elements["CONS"]
364
363
  if eln_cons is not None and len(cons[0]) > 0:
365
364
  el_cons = to_decimal(cons, "1000")
@@ -381,8 +380,7 @@ def _process_CCD4(elements, headers):
381
380
  net = Decimal("0.00")
382
381
 
383
382
  breakdown[eln_gbp] = net
384
-
385
- headers["mpan_core"] = mpan_core
383
+ breakdown["raw-lines"] = [headers["line"]]
386
384
 
387
385
  try:
388
386
  reads = headers["reads"]
@@ -390,12 +388,10 @@ def _process_CCD4(elements, headers):
390
388
  except KeyError:
391
389
  reads = []
392
390
 
393
- return {
391
+ bill = {
394
392
  "kwh": kwh if eln_gbp == "ro-gbp" else Decimal("0.00"),
395
393
  "reference": headers["reference"] + "_" + eln_gbp[:-4],
396
394
  "issue_date": headers["issue_date"],
397
- "mpan_core": mpan_core,
398
- "account": mpan_core,
399
395
  "start_date": start_date,
400
396
  "finish_date": finish_date,
401
397
  "net": net,
@@ -405,6 +401,7 @@ def _process_CCD4(elements, headers):
405
401
  "reads": reads,
406
402
  "bill_type_code": headers["bill_type_code"],
407
403
  }
404
+ headers["bills"].append(bill)
408
405
 
409
406
 
410
407
  def _process_CDT(elements, headers):
@@ -412,21 +409,39 @@ def _process_CDT(elements, headers):
412
409
  headers["customer_number"] = customer_id
413
410
 
414
411
 
412
+ def _process_CLO(elements, headers):
413
+ cloc = elements["CLOC"]
414
+ headers["account"] = cloc[1]
415
+
416
+
415
417
  def _process_END(elements, headers):
416
418
  pass
417
419
 
418
420
 
421
+ def _process_MAN(elements, headers):
422
+ madn = elements["MADN"]
423
+
424
+ headers["mpan_core"] = parse_mpan_core("".join(madn[0:3]))
425
+
426
+
419
427
  def _process_MHD(elements, headers):
420
428
  message_type = elements["TYPE"][0]
421
429
  if message_type == "UTLBIL":
430
+ last_line = headers["lines"][-1]
422
431
  keep_keys = {"customer_number"}
423
432
  keep = {k: headers[k] for k in keep_keys}
424
433
  headers.clear()
425
434
  headers.update(keep)
435
+ headers["lines"] = [last_line]
436
+ headers["breakdown"] = defaultdict(
437
+ int, {"vat": {}, "raw-lines": headers["lines"]}
438
+ )
439
+ headers["kwh"] = Decimal(0)
440
+ headers["reads"] = []
426
441
 
427
442
 
428
443
  def _process_MTR(elements, headers):
429
- pass
444
+ headers["lines"] = []
430
445
 
431
446
 
432
447
  def _process_VAT(elements, headers):
@@ -434,21 +449,7 @@ def _process_VAT(elements, headers):
434
449
  vat_percentage = to_decimal(elements["VATP"]) / Decimal("1000")
435
450
  vat_net = Decimal("0.00") + to_decimal(elements["UVLA"]) / Decimal("100")
436
451
 
437
- return {
438
- "bill_type_code": headers["bill_type_code"],
439
- "account": headers["mpan_core"],
440
- "mpan_core": headers["mpan_core"],
441
- "reference": headers["reference"] + "_vat",
442
- "issue_date": headers["issue_date"],
443
- "start_date": headers["bill_start_date"],
444
- "finish_date": headers["bill_finish_date"],
445
- "kwh": Decimal("0.00"),
446
- "net": Decimal("0.00"),
447
- "vat": vat,
448
- "gross": vat,
449
- "breakdown": {"vat": {vat_percentage: {"vat": vat, "net": vat_net}}},
450
- "reads": [],
451
- }
452
+ headers["breakdown"]["vat"][vat_percentage] = {"vat": vat, "net": vat_net}
452
453
 
453
454
 
454
455
  def _process_NOOP(elements, headers):
@@ -457,17 +458,17 @@ def _process_NOOP(elements, headers):
457
458
 
458
459
  CODE_FUNCS = {
459
460
  "BCD": _process_BCD,
460
- "BTL": _process_NOOP,
461
+ "BTL": _process_BTL,
461
462
  "CCD1": _process_CCD1,
462
463
  "CCD2": _process_CCD2,
463
464
  "CCD3": _process_CCD3,
464
465
  "CCD4": _process_CCD4,
465
466
  "CDT": _process_CDT,
466
- "CLO": _process_NOOP,
467
+ "CLO": _process_CLO,
467
468
  "DNA": _process_NOOP,
468
469
  "END": _process_END,
469
470
  "FIL": _process_NOOP,
470
- "MAN": _process_NOOP,
471
+ "MAN": _process_MAN,
471
472
  "MHD": _process_MHD,
472
473
  "MTR": _process_MTR,
473
474
  "SDT": _process_NOOP,
@@ -479,21 +480,6 @@ CODE_FUNCS = {
479
480
  }
480
481
 
481
482
 
482
- def _customer_mods(headers, bill):
483
- if headers["customer_number"] == "WESSEXWAT":
484
- if (
485
- headers["element_code"] == "307660"
486
- and "ro-gbp" in bill["breakdown"]
487
- and bill["issue_date"] == to_utc(ct_datetime(2023, 4, 14))
488
- and bill["start_date"] == to_utc(ct_datetime(2023, 3, 1))
489
- and bill["finish_date"] == to_utc(ct_datetime(2023, 3, 31, 23, 30))
490
- ):
491
- bill["start_date"] = to_utc(ct_datetime(2021, 4, 1))
492
- bill["finish_date"] = to_utc(ct_datetime(2022, 3, 31, 23, 30))
493
-
494
- return bill
495
-
496
-
497
483
  class Parser:
498
484
  def __init__(self, f):
499
485
  self.edi_str = str(f.read(), "utf-8", errors="ignore")
@@ -501,9 +487,9 @@ class Parser:
501
487
 
502
488
  def make_raw_bills(self):
503
489
  bills = []
504
- headers = {}
505
- bill = None
490
+ headers = {"lines": []}
506
491
  for self.line_number, line, seg_name, elements in parse_edi(self.edi_str):
492
+ headers["lines"].append(line)
507
493
  try:
508
494
  func = CODE_FUNCS[seg_name]
509
495
  except KeyError:
@@ -523,7 +509,6 @@ class Parser:
523
509
  ) from e
524
510
 
525
511
  if bill is not None:
526
- bill["breakdown"]["raw-lines"] = [line]
527
- bills.append(_customer_mods(headers, bill))
512
+ bills.append(bill)
528
513
 
529
514
  return bills
@@ -0,0 +1,459 @@
1
+ from collections import defaultdict
2
+ from decimal import Decimal
3
+
4
+
5
+ from werkzeug.exceptions import BadRequest
6
+
7
+ from chellow.edi_lib import (
8
+ ct_datetime,
9
+ parse_edi,
10
+ to_date,
11
+ to_decimal,
12
+ to_finish_date,
13
+ to_utc,
14
+ )
15
+ from chellow.utils import HH, parse_mpan_core
16
+
17
+
18
+ read_type_map = {
19
+ "00": "N",
20
+ "09": "N3",
21
+ "04": "C",
22
+ "02": "E",
23
+ "11": "E3",
24
+ "01": "EM",
25
+ "03": "W",
26
+ "06": "X",
27
+ "05": "CP",
28
+ "12": "IF",
29
+ }
30
+
31
+
32
+ TMOD_MAP = {
33
+ "139039": ("aahedc-gbp", "aahedc-rate", "aahedc-kwh"),
34
+ "064305": ("fit-gbp", None, None),
35
+ "590346": ("cfd-operational-gbp", None, None),
36
+ "269100": ("bsuos-gbp", "bsuos-rate", "bsuos-kwh"),
37
+ "422733": ("ccl-gbp", "ccl-rate", "ccl-kwh"),
38
+ "273237": ("cfd-operational-gbp", "cfd-operational-rate", "cfd-operational-kwh"),
39
+ "954379": ("cfd-interim-gbp", "cfd-interim-rate", "cfd-interim-kwh"),
40
+ "538249": ("capaity-gbp", None, None),
41
+ "568307": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
42
+ "020330": ("eii-gbp", None, None),
43
+ "439724": ("eii-gbp", None, None),
44
+ "247610": ("eii-gbp", None, None),
45
+ "930504": ("eii-gbp", None, None),
46
+ "331201": ("eii-gbp", None, None),
47
+ "307253": ("eii-gbp", "eii-rate", "eii-kwh"),
48
+ "065950": ("eii-gbp", "eii-rate", "eii-kwh"),
49
+ "095469": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
50
+ "637050": ("meter-rental-gbp", "meter-rental-rate", "meter-rental-days"),
51
+ "489920": ("elexon-gbp", "elexon-rate", "elexon-nbp-kwh"),
52
+ "704107": ("fit-gbp", None, None),
53
+ "019090": ("rego-gbp", "rego-rate", "rego-kwh"),
54
+ "033667": ("management-gbp", "management-rate", "management-kwh"),
55
+ "091890": ("shape-gbp", "shape-rate", "shape-kwh"),
56
+ "122568": ("nrg-msp-gbp", "nrg-rate", "nrg-msp-kwh"),
57
+ "716514": ("duos-amber-gbp", "duos-amber-rate", "duos-amber-kwh"),
58
+ "769979": ("duos-red-gbp", "duos-red-rate", "duos-red-kwh"),
59
+ "794486": ("capacity-gbp", "capacity-rate", "capacity-kwh"),
60
+ "797790": ("duos-reactive-gbp", "duos-reactive-rate", "duos-reactive-kvarh"),
61
+ "709522": (
62
+ "duos-excess-availability-gbp",
63
+ "duos-excess-availability-rate",
64
+ "duos-excess-availability-kva",
65
+ ),
66
+ "644819": ("duos-fixed-gbp", "duos-fixed-rate", "duos-fixed-days"),
67
+ "806318": ("duos-green-gbp", "duos-green-rate", "duos-green-kwh"),
68
+ "209269": ("tnuos-gbp", "tnuos-rate", "tnuos-days"),
69
+ "229128": ("ro-gbp", "ro-rate", "ro-kwh"),
70
+ "012069": ("tnuos-gbp", None, None),
71
+ }
72
+
73
+ TPR_LOOKUP = {
74
+ "Day": "00043",
75
+ "Off Peak / Weekends": "00210",
76
+ "Night": "00210",
77
+ "Default Rate": "00043",
78
+ "Single": "00210",
79
+ }
80
+
81
+
82
+ def _process_BCD(elements, headers):
83
+ issue_date = to_date(elements["IVDT"][0])
84
+ reference = elements["INVN"][0]
85
+ bill_type_code = elements["BTCD"][0]
86
+
87
+ headers["issue_date"] = issue_date
88
+ headers["bill_type_code"] = bill_type_code
89
+ headers["reference"] = reference
90
+
91
+
92
+ def _process_BTL(elements, headers):
93
+ for bill in headers["bills"]:
94
+ bill["mpan_core"] = headers["mpan_core"]
95
+ bill["account"] = headers["account"]
96
+ _customer_mods(headers, bill)
97
+ return headers["bills"]
98
+
99
+
100
+ def _process_CCD1(elements, headers):
101
+ tcod = elements["TCOD"]
102
+ pres_read_date = to_finish_date(elements["PRDT"][0])
103
+
104
+ prev_read_date = to_finish_date(elements["PVDT"][0])
105
+
106
+ m = elements["MLOC"][0]
107
+ mpan = " ".join((m[13:15], m[15:18], m[18:], m[:2], m[2:6], m[6:10], m[10:13]))
108
+
109
+ prrd = elements["PRRD"]
110
+ if len(prrd) < 4:
111
+ return
112
+ pres_read_type = read_type_map[prrd[1]]
113
+ prev_read_type = read_type_map[prrd[3]]
114
+
115
+ coefficient = Decimal(elements["ADJF"][1]) / Decimal(100000)
116
+ pres_reading_value = Decimal(prrd[0])
117
+ prev_reading_value = Decimal(prrd[2])
118
+ msn = elements["MTNR"][0]
119
+ tpr_code = elements["TMOD"][0]
120
+ if tpr_code == "kW":
121
+ units = "kW"
122
+ tpr_code = None
123
+ elif tpr_code == "kVA":
124
+ units = "kVA"
125
+ tpr_code = None
126
+ else:
127
+ units = "kWh"
128
+ tpr_code = TPR_LOOKUP[tcod[1]]
129
+
130
+ try:
131
+ reads = headers["reads"]
132
+ except KeyError:
133
+ reads = headers["reads"] = []
134
+
135
+ reads.append(
136
+ {
137
+ "msn": msn,
138
+ "mpan": mpan,
139
+ "coefficient": coefficient,
140
+ "units": units,
141
+ "tpr_code": tpr_code,
142
+ "prev_date": prev_read_date,
143
+ "prev_value": prev_reading_value,
144
+ "prev_type_code": prev_read_type,
145
+ "pres_date": pres_read_date,
146
+ "pres_value": pres_reading_value,
147
+ "pres_type_code": pres_read_type,
148
+ }
149
+ )
150
+
151
+
152
+ def _process_CCD2(elements, headers):
153
+ breakdown = defaultdict(int)
154
+
155
+ element_code = elements["TMOD"][0]
156
+ headers["element_code"] = element_code
157
+ try:
158
+ eln_gbp, eln_rate, eln_cons = TMOD_MAP[element_code]
159
+ except KeyError:
160
+ raise BadRequest(f"Can't find the element code {element_code} in the TMOD_MAP.")
161
+
162
+ cons = elements["CONS"]
163
+ kwh = Decimal("0")
164
+ if eln_cons is not None and len(cons[0]) > 0:
165
+ el_cons = to_decimal(cons) / Decimal("1000")
166
+ if eln_gbp == "duos-availability-gbp":
167
+ breakdown[eln_cons] = [el_cons]
168
+ else:
169
+ breakdown[eln_cons] = kwh = el_cons
170
+
171
+ if eln_rate is not None:
172
+ rate = to_decimal(elements["BPRI"]) / Decimal("100000")
173
+ breakdown[eln_rate] = [rate]
174
+
175
+ start_date = to_date(elements["CSDT"][0])
176
+ headers["bill_start_date"] = start_date
177
+
178
+ finish_date = to_date(elements["CEDT"][0]) - HH
179
+ headers["bill_finish_date"] = finish_date
180
+
181
+ if "CTOT" in elements:
182
+ net = Decimal("0.00") + to_decimal(elements["CTOT"]) / Decimal("100")
183
+ else:
184
+ net = Decimal("0.00")
185
+
186
+ breakdown[eln_gbp] = net
187
+ breakdown["raw-lines"] = [headers["line"]]
188
+
189
+ try:
190
+ reads = headers["reads"]
191
+ headers["reads"] = []
192
+ except KeyError:
193
+ reads = []
194
+
195
+ bill = {
196
+ "bill_type_code": headers["bill_type_code"],
197
+ "reference": headers["reference"] + "_" + eln_gbp[:-4],
198
+ "issue_date": headers["issue_date"],
199
+ "start_date": start_date,
200
+ "finish_date": finish_date,
201
+ "kwh": kwh if eln_gbp == "ro-gbp" else Decimal("0"),
202
+ "net": net,
203
+ "vat": Decimal("0.00"),
204
+ "gross": net,
205
+ "breakdown": breakdown,
206
+ "reads": reads,
207
+ }
208
+ headers["bills"].append(bill)
209
+
210
+
211
+ def _process_CCD3(elements, headers):
212
+ breakdown = defaultdict(int)
213
+
214
+ element_code = elements["TMOD"][0]
215
+ headers["element_code"] = element_code
216
+ try:
217
+ eln_gbp, eln_rate, eln_cons = TMOD_MAP[element_code]
218
+ except KeyError:
219
+ raise BadRequest(f"Can't find the element code {element_code} in the TMOD_MAP.")
220
+
221
+ cons = elements["CONS"]
222
+ if eln_cons is not None and len(cons[0]) > 0:
223
+ el_cons = to_decimal(cons) / Decimal("1000")
224
+ breakdown[eln_cons] = kwh = el_cons
225
+ else:
226
+ kwh = Decimal("0")
227
+
228
+ bpri = elements["BPRI"]
229
+ if len(bpri[0]) > 0:
230
+ rate = to_decimal(bpri) / Decimal("100000")
231
+ breakdown[eln_rate] = [rate]
232
+
233
+ start_date = to_date(elements["CSDT"][0])
234
+ headers["bill_start_date"] = start_date
235
+
236
+ finish_date = to_date(elements["CEDT"][0]) - HH
237
+ headers["bill_finish_date"] = finish_date
238
+
239
+ if "CTOT" in elements:
240
+ net = Decimal("0.00") + to_decimal(elements["CTOT"]) / Decimal("100")
241
+ else:
242
+ net = Decimal("0.00")
243
+
244
+ breakdown[eln_gbp] = net
245
+ breakdown["raw-lines"] = [headers["line"]]
246
+
247
+ try:
248
+ reads = headers["reads"]
249
+ headers["reads"] = []
250
+ except KeyError:
251
+ reads = []
252
+
253
+ bill = {
254
+ "bill_type_code": headers["bill_type_code"],
255
+ "issue_date": headers["issue_date"],
256
+ "reference": headers["reference"] + "_" + eln_gbp[:-4],
257
+ "start_date": start_date,
258
+ "finish_date": finish_date,
259
+ "net": net,
260
+ "kwh": kwh if eln_gbp == "ro-gbp" else Decimal("0"),
261
+ "vat": Decimal("0.00"),
262
+ "gross": net,
263
+ "breakdown": breakdown,
264
+ "reads": reads,
265
+ }
266
+
267
+ headers["bills"].append(bill)
268
+
269
+
270
+ def _process_CCD4(elements, headers):
271
+ breakdown = defaultdict(int)
272
+
273
+ element_code = elements["TMOD"][0]
274
+ headers["element_code"] = element_code
275
+ try:
276
+ eln_gbp, eln_rate, eln_cons = TMOD_MAP[element_code]
277
+ except KeyError:
278
+ raise BadRequest(f"Can't find the element code {element_code} in the TMOD_MAP.")
279
+
280
+ cons = elements["CONS"]
281
+ if eln_cons is not None and len(cons[0]) > 0:
282
+ el_cons = to_decimal(cons, "1000")
283
+ breakdown[eln_cons] = kwh = el_cons
284
+
285
+ if eln_rate is not None:
286
+ rate = to_decimal(elements["BPRI"], "100000")
287
+ breakdown[eln_rate] = [rate]
288
+
289
+ start_date = to_date(elements["CSDT"][0])
290
+ headers["bill_start_date"] = start_date
291
+
292
+ finish_date = to_date(elements["CEDT"][0]) - HH
293
+ headers["bill_finish_date"] = finish_date
294
+
295
+ if "CTOT" in elements:
296
+ net = Decimal("0.00") + to_decimal(elements["CTOT"], "100")
297
+ else:
298
+ net = Decimal("0.00")
299
+
300
+ breakdown[eln_gbp] = net
301
+ breakdown["raw-lines"] = [headers["line"]]
302
+
303
+ try:
304
+ reads = headers["reads"]
305
+ del headers["reads"][:]
306
+ except KeyError:
307
+ reads = []
308
+
309
+ bill = {
310
+ "kwh": kwh if eln_gbp == "ro-gbp" else Decimal("0.00"),
311
+ "reference": headers["reference"] + "_" + eln_gbp[:-4],
312
+ "issue_date": headers["issue_date"],
313
+ "start_date": start_date,
314
+ "finish_date": finish_date,
315
+ "net": net,
316
+ "vat": Decimal("0.00"),
317
+ "gross": net,
318
+ "breakdown": breakdown,
319
+ "reads": reads,
320
+ "bill_type_code": headers["bill_type_code"],
321
+ }
322
+ headers["bills"].append(bill)
323
+
324
+
325
+ def _process_CDT(elements, headers):
326
+ customer_id = elements["CIDN"][0]
327
+ headers["customer_number"] = customer_id
328
+
329
+
330
+ def _process_CLO(elements, headers):
331
+ cloc = elements["CLOC"]
332
+ headers["account"] = cloc[1]
333
+
334
+
335
+ def _process_END(elements, headers):
336
+ pass
337
+
338
+
339
+ def _process_MAN(elements, headers):
340
+ madn = elements["MADN"]
341
+
342
+ headers["mpan_core"] = parse_mpan_core("".join(madn[0:3]))
343
+
344
+
345
+ def _process_MHD(elements, headers):
346
+ message_type = elements["TYPE"][0]
347
+ if message_type == "UTLBIL":
348
+ keep_keys = {"customer_number"}
349
+ keep = {k: headers[k] for k in keep_keys}
350
+ headers.clear()
351
+ headers.update(keep)
352
+ headers["bills"] = []
353
+
354
+
355
+ def _process_MTR(elements, headers):
356
+ pass
357
+
358
+
359
+ def _process_VAT(elements, headers):
360
+ vat = Decimal("0.00") + to_decimal(elements["UVTT"]) / Decimal("100")
361
+ vat_percentage = to_decimal(elements["VATP"]) / Decimal("1000")
362
+ vat_net = Decimal("0.00") + to_decimal(elements["UVLA"]) / Decimal("100")
363
+
364
+ bill = {
365
+ "bill_type_code": headers["bill_type_code"],
366
+ "account": headers["account"],
367
+ "mpan_core": headers["mpan_core"],
368
+ "reference": headers["reference"] + "_vat",
369
+ "issue_date": headers["issue_date"],
370
+ "start_date": headers["bill_start_date"],
371
+ "finish_date": headers["bill_finish_date"],
372
+ "kwh": Decimal("0.00"),
373
+ "net": Decimal("0.00"),
374
+ "vat": vat,
375
+ "gross": vat,
376
+ "breakdown": {
377
+ "raw-lines": [headers["line"]],
378
+ "vat": {vat_percentage: {"vat": vat, "net": vat_net}},
379
+ },
380
+ "reads": [],
381
+ }
382
+ headers["bills"].append(bill)
383
+
384
+
385
+ def _process_NOOP(elements, headers):
386
+ pass
387
+
388
+
389
+ CODE_FUNCS = {
390
+ "BCD": _process_BCD,
391
+ "BTL": _process_BTL,
392
+ "CCD1": _process_CCD1,
393
+ "CCD2": _process_CCD2,
394
+ "CCD3": _process_CCD3,
395
+ "CCD4": _process_CCD4,
396
+ "CDT": _process_CDT,
397
+ "CLO": _process_CLO,
398
+ "DNA": _process_NOOP,
399
+ "END": _process_END,
400
+ "FIL": _process_NOOP,
401
+ "MAN": _process_MAN,
402
+ "MHD": _process_MHD,
403
+ "MTR": _process_MTR,
404
+ "SDT": _process_NOOP,
405
+ "STX": _process_NOOP,
406
+ "TYP": _process_NOOP,
407
+ "TTL": _process_NOOP,
408
+ "VAT": _process_VAT,
409
+ "VTS": _process_NOOP,
410
+ }
411
+
412
+
413
+ def _customer_mods(headers, bill):
414
+ if headers["customer_number"] == "WESSEXWAT":
415
+ if (
416
+ headers["element_code"] == "307660"
417
+ and "ro-gbp" in bill["breakdown"]
418
+ and bill["issue_date"] == to_utc(ct_datetime(2023, 4, 14))
419
+ and bill["start_date"] == to_utc(ct_datetime(2023, 3, 1))
420
+ and bill["finish_date"] == to_utc(ct_datetime(2023, 3, 31, 23, 30))
421
+ ):
422
+ bill["start_date"] = to_utc(ct_datetime(2021, 4, 1))
423
+ bill["finish_date"] = to_utc(ct_datetime(2022, 3, 31, 23, 30))
424
+
425
+ return bill
426
+
427
+
428
+ class Parser:
429
+ def __init__(self, f):
430
+ self.edi_str = str(f.read(), "utf-8", errors="ignore")
431
+ self.line_number = None
432
+
433
+ def make_raw_bills(self):
434
+ bills = []
435
+ headers = {"bills": []}
436
+ for self.line_number, line, seg_name, elements in parse_edi(self.edi_str):
437
+ headers["line"] = line
438
+ try:
439
+ func = CODE_FUNCS[seg_name]
440
+ except KeyError:
441
+ raise BadRequest(f"Code {seg_name} not recognized.")
442
+
443
+ try:
444
+ bills_chunk = func(elements, headers)
445
+ except BadRequest as e:
446
+ raise BadRequest(
447
+ f"{e.description} on line {self.line_number} line {line} "
448
+ f"seg_name {seg_name} elements {elements}"
449
+ )
450
+ except BaseException as e:
451
+ raise BadRequest(
452
+ f"{e} on line {self.line_number} line {line} "
453
+ f"seg_name {seg_name} elements {elements}"
454
+ ) from e
455
+
456
+ if bills_chunk is not None:
457
+ bills.extend(bills_chunk)
458
+
459
+ return bills
chellow/e/views.py CHANGED
@@ -28,7 +28,7 @@ from sqlalchemy.orm import aliased, joinedload
28
28
 
29
29
  from werkzeug.exceptions import BadRequest
30
30
 
31
- from zish import dumps, loads
31
+ from zish import ZishException, dumps, loads
32
32
 
33
33
  import chellow.e.dno_rate_parser
34
34
  import chellow.e.lcc
@@ -4910,62 +4910,83 @@ def supplier_batches_get():
4910
4910
  @e.route("/supplier_batches/<int:batch_id>")
4911
4911
  def supplier_batch_get(batch_id):
4912
4912
  batch = Batch.get_by_id(g.sess, batch_id)
4913
-
4914
4913
  num_bills = sum_net_gbp = sum_vat_gbp = sum_gross_gbp = sum_kwh = 0
4915
4914
  vat_breakdown = {}
4916
- bills = (
4917
- g.sess.execute(
4918
- select(Bill)
4919
- .where(Bill.batch == batch)
4920
- .order_by(Bill.reference)
4921
- .options(joinedload(Bill.bill_type))
4922
- )
4923
- .scalars()
4924
- .all()
4925
- )
4926
- for bill in bills:
4927
- num_bills += 1
4928
- sum_net_gbp += bill.net
4929
- sum_vat_gbp += bill.vat
4930
- sum_gross_gbp += bill.gross
4931
- sum_kwh += bill.kwh
4932
4915
 
4933
- bd = bill.bd
4934
- if "vat" in bd:
4935
- for vat_percentage, vat_vals in bd["vat"].items():
4936
- try:
4937
- vbd = vat_breakdown[vat_percentage]
4938
- except KeyError:
4939
- vbd = vat_breakdown[vat_percentage] = defaultdict(int)
4916
+ try:
4940
4917
 
4941
- vbd["vat"] += vat_vals["vat"]
4942
- vbd["net"] += vat_vals["net"]
4918
+ bills = (
4919
+ g.sess.execute(
4920
+ select(Bill)
4921
+ .where(Bill.batch == batch)
4922
+ .order_by(Bill.reference)
4923
+ .options(joinedload(Bill.bill_type))
4924
+ )
4925
+ .scalars()
4926
+ .all()
4927
+ )
4928
+ for bill in bills:
4929
+ num_bills += 1
4930
+ sum_net_gbp += bill.net
4931
+ sum_vat_gbp += bill.vat
4932
+ sum_gross_gbp += bill.gross
4933
+ sum_kwh += bill.kwh
4943
4934
 
4944
- config_contract = Contract.get_non_core_by_name(g.sess, "configuration")
4945
- properties = config_contract.make_properties()
4946
- if "batch_reports" in properties:
4947
- batch_reports = []
4948
- for report_id in properties["batch_reports"]:
4949
- batch_reports.append(Report.get_by_id(g.sess, report_id))
4950
- else:
4951
- batch_reports = None
4935
+ try:
4936
+ bd = bill.bd
4952
4937
 
4953
- importer_ids = sorted(
4954
- chellow.e.bill_importer.get_bill_import_ids(batch), reverse=True
4955
- )
4956
- return render_template(
4957
- "supplier_batch.html",
4958
- batch=batch,
4959
- bills=bills,
4960
- batch_reports=batch_reports,
4961
- num_bills=num_bills,
4962
- sum_net_gbp=sum_net_gbp,
4963
- sum_vat_gbp=sum_vat_gbp,
4964
- sum_gross_gbp=sum_gross_gbp,
4965
- sum_kwh=sum_kwh,
4966
- vat_breakdown=vat_breakdown,
4967
- importer_ids=importer_ids,
4968
- )
4938
+ if "vat" in bd:
4939
+ for vat_percentage, vat_vals in bd["vat"].items():
4940
+ try:
4941
+ vbd = vat_breakdown[vat_percentage]
4942
+ except KeyError:
4943
+ vbd = vat_breakdown[vat_percentage] = defaultdict(int)
4944
+
4945
+ vbd["vat"] += vat_vals["vat"]
4946
+ vbd["net"] += vat_vals["net"]
4947
+ except ZishException as e:
4948
+ raise BadRequest(f"Problem with bill {bill.id}") from e
4949
+
4950
+ config_contract = Contract.get_non_core_by_name(g.sess, "configuration")
4951
+ properties = config_contract.make_properties()
4952
+ if "batch_reports" in properties:
4953
+ batch_reports = []
4954
+ for report_id in properties["batch_reports"]:
4955
+ batch_reports.append(Report.get_by_id(g.sess, report_id))
4956
+ else:
4957
+ batch_reports = None
4958
+
4959
+ importer_ids = sorted(
4960
+ chellow.e.bill_importer.get_bill_import_ids(batch), reverse=True
4961
+ )
4962
+ return render_template(
4963
+ "supplier_batch.html",
4964
+ batch=batch,
4965
+ bills=bills,
4966
+ batch_reports=batch_reports,
4967
+ num_bills=num_bills,
4968
+ sum_net_gbp=sum_net_gbp,
4969
+ sum_vat_gbp=sum_vat_gbp,
4970
+ sum_gross_gbp=sum_gross_gbp,
4971
+ sum_kwh=sum_kwh,
4972
+ vat_breakdown=vat_breakdown,
4973
+ importer_ids=importer_ids,
4974
+ )
4975
+ except BadRequest as e:
4976
+ flash(e.description)
4977
+ return make_response(
4978
+ render_template(
4979
+ "supplier_batch.html",
4980
+ batch=batch,
4981
+ num_bills=num_bills,
4982
+ sum_net_gbp=sum_net_gbp,
4983
+ sum_vat_gbp=sum_vat_gbp,
4984
+ vat_breakdown=vat_breakdown,
4985
+ sum_gross_gbp=sum_gross_gbp,
4986
+ sum_kwh=sum_kwh,
4987
+ ),
4988
+ 400,
4989
+ )
4969
4990
 
4970
4991
 
4971
4992
  @e.route("/supplier_batches/<int:batch_id>/edit")
@@ -18,6 +18,7 @@ from chellow.utils import (
18
18
  hh_range,
19
19
  req_date,
20
20
  req_int,
21
+ to_ct,
21
22
  )
22
23
 
23
24
 
@@ -44,10 +45,11 @@ def content(user_id, report_run_id, contract_id, months_length, finish_date):
44
45
  )
45
46
  writer.writerow(titles)
46
47
 
48
+ finish_date_ct = to_ct(finish_date)
47
49
  months = list(
48
50
  c_months_u(
49
- finish_year=finish_date.year,
50
- finish_month=finish_date.month,
51
+ finish_year=finish_date_ct.year,
52
+ finish_month=finish_date_ct.month,
51
53
  months=months_length,
52
54
  )
53
55
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1750597108.0.0
3
+ Version: 1751036864.0.0
4
4
  Summary: Web Application for checking UK energy bills.
5
5
  Project-URL: Homepage, https://github.com/WessexWater/chellow
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -25,7 +25,7 @@ Requires-Dist: requests==2.32.4
25
25
  Requires-Dist: sqlalchemy==2.0.30
26
26
  Requires-Dist: waitress==3.0.1
27
27
  Requires-Dist: xlrd==2.0.1
28
- Requires-Dist: zish==0.1.11
28
+ Requires-Dist: zish==0.1.12
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  # Chellow
@@ -44,13 +44,14 @@ chellow/e/system_price.py,sha256=6w5J7bzwFAZubE2zdOFRiS8IIrVP8hkoIOaG2yCt-Ic,623
44
44
  chellow/e/tlms.py,sha256=Wb9ZuxscMzxXs0FT06Iu5YXeccmO6ai_mUjnJGJwTM4,9045
45
45
  chellow/e/tnuos.py,sha256=NBmc-f3oezrl4gviAKobljHfICTpBKxxxEGBGJi_lRk,4927
46
46
  chellow/e/triad.py,sha256=lIQj7EdUrcFwEqleuHZXYU_bfzIwNOqUVVxB3NPQt4A,13710
47
- chellow/e/views.py,sha256=jNm52QtQLUcm25WEy480Lj5gyJnCnxyUSpAeYSWHCew,219257
47
+ chellow/e/views.py,sha256=e5rMZFT2fNO3P14_0nCyOEwNWnMiayx2wqo4PJmBwRM,220111
48
48
  chellow/e/bill_parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  chellow/e/bill_parsers/activity_mop_stark_xlsx.py,sha256=UgWXDPzQkQghyj_lfgBqoSJpHB-t-qOdSaB8qY6GLog,4071
50
50
  chellow/e/bill_parsers/annual_mop_stark_xlsx.py,sha256=-HMoIfa_utXYKA44RuC0Xqv3vd2HLeQU_4P0iBUd3WA,4219
51
51
  chellow/e/bill_parsers/bgb_edi.py,sha256=GuwHeYbAGk7BVg5n19FcTANFDyKI-y0z3f9niQaPSSw,4828
52
52
  chellow/e/bill_parsers/csv.py,sha256=U5zcIaZ6B5QTTpFDAcBnk4G2r8B3j5kJhDPL4AJNkEk,5640
53
- chellow/e/bill_parsers/drax_edi.py,sha256=HI_P_ot0bJXMJjKSJACCZijsp7_Wla5ZkwgJHmC5A7I,17760
53
+ chellow/e/bill_parsers/drax_edi.py,sha256=syHvC-q1DK3fuSfI1IgMejNlVa7zEnrr4H9zU2mxqlA,15081
54
+ chellow/e/bill_parsers/drax_element_edi.py,sha256=kxjg1KA4UheMa8doGp9skXtQYrNK8Eisy9ksafR5TQo,13661
54
55
  chellow/e/bill_parsers/edf_export_xlsx.py,sha256=J4lY8epiSTIePZ6D1SGD76TXRoev35KrUC2sjHkNqlE,6632
55
56
  chellow/e/bill_parsers/engie_edi.py,sha256=PDMDI0aqUM1lalgzxih1YmMho11n1rMqE0vyL-aEIs8,15840
56
57
  chellow/e/bill_parsers/engie_export_xlsx.py,sha256=oZO0qHdDlOxjJ6J5Ate82CkAoX4bxed1EJyUKHxBcpk,4690
@@ -109,7 +110,7 @@ chellow/reports/report_g_supplies_snapshot.py,sha256=9xB6RDrnbgxuomMcP1b1yEP4kOn
109
110
  chellow/reports/report_g_supply_virtual_bill.py,sha256=EaYrB8PHJIXrUuhiZ7dwUlbNBkuyJebQHrdc308_z1o,3653
110
111
  chellow/reports/report_g_virtual_bills.py,sha256=20vHa5LGQwOAlJlaGJaGszZrrbT0PMOZJf6hSxU2hIQ,4528
111
112
  chellow/reports/report_g_virtual_bills_hh.py,sha256=gaiLEmKTpq6JsfZ1p0SdCDuPvzvigXp6z88gHRCA63w,3416
112
- chellow/reports/report_missing_bills.py,sha256=3uWA6Wyskn30tUDOV_W6u_009flfKzua7vDh_jsBDaM,5956
113
+ chellow/reports/report_missing_bills.py,sha256=aCgpVpwjN7jKg1S_pnmnHgo9FKFFSThSkiEKAUYvtSo,6021
113
114
  chellow/reports/report_sscs.py,sha256=fQWyVG-gdg37DyNHgpNARpSxIwTl7mCn20fDLwx9oHg,3214
114
115
  chellow/reports/report_supply_contacts.py,sha256=pvwlInaPYV_pa9MMK6vh854plHFwv3m5zo5xulR1g5I,3599
115
116
  chellow/static/css/chellow.css,sha256=dnkuj9Z1BCOV_L2Y26lDd2QlTmFFhATa1YvwPVch1Oc,5375
@@ -384,6 +385,6 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
384
385
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
385
386
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
386
387
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
387
- chellow-1750597108.0.0.dist-info/METADATA,sha256=SnpCk6v8Tc0NBAR7edsSmoofholqC6gS3KQcAxcsuwE,12238
388
- chellow-1750597108.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
389
- chellow-1750597108.0.0.dist-info/RECORD,,
388
+ chellow-1751036864.0.0.dist-info/METADATA,sha256=Ibv1Kc4XId9-HTuNeU9f1P-gSNH-_BtFhxHeAjwCmEk,12238
389
+ chellow-1751036864.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
390
+ chellow-1751036864.0.0.dist-info/RECORD,,