chellow 1755614564.0.0__py3-none-any.whl → 1759155233.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.

Files changed (75) hide show
  1. chellow/e/bill_importer.py +136 -80
  2. chellow/e/bill_parsers/activity_mop_stark_xlsx.py +99 -86
  3. chellow/e/bill_parsers/annual_mop_stark_xlsx.py +78 -61
  4. chellow/e/bill_parsers/csv.py +139 -101
  5. chellow/e/bill_parsers/drax_edi.py +65 -88
  6. chellow/e/bill_parsers/engie_edi.py +187 -255
  7. chellow/e/bill_parsers/engie_xls.py +153 -167
  8. chellow/e/bill_parsers/haven_edi.py +189 -228
  9. chellow/e/bill_parsers/haven_edi_tprs.py +67 -67
  10. chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py +75 -66
  11. chellow/e/bill_parsers/settlement_dc_stark_xlsx.py +229 -126
  12. chellow/e/bill_parsers/sse_edi.py +107 -75
  13. chellow/e/bill_parsers/sww_xls.py +78 -91
  14. chellow/e/computer.py +1 -1
  15. chellow/e/views.py +626 -281
  16. chellow/edi_lib.py +4 -27
  17. chellow/models.py +92 -3
  18. chellow/reports/report_111.py +478 -616
  19. chellow/reports/report_247.py +96 -137
  20. chellow/templates/e/dc_batch.html +110 -157
  21. chellow/templates/e/dc_batch_add.html +2 -3
  22. chellow/templates/e/dc_batch_edit.html +42 -46
  23. chellow/templates/e/dc_batch_file.html +2 -3
  24. chellow/templates/e/dc_batch_file_edit.html +28 -40
  25. chellow/templates/e/dc_batch_upload_file.html +68 -0
  26. chellow/templates/e/dc_batches.html +2 -1
  27. chellow/templates/e/dc_batches_edit.html +26 -0
  28. chellow/templates/e/dc_bill.html +27 -5
  29. chellow/templates/e/dc_bill_add.html +4 -4
  30. chellow/templates/e/dc_bill_edit.html +43 -63
  31. chellow/templates/e/dc_bill_import.html +1 -1
  32. chellow/templates/e/dc_bill_import_contract.html +130 -0
  33. chellow/templates/e/dc_contract.html +1 -1
  34. chellow/templates/e/dc_element.html +41 -0
  35. chellow/templates/e/dc_element_add.html +36 -0
  36. chellow/templates/e/dc_element_edit.html +49 -0
  37. chellow/templates/e/dc_rate_script_edit.html +27 -43
  38. chellow/templates/e/mop_batch.html +105 -152
  39. chellow/templates/e/mop_batch_add.html +2 -3
  40. chellow/templates/e/mop_batch_edit.html +43 -51
  41. chellow/templates/e/mop_batch_upload_file.html +71 -5
  42. chellow/templates/e/mop_batches.html +2 -1
  43. chellow/templates/e/mop_batches_edit.html +26 -0
  44. chellow/templates/e/mop_bill.html +31 -8
  45. chellow/templates/e/mop_bill_add.html +7 -27
  46. chellow/templates/e/mop_bill_import.html +1 -1
  47. chellow/templates/e/mop_bill_import_contract.html +130 -0
  48. chellow/templates/e/mop_contract.html +4 -5
  49. chellow/templates/e/mop_element.html +41 -0
  50. chellow/templates/e/mop_element_add.html +36 -0
  51. chellow/templates/e/mop_element_edit.html +49 -0
  52. chellow/templates/e/supplier_batch.html +3 -7
  53. chellow/templates/e/supplier_batch_add.html +2 -2
  54. chellow/templates/e/supplier_batch_edit.html +1 -1
  55. chellow/templates/e/supplier_batch_file.html +3 -5
  56. chellow/templates/e/supplier_batch_file_add.html +18 -11
  57. chellow/templates/e/supplier_batch_upload_file.html +83 -9
  58. chellow/templates/e/supplier_batches.html +4 -4
  59. chellow/templates/e/supplier_batches_edit.html +26 -0
  60. chellow/templates/e/supplier_bill.html +29 -6
  61. chellow/templates/e/supplier_bill_add.html +3 -3
  62. chellow/templates/e/supplier_bill_import.html +1 -1
  63. chellow/templates/e/supplier_bill_import_contract.html +118 -0
  64. chellow/templates/e/supplier_contract.html +1 -1
  65. chellow/templates/e/supplier_element.html +45 -0
  66. chellow/templates/e/supplier_element_add.html +36 -0
  67. chellow/templates/e/supplier_element_edit.html +51 -0
  68. chellow/templates/report_run_bill_check.html +137 -179
  69. chellow/templates/report_run_row_bill_check.html +182 -179
  70. chellow/views.py +55 -65
  71. {chellow-1755614564.0.0.dist-info → chellow-1759155233.0.0.dist-info}/METADATA +2 -2
  72. {chellow-1755614564.0.0.dist-info → chellow-1759155233.0.0.dist-info}/RECORD +73 -60
  73. chellow/e/bill_parsers/drax_element_edi.py +0 -459
  74. chellow/templates/e/supplier_bill_imports.html +0 -421
  75. {chellow-1755614564.0.0.dist-info → chellow-1759155233.0.0.dist-info}/WHEEL +0 -0
@@ -4,11 +4,227 @@ from decimal import Decimal, InvalidOperation
4
4
  from dateutil.relativedelta import relativedelta
5
5
 
6
6
  from openpyxl import load_workbook
7
+ from openpyxl.utils import get_column_letter
7
8
 
8
9
  from werkzeug.exceptions import BadRequest
9
10
 
10
11
  from chellow.models import Session
11
- from chellow.utils import parse_mpan_core, to_utc
12
+ from chellow.utils import parse_mpan_core, to_ct, to_utc
13
+
14
+ COPS = ("2", "3", "5", "10")
15
+
16
+ COLUMN_MAP = {
17
+ "quarterly unmetered charge": "unmetered charge",
18
+ "no. cop 5/10 meters": "no. cop 5 meters",
19
+ "annual cop 5/10 dc/da rate": "annual cop 5 dc/da rate",
20
+ "monthly cop 3 charge": "cop 3 charge",
21
+ "cop 5/10 charge": "cop 5 charge",
22
+ "monthly cop 5/10 charge": "cop 5 charge",
23
+ "total": "grand total",
24
+ "total ex vat": "grand total",
25
+ "total in vat": "grand total 2",
26
+ "total inc vat": "grand total 2",
27
+ "vat @ 20 %": "vat @ 20%",
28
+ "vat @20%": "vat @ 20%",
29
+ }
30
+ for cop in COPS:
31
+ COLUMN_MAP[f"quarterly cop {cop} charge"] = f"cop {cop} charge"
32
+
33
+
34
+ def make_column_lookup(sheet):
35
+ column_lookup = {}
36
+ for cell in sheet[11]:
37
+ title = str(cell.value).strip().lower()
38
+ try:
39
+ title = COLUMN_MAP[title]
40
+ except KeyError:
41
+ pass
42
+ if title in column_lookup:
43
+ title += " 2"
44
+ column_lookup[title] = get_column_letter(cell.column)
45
+ return column_lookup
46
+
47
+
48
+ def get_ct_date(sheet, col, row):
49
+ cell = get_cell(sheet, col, row)
50
+ val = cell.value
51
+ if not isinstance(val, Datetime):
52
+ raise BadRequest(f"Problem reading {val} as a timestamp at {cell.coordinate}.")
53
+ return to_ct(val)
54
+
55
+
56
+ def get_start_date(sheet, col, row):
57
+ dt = get_ct_date(sheet, col, row)
58
+ return None if dt is None else to_utc(dt)
59
+
60
+
61
+ def get_cell(sheet, col, row):
62
+ try:
63
+ coordinates = f"{col}{row}"
64
+ return sheet[coordinates]
65
+ except IndexError:
66
+ raise BadRequest(f"Can't find the cell {coordinates} on sheet {sheet}.")
67
+
68
+
69
+ def get_str(sheet, col, row):
70
+ return get_cell(sheet, col, row).value.strip()
71
+
72
+
73
+ def get_dec(sheet, col, row):
74
+ cell = get_cell(sheet, col, row)
75
+ if cell.value is None:
76
+ return None
77
+ else:
78
+ try:
79
+ return Decimal(str(cell.value))
80
+ except InvalidOperation as e:
81
+ raise BadRequest(f"Problem parsing the number at {cell.coordinate}. {e}")
82
+
83
+
84
+ def get_gbp(sheet, col, row):
85
+ gbp = get_dec(sheet, col, row)
86
+ if gbp is None:
87
+ return gbp
88
+ else:
89
+ return Decimal("0.00") + round(gbp, 2)
90
+
91
+
92
+ def get_int(sheet, col, row):
93
+ return int(get_cell(sheet, col, row).value)
94
+
95
+
96
+ def _process_row(sess, sheet, row, issue_date, cl):
97
+ mpan_core = parse_mpan_core(str(get_int(sheet, "B", row)))
98
+ start_date_ct = get_ct_date(sheet, cl["bill from"], row)
99
+ start_date = to_utc(start_date_ct)
100
+ finish_date_ct = get_ct_date(sheet, cl["bill to"], row) + relativedelta(
101
+ hours=23, minutes=30
102
+ )
103
+ finish_date = to_utc(finish_date_ct)
104
+
105
+ elements = []
106
+ days = (finish_date_ct - start_date_ct).days + 1
107
+
108
+ el_titles = [("unmetered", "unmetered quant", "unmetered rate", "unmetered charge")]
109
+ for cop in ("2", "3", "5", "10"):
110
+ el_titles.append(
111
+ (
112
+ cop,
113
+ f"no. cop {cop} meters",
114
+ f"annual cop {cop} dc/da rate",
115
+ f"cop {cop} charge",
116
+ )
117
+ )
118
+
119
+ for cop, mpans_title, rate_title, net_title in el_titles:
120
+ if mpans_title in cl:
121
+ mpans = get_int(sheet, cl[mpans_title], row)
122
+ rate = get_dec(sheet, cl[rate_title], row)
123
+ net = get_gbp(sheet, cl[net_title], row)
124
+ if net not in (None, 0):
125
+ elements.append(
126
+ {
127
+ "name": "mpan",
128
+ "start_date": start_date,
129
+ "finish_date": finish_date,
130
+ "net": net,
131
+ "breakdown": {
132
+ "rate": {rate},
133
+ "days": days,
134
+ "mpan-days": mpans * days,
135
+ "cop": {cop},
136
+ },
137
+ }
138
+ )
139
+
140
+ for typ in ("adhoc", "regular"):
141
+ hand_visits_title = f"no. hand held visits ({typ})"
142
+ if hand_visits_title in cl:
143
+ hand_visits = get_dec(sheet, cl[hand_visits_title], row)
144
+ hand_rate = get_dec(sheet, cl[f"hand held visit ({typ}) rate"], row)
145
+ hand_gbp = get_gbp(sheet, cl[f"hand held visit ({typ}) charge"], row)
146
+ if hand_gbp != 0:
147
+ elements.append(
148
+ {
149
+ "name": "activity",
150
+ "start_date": start_date,
151
+ "finish_date": finish_date,
152
+ "net": hand_gbp,
153
+ "breakdown": {
154
+ "rate": {hand_rate},
155
+ "activity-name": {"hand_held_visit"},
156
+ "visits": hand_visits,
157
+ },
158
+ }
159
+ )
160
+
161
+ annual_visits_count = get_int(sheet, cl["no. annual site visits"], row)
162
+ annual_visits_rate = get_dec(sheet, cl["annual site visit rate"], row)
163
+ annual_visits_gbp = get_gbp(sheet, cl["annual site visit charge"], row)
164
+ if annual_visits_gbp != 0:
165
+ elements.append(
166
+ {
167
+ "name": "activitiy",
168
+ "start_date": start_date,
169
+ "finish_date": finish_date,
170
+ "net": annual_visits_gbp,
171
+ "breakdown": {
172
+ "rate": {annual_visits_rate},
173
+ "count": annual_visits_count,
174
+ "activity-name": {"annual_site_visit"},
175
+ },
176
+ }
177
+ )
178
+
179
+ breakdown = {
180
+ "raw_lines": [],
181
+ "settlement-status": ["settlement"],
182
+ }
183
+ net = get_gbp(sheet, cl["grand total"], row)
184
+ sum_el = sum(el["net"] for el in elements)
185
+ if net != sum_el:
186
+ elements.append(
187
+ {
188
+ "name": "activitiy",
189
+ "start_date": start_date,
190
+ "finish_date": finish_date,
191
+ "net": net - sum_el,
192
+ "breakdown": {
193
+ "activity-name": {"discrepancy"},
194
+ },
195
+ }
196
+ )
197
+
198
+ vat = Decimal("0.00")
199
+ if "vat @ 20%" in cl:
200
+ vat += get_gbp(sheet, cl["vat @ 20%"], row)
201
+ gross = Decimal("0.00")
202
+ if "grand total 2" in cl:
203
+ gross += get_gbp(sheet, cl["grand total 2"], row)
204
+
205
+ return {
206
+ "bill_type_code": "N",
207
+ "kwh": Decimal(0),
208
+ "net": net,
209
+ "vat": vat,
210
+ "gross": gross,
211
+ "reads": [],
212
+ "breakdown": breakdown,
213
+ "account": mpan_core,
214
+ "issue_date": issue_date,
215
+ "start_date": start_date,
216
+ "finish_date": finish_date,
217
+ "mpan_core": mpan_core,
218
+ "reference": "_".join(
219
+ (
220
+ start_date_ct.strftime("%Y%m%d"),
221
+ finish_date_ct.strftime("%Y%m%d"),
222
+ to_ct(issue_date).strftime("%Y%m%d"),
223
+ mpan_core,
224
+ )
225
+ ),
226
+ "elements": elements,
227
+ }
12
228
 
13
229
 
14
230
  class Parser:
@@ -31,139 +247,26 @@ class Parser:
31
247
  self._title_line = line
32
248
  return line
33
249
 
34
- def get_ct_date(self, col, row):
35
- cell = self.get_cell(col, row)
36
- val = cell.value
37
- if not isinstance(val, Datetime):
38
- raise BadRequest(
39
- f"Problem reading {val} as a timestamp at {cell.coordinate}."
40
- )
41
- return val
42
-
43
- def get_start_date(self, col, row):
44
- dt = self.get_ct_date(col, row)
45
- return None if dt is None else to_utc(dt)
46
-
47
- def get_cell(self, col, row):
48
- try:
49
- coordinates = f"{col}{row}"
50
- return self.sheet[coordinates]
51
- except IndexError:
52
- raise BadRequest(
53
- f"Can't find the cell {coordinates} on sheet {self.sheet}."
54
- )
55
-
56
- def get_str(self, col, row):
57
- return self.get_cell(col, row).value.strip()
58
-
59
- def get_dec(self, col, row):
60
- cell = self.get_cell(col, row)
61
- try:
62
- return Decimal(str(cell.value))
63
- except InvalidOperation as e:
64
- raise BadRequest(f"Problem parsing the number at {cell.coordinate}. {e}")
65
-
66
- def get_int(self, col, row):
67
- return int(self.get_cell(col, row).value)
68
-
69
250
  def make_raw_bills(self):
70
- row_index = None
71
251
  try:
72
252
  with Session() as sess:
73
253
  bills = []
74
- issue_date_str = self.get_str("A", 7)
75
- issue_date = Datetime.strptime(issue_date_str[6:], "%d/%m/%Y %H:%M:%S")
254
+ issue_date_str = get_str(self.sheet, "A", 7)
255
+ issue_date = to_utc(
256
+ to_ct(Datetime.strptime(issue_date_str[6:-3], "%d/%m/%Y %H:%M"))
257
+ )
258
+ column_lookup = make_column_lookup(self.sheet)
259
+
76
260
  for row in range(12, len(self.sheet["A"]) + 1):
77
- val = self.get_cell("A", row).value
261
+ val = get_cell(self.sheet, "A", row).value
78
262
  if val is None or val == "":
79
- break
263
+ continue
80
264
 
81
- self._set_last_line(row_index, val)
82
- mpan_core = parse_mpan_core(str(self.get_int("B", row)))
83
- start_date = self.get_start_date("D", row)
84
- finish_date = self.get_start_date("E", row) + relativedelta(
85
- hours=23, minutes=30
86
- )
87
-
88
- net = round(self.get_dec("AO", row), 2)
89
-
90
- cop_3_meters = self.get_int("G", row)
91
- cop_3_rate = self.get_dec("H", row)
92
- cop_3_gbp = self.get_dec("I", row)
93
-
94
- # Cop 5 meters
95
- self.get_int("J", row)
96
- cop_5_rate = self.get_dec("K", row)
97
- cop_5_gbp = self.get_dec("L", row)
98
-
99
- ad_hoc_visits = self.get_dec("P", row)
100
- ad_hoc_rate = self.get_dec("Q", row)
101
- ad_hoc_gbp = self.get_dec("R", row)
102
- activity_names = set()
103
- activity_gbp = Decimal("0")
104
- if ad_hoc_gbp != 0:
105
- activity_names.add("ad_hoc_visit")
106
- activity_gbp += ad_hoc_gbp
107
-
108
- annual_visits = self.get_int("S", row)
109
- annual_rate = self.get_dec("T", row)
110
- annual_gbp = self.get_dec("U", row)
111
- if annual_gbp != 0:
112
- activity_names.add("annual_visit")
113
- activity_gbp += annual_gbp
114
-
115
- if cop_3_meters > 0:
116
- cop = "3"
117
- mpan_rate = cop_3_rate
118
- mpan_gbp = cop_3_gbp
119
- else:
120
- cop = "5"
121
- mpan_rate = cop_5_rate
122
- mpan_gbp = cop_5_gbp
123
-
124
- breakdown = {
125
- "raw_lines": [],
126
- "cop": [cop],
127
- "settlement-status": ["settlement"],
128
- "mpan-rate": [mpan_rate],
129
- "mpan-gbp": mpan_gbp,
130
- "ad-hoc-visits": ad_hoc_visits,
131
- "ad-hoc-rate": [ad_hoc_rate],
132
- "ad-hoc-gbp-info": ad_hoc_gbp,
133
- "annual-visits-count": annual_visits,
134
- "annual-visits-rate": [annual_rate],
135
- "annual-visits-gbp-info": annual_gbp,
136
- }
137
- if len(activity_names) > 0:
138
- breakdown["activity-name"] = sorted(activity_names)
139
-
140
- if activity_gbp != 0:
141
- breakdown["activity-gbp"] = activity_gbp
142
-
143
- bills.append(
144
- {
145
- "bill_type_code": "N",
146
- "kwh": Decimal(0),
147
- "vat": Decimal("0.00"),
148
- "net": net,
149
- "gross": net,
150
- "reads": [],
151
- "breakdown": breakdown,
152
- "account": mpan_core,
153
- "issue_date": issue_date,
154
- "start_date": start_date,
155
- "finish_date": finish_date,
156
- "mpan_core": mpan_core,
157
- "reference": "_".join(
158
- (
159
- start_date.strftime("%Y%m%d"),
160
- finish_date.strftime("%Y%m%d"),
161
- issue_date.strftime("%Y%m%d"),
162
- mpan_core,
163
- )
164
- ),
165
- }
265
+ self._set_last_line(row, val)
266
+ bill = _process_row(
267
+ sess, self.sheet, row, issue_date, column_lookup
166
268
  )
269
+ bills.append(bill)
167
270
  sess.rollback()
168
271
 
169
272
  except BadRequest as e:
@@ -3,7 +3,14 @@ from decimal import Decimal
3
3
 
4
4
  from werkzeug.exceptions import BadRequest
5
5
 
6
- from chellow.edi_lib import parse_edi, to_ct_date, to_date, to_decimal, to_finish_date
6
+ from chellow.edi_lib import (
7
+ parse_edi,
8
+ to_ct_date,
9
+ to_date,
10
+ to_decimal,
11
+ to_finish_date,
12
+ to_gbp,
13
+ )
7
14
  from chellow.models import Era, Session
8
15
  from chellow.utils import HH, ct_datetime, parse_mpan_core, to_utc
9
16
 
@@ -196,6 +203,45 @@ def _process_BCD(elements, headers):
196
203
  headers["finish_date"] = to_finish_date(sumo[1])
197
204
 
198
205
 
206
+ def _process_BTL(elements, headers):
207
+ uvlt = elements["UVLT"]
208
+ utva = elements["UTVA"]
209
+ tbtl = elements["TBTL"]
210
+
211
+ if headers["mpan_core"] is None:
212
+ sess = headers["sess"]
213
+ era = (
214
+ sess.query(Era)
215
+ .filter(Era.imp_supplier_account == headers["account"])
216
+ .first()
217
+ )
218
+ if era is not None:
219
+ headers["mpan_core"] = era.imp_mpan_core
220
+ sess.close()
221
+
222
+ if headers["is_ebatch"]:
223
+ for r in headers["reads"]:
224
+ if r["pres_type_code"] == "C":
225
+ r["pres_type_code"] = "E"
226
+
227
+ return {
228
+ "bill_type_code": headers["bill_type_code"],
229
+ "account": headers["account"],
230
+ "mpan_core": headers["mpan_core"],
231
+ "reference": headers["reference"],
232
+ "issue_date": headers["issue_date"],
233
+ "start_date": headers["start_date"],
234
+ "finish_date": headers["finish_date"],
235
+ "kwh": headers["kwh"],
236
+ "net": to_gbp(uvlt),
237
+ "vat": to_gbp(utva),
238
+ "gross": to_gbp(tbtl),
239
+ "breakdown": headers["breakdown"],
240
+ "reads": headers["reads"],
241
+ "elements": headers["elements"],
242
+ }
243
+
244
+
199
245
  def _process_MHD(elements, headers):
200
246
  message_type = elements["TYPE"][0]
201
247
  sess = headers["sess"]
@@ -204,7 +250,7 @@ def _process_MHD(elements, headers):
204
250
  headers["kwh"] = Decimal("0")
205
251
  headers["reads"] = []
206
252
  headers["breakdown"] = defaultdict(int, {"raw-lines": []})
207
- headers["bill_elements"] = []
253
+ headers["elements"] = []
208
254
  headers["errors"] = []
209
255
  headers["sess"] = sess
210
256
  headers["mpan_core"] = None
@@ -215,9 +261,6 @@ def _process_MHD(elements, headers):
215
261
  headers["issue_date"] = None
216
262
  headers["start_date"] = None
217
263
  headers["finish_date"] = None
218
- headers["net"] = Decimal("0.00")
219
- headers["vat"] = Decimal("0.00")
220
- headers["gross"] = Decimal("0.00")
221
264
  headers["message_type"] = message_type
222
265
 
223
266
 
@@ -323,6 +366,11 @@ def _process_CCD2(elements, headers):
323
366
 
324
367
  adjf = elements["ADJF"]
325
368
  cona = elements["CONA"]
369
+ nuct = elements["NUCT"]
370
+ csdt = elements["CSDT"]
371
+ cedt = elements["CEDT"]
372
+ ctot = elements["CTOT"]
373
+ cppu = elements["CPPU"]
326
374
 
327
375
  coefficient = Decimal(adjf[1]) / Decimal(100000)
328
376
  pres_reading_value = Decimal(prrd[0])
@@ -338,26 +386,28 @@ def _process_CCD2(elements, headers):
338
386
  if tpr == "kW":
339
387
  units = "kW"
340
388
  tpr = None
341
- prefix = "md-"
389
+ el_name = "md"
342
390
  elif tpr == "kVA":
343
391
  units = "kVA"
344
392
  tpr = None
345
- prefix = "md-"
393
+ el_name = "md"
346
394
  else:
347
395
  units = "kWh"
348
396
  headers["kwh"] += to_decimal(cona) / Decimal("1000")
349
- prefix = tpr + "-"
397
+ el_name = tpr
350
398
 
351
- nuct = elements["NUCT"]
352
- breakdown = headers["breakdown"]
353
- breakdown[prefix + "kwh"] += to_decimal(nuct) / Decimal("1000")
354
- cppu = elements["CPPU"]
355
- rate_key = prefix + "rate"
356
- if rate_key not in breakdown:
357
- breakdown[rate_key] = set()
358
- breakdown[rate_key].add(to_decimal(cppu) / Decimal("100000"))
359
- ctot = elements["CTOT"]
360
- breakdown[prefix + "gbp"] += to_decimal(ctot) / Decimal("100")
399
+ headers["elements"].append(
400
+ {
401
+ "name": el_name,
402
+ "breakdown": {
403
+ cona[1].lower(): to_decimal(nuct) / Decimal("1000"),
404
+ "rate": {to_decimal(cppu) / Decimal("100000")},
405
+ },
406
+ "start_date": to_date(csdt[0]),
407
+ "finish_date": to_finish_date(cedt[0]),
408
+ "net": to_gbp(ctot),
409
+ }
410
+ )
361
411
 
362
412
  if mpan_core in WRONG_TPRS and pres_read_date == to_utc(
363
413
  ct_datetime(2020, 4, 1, 23, 30)
@@ -400,12 +450,9 @@ def _process_CCD3(elements, headers):
400
450
  tmod = elements["TMOD"]
401
451
  tmod0 = tmod[0]
402
452
  if tmod0 == "CCL":
403
- prefix = kwh_prefix = "ccl-"
404
- elif tmod0 in ["CQFITC", "CMFITC"]:
405
- prefix = "fit-"
406
- kwh_prefix = "fit-msp-"
407
- elif tmod0 == "FITARR":
408
- prefix = kwh_prefix = "fit-reconciliation-"
453
+ el_name = "ccl"
454
+ elif tmod0 in ("CQFITC", "CMFITC", "FITARR"):
455
+ el_name = "fit"
409
456
  else:
410
457
  tpr_code = tmod0
411
458
  if tpr_code not in tmod_map:
@@ -413,66 +460,51 @@ def _process_CCD3(elements, headers):
413
460
  f"The TPR code {tpr_code} can't be found in the TPR "
414
461
  f"list for mpan {headers['mpan']}."
415
462
  )
416
- prefix = kwh_prefix = tmod_map[tpr_code] + "-"
463
+ el_name = tmod_map[tpr_code]
417
464
 
418
465
  nuct = elements["NUCT"]
419
- breakdown = headers["breakdown"]
420
- breakdown[kwh_prefix + "kwh"] += to_decimal(nuct) / Decimal("1000")
421
466
  cppu = elements["CPPU"]
422
-
423
- rate_key = prefix + "rate"
424
- if rate_key not in breakdown:
425
- breakdown[rate_key] = set()
426
- breakdown[rate_key].add(to_decimal(cppu) / Decimal("100000"))
427
-
428
467
  ctot = elements["CTOT"]
429
- breakdown[prefix + "gbp"] += to_decimal(ctot) / Decimal("100")
468
+ csdt = elements["CSDT"]
469
+ start_date = to_date(csdt[0])
470
+ cedt = elements["CEDT"]
471
+ finish_date = to_finish_date(cedt[0])
472
+ headers["elements"].append(
473
+ {
474
+ "name": el_name,
475
+ "net": to_gbp(ctot),
476
+ "start_date": start_date,
477
+ "finish_date": finish_date,
478
+ "breakdown": {
479
+ "kwh": to_decimal(nuct) / Decimal("1000"),
480
+ "rate": {to_decimal(cppu) / Decimal("100000")},
481
+ },
482
+ }
483
+ )
430
484
 
431
485
 
432
486
  def _process_CCD4(elements, headers):
433
487
  ndrp = elements["NDRP"]
434
- breakdown = headers["breakdown"]
435
- if len(ndrp[0]) > 0:
436
- breakdown["standing-days"] += to_decimal(ndrp)
437
488
  ctot = elements["CTOT"]
489
+ csdt = elements["CSDT"]
490
+ cedt = elements["CEDT"]
491
+
438
492
  if len(ctot[0]) > 0:
439
- breakdown["standing-gbp"] += to_decimal(ctot) / Decimal("100")
493
+ breakdown = {}
494
+ el = {
495
+ "name": "standing",
496
+ "net": to_gbp(ctot),
497
+ "start_date": to_date(csdt[0]),
498
+ "finish_date": to_finish_date(cedt[0]),
499
+ "breakdown": breakdown,
500
+ }
501
+ if len(ndrp[0]) > 0:
502
+ breakdown["days"] = to_decimal(ndrp)
503
+ headers["elements"].append(el)
440
504
 
441
505
 
442
506
  def _process_MTR(elements, headers):
443
- if headers["message_type"] == "UTLBIL":
444
- if headers["mpan_core"] is None:
445
- sess = headers["sess"]
446
- era = (
447
- sess.query(Era)
448
- .filter(Era.imp_supplier_account == headers["account"])
449
- .first()
450
- )
451
- if era is not None:
452
- headers["mpan_core"] = era.imp_mpan_core
453
- sess.close()
454
-
455
- if headers["is_ebatch"]:
456
- for r in headers["reads"]:
457
- if r["pres_type_code"] == "C":
458
- r["pres_type_code"] = "E"
459
-
460
- raw_bill = {
461
- "bill_type_code": headers["bill_type_code"],
462
- "account": headers["account"],
463
- "mpan_core": headers["mpan_core"],
464
- "reference": headers["reference"],
465
- "issue_date": headers["issue_date"],
466
- "start_date": headers["start_date"],
467
- "finish_date": headers["finish_date"],
468
- "kwh": headers["kwh"],
469
- "net": headers["net"],
470
- "vat": headers["vat"],
471
- "gross": headers["gross"],
472
- "breakdown": headers["breakdown"],
473
- "reads": headers["reads"],
474
- }
475
- return raw_bill
507
+ pass
476
508
 
477
509
 
478
510
  def _process_MAN(elements, headers):
@@ -482,11 +514,11 @@ def _process_MAN(elements, headers):
482
514
 
483
515
  def _process_VAT(elements, headers):
484
516
  uvla = elements["UVLA"]
485
- headers["net"] += to_decimal(uvla) / Decimal("100")
517
+ to_decimal(uvla) / Decimal("100")
486
518
  uvtt = elements["UVTT"]
487
- headers["vat"] += to_decimal(uvtt) / Decimal("100")
519
+ to_decimal(uvtt) / Decimal("100")
488
520
  ucsi = elements["UCSI"]
489
- headers["gross"] += to_decimal(ucsi) / Decimal("100")
521
+ to_decimal(ucsi) / Decimal("100")
490
522
 
491
523
 
492
524
  def _process_NOOP(elements, headers):
@@ -495,7 +527,7 @@ def _process_NOOP(elements, headers):
495
527
 
496
528
  CODE_FUNCS = {
497
529
  "BCD": _process_BCD,
498
- "BTL": _process_NOOP,
530
+ "BTL": _process_BTL,
499
531
  "CCD1": _process_CCD1,
500
532
  "CCD2": _process_CCD2,
501
533
  "CCD3": _process_CCD3,