chellow 1757320031.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.
- chellow/e/bill_importer.py +136 -80
- chellow/e/bill_parsers/activity_mop_stark_xlsx.py +99 -86
- chellow/e/bill_parsers/annual_mop_stark_xlsx.py +78 -61
- chellow/e/bill_parsers/csv.py +139 -101
- chellow/e/bill_parsers/drax_edi.py +65 -89
- chellow/e/bill_parsers/engie_edi.py +187 -255
- chellow/e/bill_parsers/engie_xls.py +153 -167
- chellow/e/bill_parsers/haven_edi.py +189 -228
- chellow/e/bill_parsers/haven_edi_tprs.py +67 -67
- chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py +75 -66
- chellow/e/bill_parsers/settlement_dc_stark_xlsx.py +229 -126
- chellow/e/bill_parsers/sse_edi.py +107 -75
- chellow/e/bill_parsers/sww_xls.py +78 -91
- chellow/e/computer.py +1 -1
- chellow/e/views.py +626 -281
- chellow/edi_lib.py +4 -27
- chellow/models.py +92 -3
- chellow/reports/report_111.py +478 -616
- chellow/reports/report_247.py +96 -137
- chellow/templates/e/dc_batch.html +110 -157
- chellow/templates/e/dc_batch_add.html +2 -3
- chellow/templates/e/dc_batch_edit.html +42 -46
- chellow/templates/e/dc_batch_file.html +2 -3
- chellow/templates/e/dc_batch_file_edit.html +28 -40
- chellow/templates/e/dc_batch_upload_file.html +68 -0
- chellow/templates/e/dc_batches.html +2 -1
- chellow/templates/e/dc_batches_edit.html +26 -0
- chellow/templates/e/dc_bill.html +27 -5
- chellow/templates/e/dc_bill_add.html +4 -4
- chellow/templates/e/dc_bill_edit.html +43 -63
- chellow/templates/e/dc_bill_import.html +1 -1
- chellow/templates/e/dc_bill_import_contract.html +130 -0
- chellow/templates/e/dc_contract.html +1 -1
- chellow/templates/e/dc_element.html +41 -0
- chellow/templates/e/dc_element_add.html +36 -0
- chellow/templates/e/dc_element_edit.html +49 -0
- chellow/templates/e/dc_rate_script_edit.html +27 -43
- chellow/templates/e/mop_batch.html +105 -152
- chellow/templates/e/mop_batch_add.html +2 -3
- chellow/templates/e/mop_batch_edit.html +43 -51
- chellow/templates/e/mop_batch_upload_file.html +71 -5
- chellow/templates/e/mop_batches.html +2 -1
- chellow/templates/e/mop_batches_edit.html +26 -0
- chellow/templates/e/mop_bill.html +31 -8
- chellow/templates/e/mop_bill_add.html +7 -27
- chellow/templates/e/mop_bill_import.html +1 -1
- chellow/templates/e/mop_bill_import_contract.html +130 -0
- chellow/templates/e/mop_contract.html +4 -5
- chellow/templates/e/mop_element.html +41 -0
- chellow/templates/e/mop_element_add.html +36 -0
- chellow/templates/e/mop_element_edit.html +49 -0
- chellow/templates/e/supplier_batch.html +3 -7
- chellow/templates/e/supplier_batch_add.html +2 -2
- chellow/templates/e/supplier_batch_edit.html +1 -1
- chellow/templates/e/supplier_batch_file.html +3 -5
- chellow/templates/e/supplier_batch_file_add.html +18 -11
- chellow/templates/e/supplier_batch_upload_file.html +83 -9
- chellow/templates/e/supplier_batches.html +4 -4
- chellow/templates/e/supplier_batches_edit.html +26 -0
- chellow/templates/e/supplier_bill.html +29 -6
- chellow/templates/e/supplier_bill_add.html +3 -3
- chellow/templates/e/supplier_bill_import.html +1 -1
- chellow/templates/e/supplier_bill_import_contract.html +118 -0
- chellow/templates/e/supplier_contract.html +1 -1
- chellow/templates/e/supplier_element.html +45 -0
- chellow/templates/e/supplier_element_add.html +36 -0
- chellow/templates/e/supplier_element_edit.html +51 -0
- chellow/templates/report_run_bill_check.html +137 -179
- chellow/templates/report_run_row_bill_check.html +182 -179
- chellow/views.py +55 -65
- {chellow-1757320031.0.0.dist-info → chellow-1759155233.0.0.dist-info}/METADATA +2 -2
- {chellow-1757320031.0.0.dist-info → chellow-1759155233.0.0.dist-info}/RECORD +73 -60
- chellow/e/bill_parsers/drax_element_edi.py +0 -459
- chellow/templates/e/supplier_bill_imports.html +0 -421
- {chellow-1757320031.0.0.dist-info → chellow-1759155233.0.0.dist-info}/WHEEL +0 -0
chellow/reports/report_111.py
CHANGED
|
@@ -14,11 +14,10 @@ from flask import g, redirect, request
|
|
|
14
14
|
|
|
15
15
|
from sqlalchemy import or_, select
|
|
16
16
|
from sqlalchemy.orm import joinedload, subqueryload
|
|
17
|
-
from sqlalchemy.sql.expression import null
|
|
17
|
+
from sqlalchemy.sql.expression import null
|
|
18
18
|
|
|
19
19
|
from werkzeug.exceptions import BadRequest
|
|
20
20
|
|
|
21
|
-
from zish import ZishLocationException, loads
|
|
22
21
|
|
|
23
22
|
from chellow.dloads import open_file
|
|
24
23
|
from chellow.e.computer import SupplySource, contract_func
|
|
@@ -26,14 +25,13 @@ from chellow.models import (
|
|
|
26
25
|
Batch,
|
|
27
26
|
Bill,
|
|
28
27
|
Contract,
|
|
28
|
+
Element,
|
|
29
29
|
Era,
|
|
30
30
|
Llfc,
|
|
31
31
|
MtcParticipant,
|
|
32
32
|
RSession,
|
|
33
33
|
RegisterRead,
|
|
34
34
|
ReportRun,
|
|
35
|
-
Site,
|
|
36
|
-
SiteEra,
|
|
37
35
|
Supply,
|
|
38
36
|
User,
|
|
39
37
|
)
|
|
@@ -52,41 +50,49 @@ from chellow.utils import (
|
|
|
52
50
|
)
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
def
|
|
53
|
+
def _add_gap_hh(gaps, hh_start, gap_type):
|
|
56
54
|
try:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
hh = gaps[hh_start]
|
|
56
|
+
match (hh, gap_type):
|
|
57
|
+
case ("middle", _):
|
|
58
|
+
pass
|
|
59
|
+
case (_, "middle"):
|
|
60
|
+
gaps[hh_start] = "middle"
|
|
61
|
+
case ("start", "start"):
|
|
62
|
+
pass
|
|
63
|
+
case ("start", "finish"):
|
|
64
|
+
gaps[hh_start] = "start_finish"
|
|
65
|
+
case ("finish", "finish"):
|
|
66
|
+
pass
|
|
67
|
+
case ("finish", "start"):
|
|
68
|
+
gaps[hh_start] = "start_finish"
|
|
69
|
+
case ("start_finish", "finish"):
|
|
70
|
+
pass
|
|
71
|
+
case ("start_finish", "start"):
|
|
72
|
+
pass
|
|
73
|
+
case _:
|
|
74
|
+
raise BadRequest(f"Gap combination ({hh}, {gap_type}) not recognized.")
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
except KeyError:
|
|
77
|
+
hh = gaps[hh_start] = gap_type
|
|
63
78
|
|
|
64
|
-
for hh_start in hhs:
|
|
65
|
-
try:
|
|
66
|
-
hhgap = elgap[hh_start]
|
|
67
|
-
except KeyError:
|
|
68
|
-
hhgap = elgap[hh_start] = {
|
|
69
|
-
"has_covered": False,
|
|
70
|
-
"has_virtual": False,
|
|
71
|
-
"gbp": 0,
|
|
72
|
-
}
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
def _add_gap(caches, gaps, start_date, finish_date):
|
|
81
|
+
hhs = hh_range(caches, start_date, finish_date)
|
|
82
|
+
_add_gap_hh(gaps, hhs[0], "start")
|
|
83
|
+
_add_gap_hh(gaps, hhs[-1] + HH, "finish")
|
|
84
|
+
for hh_start in hhs[1:]:
|
|
85
|
+
_add_gap_hh(gaps, hh_start, "middle")
|
|
79
86
|
|
|
80
87
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
88
|
+
def find_gaps(gaps):
|
|
89
|
+
if len(gaps) > 0:
|
|
90
|
+
gap_start = None
|
|
91
|
+
for ghh, gtype in sorted(gaps.items()):
|
|
92
|
+
if "finish" in gtype:
|
|
93
|
+
yield gap_start, ghh - HH
|
|
94
|
+
if "start" in gtype:
|
|
95
|
+
gap_start = ghh
|
|
90
96
|
|
|
91
97
|
|
|
92
98
|
def content(
|
|
@@ -112,8 +118,8 @@ def content(
|
|
|
112
118
|
)
|
|
113
119
|
writer = csv.writer(tmp_file, lineterminator="\n")
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
bills_q = (
|
|
122
|
+
select(Bill)
|
|
117
123
|
.order_by(Bill.supply_id, Bill.reference)
|
|
118
124
|
.options(
|
|
119
125
|
joinedload(Bill.supply),
|
|
@@ -125,30 +131,30 @@ def content(
|
|
|
125
131
|
|
|
126
132
|
if len(mpan_cores) > 0:
|
|
127
133
|
mpan_cores = list(map(parse_mpan_core, mpan_cores))
|
|
128
|
-
supply_ids =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.filter(
|
|
134
|
+
supply_ids = sess.scalars(
|
|
135
|
+
select(Era.supply_id)
|
|
136
|
+
.where(
|
|
132
137
|
or_(
|
|
133
138
|
Era.imp_mpan_core.in_(mpan_cores),
|
|
134
139
|
Era.exp_mpan_core.in_(mpan_cores),
|
|
135
140
|
)
|
|
136
141
|
)
|
|
137
142
|
.distinct()
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
).all()
|
|
144
|
+
|
|
145
|
+
bills_q = bills_q.join(Supply).where(Supply.id.in_(supply_ids))
|
|
140
146
|
|
|
141
147
|
if batch_id is not None:
|
|
142
148
|
batch = Batch.get_by_id(sess, batch_id)
|
|
143
|
-
|
|
149
|
+
bills_q = bills_q.where(Bill.batch == batch)
|
|
144
150
|
contract = batch.contract
|
|
145
151
|
elif bill_id is not None:
|
|
146
152
|
bill = Bill.get_by_id(sess, bill_id)
|
|
147
|
-
|
|
153
|
+
bills_q = bills_q.where(Bill.id == bill.id)
|
|
148
154
|
contract = bill.batch.contract
|
|
149
155
|
elif contract_id is not None:
|
|
150
156
|
contract = Contract.get_by_id(sess, contract_id)
|
|
151
|
-
|
|
157
|
+
bills_q = bills_q.join(Batch).where(
|
|
152
158
|
Batch.contract == contract,
|
|
153
159
|
Bill.start_date <= finish_date,
|
|
154
160
|
Bill.finish_date >= start_date,
|
|
@@ -171,51 +177,53 @@ def content(
|
|
|
171
177
|
)
|
|
172
178
|
virtual_bill_titles = virtual_bill_titles_func()
|
|
173
179
|
|
|
174
|
-
titles = [
|
|
175
|
-
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"exp-mpan-core",
|
|
186
|
-
"site-code",
|
|
187
|
-
"site-name",
|
|
188
|
-
"covered-from",
|
|
189
|
-
"covered-to",
|
|
190
|
-
"covered-bills",
|
|
191
|
-
"metered-kwh",
|
|
180
|
+
titles = []
|
|
181
|
+
header_titles = [
|
|
182
|
+
"imp_mpan_core",
|
|
183
|
+
"exp_mpan_core",
|
|
184
|
+
"site_code",
|
|
185
|
+
"site_name",
|
|
186
|
+
"period_start",
|
|
187
|
+
"period_finish",
|
|
188
|
+
"actual_net_gbp",
|
|
189
|
+
"virtual_net_gbp",
|
|
190
|
+
"difference_net_gbp",
|
|
192
191
|
]
|
|
192
|
+
titles.extend(header_titles)
|
|
193
193
|
for t in virtual_bill_titles:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
if t not in ("net-gbp", "vat-gbp", "gross-gbp"):
|
|
195
|
+
titles.append("actual-" + t)
|
|
196
|
+
titles.append("virtual-" + t)
|
|
197
|
+
if t.endswith("-gbp"):
|
|
198
|
+
titles.append("difference-" + t)
|
|
198
199
|
|
|
199
200
|
writer.writerow(titles)
|
|
200
201
|
|
|
201
202
|
bill_map = defaultdict(set, {})
|
|
202
|
-
for bill in
|
|
203
|
+
for bill in sess.scalars(bills_q):
|
|
203
204
|
bill_map[bill.supply.id].add(bill.id)
|
|
204
205
|
|
|
205
206
|
for supply_id, bill_ids in bill_map.items():
|
|
206
|
-
_process_supply(
|
|
207
|
-
sess,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
207
|
+
for data in _process_supply(
|
|
208
|
+
sess, caches, supply_id, bill_ids, forecast_date, contract, vbf
|
|
209
|
+
):
|
|
210
|
+
vals = {}
|
|
211
|
+
for title in header_titles:
|
|
212
|
+
vals[title] = data[title]
|
|
213
|
+
for el_name, el in data["elements"].items():
|
|
214
|
+
for part_name, part in el["parts"].items():
|
|
215
|
+
for typ, value in part.items():
|
|
216
|
+
vals[f"{typ}-{el_name}-{part_name}"] = value
|
|
217
|
+
|
|
218
|
+
writer.writerow(csv_make_val(vals.get(title)) for title in titles)
|
|
219
|
+
ReportRun.w_insert_row(
|
|
220
|
+
report_run_id,
|
|
221
|
+
"",
|
|
222
|
+
titles,
|
|
223
|
+
vals,
|
|
224
|
+
{"is_checked": False},
|
|
225
|
+
data=data,
|
|
226
|
+
)
|
|
219
227
|
ReportRun.w_update(report_run_id, "finished")
|
|
220
228
|
|
|
221
229
|
except BadRequest as e:
|
|
@@ -301,584 +309,438 @@ def do_post(sess):
|
|
|
301
309
|
return redirect(f"/report_runs/{report_run.id}", 303)
|
|
302
310
|
|
|
303
311
|
|
|
304
|
-
def
|
|
312
|
+
def _get_bill_status(sess, bill_statuses, bill):
|
|
313
|
+
try:
|
|
314
|
+
bill_status = bill_statuses[bill.id]
|
|
315
|
+
except KeyError:
|
|
316
|
+
covered_bills = dict(
|
|
317
|
+
(b.id, b)
|
|
318
|
+
for b in sess.scalars(
|
|
319
|
+
select(Bill)
|
|
320
|
+
.join(Batch)
|
|
321
|
+
.join(Contract)
|
|
322
|
+
.where(
|
|
323
|
+
Bill.supply == bill.supply,
|
|
324
|
+
Bill.start_date <= bill.finish_date,
|
|
325
|
+
Bill.finish_date >= bill.start_date,
|
|
326
|
+
Contract.market_role == bill.batch.contract.market_role,
|
|
327
|
+
)
|
|
328
|
+
.order_by(Bill.start_date, Bill.issue_date)
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
while True:
|
|
332
|
+
to_del = None
|
|
333
|
+
for a, b in combinations(covered_bills.values(), 2):
|
|
334
|
+
if all(
|
|
335
|
+
(
|
|
336
|
+
a.start_date == b.start_date,
|
|
337
|
+
a.finish_date == b.finish_date,
|
|
338
|
+
a.net == -1 * b.net,
|
|
339
|
+
a.vat == -1 * b.vat,
|
|
340
|
+
a.gross == -1 * b.gross,
|
|
341
|
+
)
|
|
342
|
+
):
|
|
343
|
+
to_del = (a.id, b.id)
|
|
344
|
+
break
|
|
345
|
+
if to_del is None:
|
|
346
|
+
break
|
|
347
|
+
else:
|
|
348
|
+
for k in to_del:
|
|
349
|
+
del covered_bills[k]
|
|
350
|
+
bill_statuses[k] = None
|
|
351
|
+
|
|
352
|
+
for k, v in covered_bills.items():
|
|
353
|
+
bill_statuses[k] = v
|
|
354
|
+
|
|
355
|
+
bill_status = bill_statuses[bill.id]
|
|
356
|
+
|
|
357
|
+
return bill_status
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _process_period(
|
|
305
361
|
sess,
|
|
306
362
|
caches,
|
|
307
|
-
|
|
308
|
-
bill_ids,
|
|
309
|
-
forecast_date,
|
|
363
|
+
supply,
|
|
310
364
|
contract,
|
|
365
|
+
bill_statuses,
|
|
366
|
+
forecast_date,
|
|
311
367
|
vbf,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
titles,
|
|
315
|
-
report_run_id,
|
|
368
|
+
period_start,
|
|
369
|
+
period_finish,
|
|
316
370
|
):
|
|
317
|
-
|
|
318
|
-
|
|
371
|
+
actual_elems = {}
|
|
372
|
+
vels = {}
|
|
373
|
+
val_elems = {}
|
|
374
|
+
virtual_bill = {"problem": "", "elements": vels}
|
|
319
375
|
market_role_code = contract.market_role.code
|
|
320
376
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
377
|
+
vals = {
|
|
378
|
+
"supply_id": supply.id,
|
|
379
|
+
"period_start": period_start,
|
|
380
|
+
"period_finish": period_finish,
|
|
381
|
+
"contract_id": contract.id,
|
|
382
|
+
"contract_name": contract.name,
|
|
383
|
+
"market_role_code": contract.market_role.code,
|
|
384
|
+
"elements": val_elems,
|
|
385
|
+
"virtual_net_gbp": 0,
|
|
386
|
+
"actual_net_gbp": 0,
|
|
387
|
+
"actual_bills": [],
|
|
388
|
+
"problem": "",
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for bill in sess.scalars(
|
|
392
|
+
select(Bill)
|
|
393
|
+
.join(Batch)
|
|
394
|
+
.where(
|
|
395
|
+
Bill.supply == supply,
|
|
396
|
+
Bill.start_date <= period_finish,
|
|
397
|
+
Bill.finish_date >= period_start,
|
|
398
|
+
Batch.contract == contract,
|
|
335
399
|
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
400
|
+
):
|
|
401
|
+
if _get_bill_status(sess, bill_statuses, bill) is not None:
|
|
402
|
+
actual_bill = {
|
|
403
|
+
"id": bill.id,
|
|
404
|
+
"start_date": bill.start_date,
|
|
405
|
+
"finish_date": bill.finish_date,
|
|
406
|
+
"problem": "",
|
|
407
|
+
"net": bill.net,
|
|
408
|
+
"vat": bill.vat,
|
|
409
|
+
"gross": bill.gross,
|
|
410
|
+
"kwh": bill.kwh,
|
|
411
|
+
"breakdown": bill.breakdown,
|
|
412
|
+
"batch_id": bill.batch_id,
|
|
413
|
+
"batch_reference": bill.batch.reference,
|
|
414
|
+
}
|
|
349
415
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
416
|
+
read_dict = {}
|
|
417
|
+
for read in bill.reads:
|
|
418
|
+
gen_start = read.present_date.replace(hour=0).replace(minute=0)
|
|
419
|
+
gen_finish = gen_start + relativedelta(days=1) - HH
|
|
420
|
+
msn_match = False
|
|
421
|
+
read_msn = read.msn
|
|
422
|
+
for read_era in supply.find_eras(sess, gen_start, gen_finish):
|
|
423
|
+
if read_msn == read_era.msn:
|
|
424
|
+
msn_match = True
|
|
425
|
+
break
|
|
355
426
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
key = str(dt) + "-" + read.msn
|
|
361
|
-
try:
|
|
362
|
-
if typ != read_dict[key]:
|
|
363
|
-
virtual_bill[
|
|
364
|
-
"problem"
|
|
365
|
-
] += f" Reads taken on {dt} have differing read types."
|
|
366
|
-
except KeyError:
|
|
367
|
-
read_dict[key] = typ
|
|
368
|
-
|
|
369
|
-
bill_start = bill.start_date
|
|
370
|
-
bill_finish = bill.finish_date
|
|
371
|
-
|
|
372
|
-
covered_start = bill_start
|
|
373
|
-
covered_finish = bill_start
|
|
374
|
-
covered_bdown = {
|
|
375
|
-
"sum-msp-kwh": 0,
|
|
376
|
-
"net-gbp": 0,
|
|
377
|
-
"vat-gbp": 0,
|
|
378
|
-
"gross-gbp": 0,
|
|
379
|
-
"problem": "",
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
vb_elems = set()
|
|
383
|
-
enlarged = True
|
|
384
|
-
|
|
385
|
-
while enlarged:
|
|
386
|
-
enlarged = False
|
|
387
|
-
covered_elems = find_elements(bill)
|
|
388
|
-
covered_bills = dict(
|
|
389
|
-
(b.id, b)
|
|
390
|
-
for b in sess.scalars(
|
|
391
|
-
select(Bill)
|
|
392
|
-
.join(Batch)
|
|
393
|
-
.where(
|
|
394
|
-
Bill.supply == supply,
|
|
395
|
-
Bill.start_date <= covered_finish,
|
|
396
|
-
Bill.finish_date >= covered_start,
|
|
397
|
-
Batch.contract == contract,
|
|
427
|
+
if not msn_match:
|
|
428
|
+
virtual_bill["problem"] += (
|
|
429
|
+
f"The MSN {read_msn} of the register read {read.id} "
|
|
430
|
+
f"doesn't match the MSN of the era."
|
|
398
431
|
)
|
|
399
|
-
.order_by(Bill.start_date, Bill.issue_date)
|
|
400
|
-
)
|
|
401
|
-
)
|
|
402
|
-
while True:
|
|
403
|
-
to_del = None
|
|
404
|
-
for a, b in combinations(covered_bills.values(), 2):
|
|
405
|
-
if all(
|
|
406
|
-
(
|
|
407
|
-
a.start_date == b.start_date,
|
|
408
|
-
a.finish_date == b.finish_date,
|
|
409
|
-
a.net == -1 * b.net,
|
|
410
|
-
a.vat == -1 * b.vat,
|
|
411
|
-
a.gross == -1 * b.gross,
|
|
412
|
-
)
|
|
413
|
-
):
|
|
414
|
-
to_del = (a.id, b.id)
|
|
415
|
-
break
|
|
416
|
-
if to_del is None:
|
|
417
|
-
break
|
|
418
|
-
else:
|
|
419
|
-
for k in to_del:
|
|
420
|
-
del covered_bills[k]
|
|
421
|
-
bill_ids.discard(k)
|
|
422
|
-
|
|
423
|
-
for k, covered_bill in tuple(covered_bills.items()):
|
|
424
|
-
elems = find_elements(covered_bill)
|
|
425
|
-
if elems.isdisjoint(covered_elems):
|
|
426
|
-
if k != bill.id:
|
|
427
|
-
del covered_bills[k]
|
|
428
|
-
continue
|
|
429
|
-
else:
|
|
430
|
-
covered_elems.update(elems)
|
|
431
432
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
433
|
+
for dt, typ in [
|
|
434
|
+
(read.present_date, read.present_type),
|
|
435
|
+
(read.previous_date, read.previous_type),
|
|
436
|
+
]:
|
|
437
|
+
key = f"{dt}-{read.msn}"
|
|
438
|
+
try:
|
|
439
|
+
if typ != read_dict[key]:
|
|
440
|
+
virtual_bill[
|
|
441
|
+
"problem"
|
|
442
|
+
] += f" Reads taken on {dt} have differing read types."
|
|
443
|
+
except KeyError:
|
|
444
|
+
read_dict[key] = typ
|
|
445
|
+
|
|
446
|
+
element_net = sum(el.net for el in bill.elements)
|
|
447
|
+
vals["actual_bills"].append(actual_bill)
|
|
448
|
+
if element_net != bill.net:
|
|
449
|
+
actual_bill["problem"] += (
|
|
450
|
+
f"The Net GBP total of the elements is {element_net} doesn't "
|
|
451
|
+
f"match the bill Net GBP value of {bill.net}. "
|
|
452
|
+
)
|
|
453
|
+
if bill.gross != bill.vat + bill.net:
|
|
454
|
+
actual_bill["problem"] += (
|
|
455
|
+
f"The Gross GBP ({bill.gross}) of the bill isn't equal to "
|
|
456
|
+
f"the Net GBP ({bill.net}) + VAT GBP ({bill.vat}) of the bill."
|
|
457
|
+
)
|
|
436
458
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
enlarged = True
|
|
440
|
-
break
|
|
459
|
+
if len(actual_bill["problem"]) > 0:
|
|
460
|
+
vals["problem"] += "Bills have problems. "
|
|
441
461
|
|
|
442
|
-
|
|
462
|
+
for element in sess.scalars(
|
|
463
|
+
select(Element)
|
|
464
|
+
.join(Bill)
|
|
465
|
+
.join(Batch)
|
|
466
|
+
.where(
|
|
467
|
+
Bill.supply == supply,
|
|
468
|
+
Element.start_date <= period_finish,
|
|
469
|
+
Element.finish_date >= period_start,
|
|
470
|
+
Batch.contract == contract,
|
|
471
|
+
)
|
|
472
|
+
):
|
|
473
|
+
if _get_bill_status(sess, bill_statuses, element.bill) is None:
|
|
443
474
|
continue
|
|
444
475
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if isinstance(v, list):
|
|
470
|
-
try:
|
|
471
|
-
covered_bdown[k].update(set(v))
|
|
472
|
-
except KeyError:
|
|
473
|
-
covered_bdown[k] = set(v)
|
|
474
|
-
except AttributeError as e:
|
|
475
|
-
raise BadRequest(
|
|
476
|
-
f"For key {k} in {[b.id for b in covered_bills.values()]} "
|
|
477
|
-
f"the value {v} can't be added to the existing value "
|
|
478
|
-
f"{covered_bdown[k]}. {e}"
|
|
479
|
-
)
|
|
480
|
-
else:
|
|
481
|
-
if isinstance(v, Decimal):
|
|
482
|
-
v = float(v)
|
|
483
|
-
try:
|
|
484
|
-
covered_bdown[k] += v
|
|
485
|
-
except KeyError:
|
|
486
|
-
covered_bdown[k] = v
|
|
487
|
-
except TypeError as detail:
|
|
488
|
-
raise BadRequest(
|
|
489
|
-
f"For key {k} in {[b.id for b in covered_bills.values()]} "
|
|
490
|
-
f"the value {v} can't be added to the existing value "
|
|
491
|
-
f"{covered_bdown[k]}. {detail}"
|
|
492
|
-
)
|
|
476
|
+
try:
|
|
477
|
+
actual_elem = actual_elems[element.name]
|
|
478
|
+
except KeyError:
|
|
479
|
+
actual_elem = actual_elems[element.name] = {
|
|
480
|
+
"parts": {"gbp": Decimal("0.00")},
|
|
481
|
+
"elements": [],
|
|
482
|
+
}
|
|
483
|
+
parts = actual_elem["parts"]
|
|
484
|
+
actual_elem["elements"].append(
|
|
485
|
+
{
|
|
486
|
+
"id": element.id,
|
|
487
|
+
"start_date": element.start_date,
|
|
488
|
+
"finish_date": element.finish_date,
|
|
489
|
+
"net": element.net,
|
|
490
|
+
"breakdown": element.breakdown,
|
|
491
|
+
"bill": {
|
|
492
|
+
"id": element.bill.id,
|
|
493
|
+
"batch": {
|
|
494
|
+
"id": element.bill.batch.id,
|
|
495
|
+
"reference": element.bill.batch.reference,
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
}
|
|
499
|
+
)
|
|
493
500
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
covered_elems.add(elem)
|
|
497
|
-
add_gap(
|
|
498
|
-
caches,
|
|
499
|
-
gaps,
|
|
500
|
-
elem,
|
|
501
|
-
covered_bill.start_date,
|
|
502
|
-
covered_bill.finish_date,
|
|
503
|
-
False,
|
|
504
|
-
v,
|
|
505
|
-
)
|
|
501
|
+
parts["gbp"] += element.net
|
|
502
|
+
vals["actual_net_gbp"] += float(element.net)
|
|
506
503
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
):
|
|
511
|
-
primary_covered_bill = covered_bill
|
|
512
|
-
|
|
513
|
-
metered_kwh = 0
|
|
514
|
-
for era in (
|
|
515
|
-
sess.query(Era)
|
|
516
|
-
.filter(
|
|
517
|
-
Era.supply == supply,
|
|
518
|
-
Era.start_date <= covered_finish,
|
|
519
|
-
or_(Era.finish_date == null(), Era.finish_date >= covered_start),
|
|
520
|
-
)
|
|
521
|
-
.distinct()
|
|
522
|
-
.options(
|
|
523
|
-
joinedload(Era.channels),
|
|
524
|
-
joinedload(Era.cop),
|
|
525
|
-
joinedload(Era.dc_contract),
|
|
526
|
-
joinedload(Era.exp_llfc),
|
|
527
|
-
joinedload(Era.exp_llfc).joinedload(Llfc.voltage_level),
|
|
528
|
-
joinedload(Era.exp_supplier_contract),
|
|
529
|
-
joinedload(Era.imp_llfc),
|
|
530
|
-
joinedload(Era.imp_llfc).joinedload(Llfc.voltage_level),
|
|
531
|
-
joinedload(Era.imp_supplier_contract),
|
|
532
|
-
joinedload(Era.mop_contract),
|
|
533
|
-
joinedload(Era.mtc_participant).joinedload(MtcParticipant.meter_type),
|
|
534
|
-
joinedload(Era.pc),
|
|
535
|
-
joinedload(Era.supply).joinedload(Supply.dno),
|
|
536
|
-
joinedload(Era.supply).joinedload(Supply.gsp_group),
|
|
537
|
-
joinedload(Era.supply).joinedload(Supply.source),
|
|
538
|
-
)
|
|
539
|
-
):
|
|
540
|
-
chunk_start = hh_max(covered_start, era.start_date)
|
|
541
|
-
chunk_finish = hh_min(covered_finish, era.finish_date)
|
|
542
|
-
|
|
543
|
-
if contract not in (
|
|
544
|
-
era.mop_contract,
|
|
545
|
-
era.dc_contract,
|
|
546
|
-
era.imp_supplier_contract,
|
|
547
|
-
era.exp_supplier_contract,
|
|
548
|
-
):
|
|
549
|
-
virtual_bill["problem"] += (
|
|
550
|
-
f"From {hh_format(chunk_start)} to {hh_format(chunk_finish)} "
|
|
551
|
-
f"the contract of the era doesn't match the contract of the bill."
|
|
552
|
-
)
|
|
553
|
-
continue
|
|
504
|
+
for k, v in element.bd.items():
|
|
505
|
+
if isinstance(v, Decimal):
|
|
506
|
+
v = float(v)
|
|
554
507
|
|
|
555
|
-
if
|
|
556
|
-
|
|
557
|
-
else:
|
|
558
|
-
polarity = era.imp_supplier_contract is not None
|
|
508
|
+
if isinstance(v, list):
|
|
509
|
+
v = set(v)
|
|
559
510
|
|
|
560
511
|
try:
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
era.id,
|
|
566
|
-
polarity,
|
|
567
|
-
primary_covered_bill.id,
|
|
568
|
-
)
|
|
569
|
-
data_source = data_sources[ds_key]
|
|
512
|
+
if isinstance(v, set):
|
|
513
|
+
parts[k].update(v)
|
|
514
|
+
else:
|
|
515
|
+
parts[k] += v
|
|
570
516
|
except KeyError:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
era,
|
|
577
|
-
polarity,
|
|
578
|
-
caches,
|
|
579
|
-
primary_covered_bill,
|
|
517
|
+
parts[k] = v
|
|
518
|
+
except TypeError as detail:
|
|
519
|
+
raise BadRequest(
|
|
520
|
+
f"For key {k} in {element.bd} the value {v} can't be added to "
|
|
521
|
+
f"the existing value {parts[k]}. {detail}"
|
|
580
522
|
)
|
|
581
|
-
vbf(data_source)
|
|
582
523
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
524
|
+
first_era = None
|
|
525
|
+
for era in sess.scalars(
|
|
526
|
+
select(Era)
|
|
527
|
+
.where(
|
|
528
|
+
Era.supply == supply,
|
|
529
|
+
Era.start_date <= period_finish,
|
|
530
|
+
or_(Era.finish_date == null(), Era.finish_date >= period_start),
|
|
531
|
+
)
|
|
532
|
+
.order_by(Era.start_date)
|
|
533
|
+
.distinct()
|
|
534
|
+
.options(
|
|
535
|
+
joinedload(Era.channels),
|
|
536
|
+
joinedload(Era.cop),
|
|
537
|
+
joinedload(Era.dc_contract),
|
|
538
|
+
joinedload(Era.exp_llfc),
|
|
539
|
+
joinedload(Era.exp_llfc).joinedload(Llfc.voltage_level),
|
|
540
|
+
joinedload(Era.exp_supplier_contract),
|
|
541
|
+
joinedload(Era.imp_llfc),
|
|
542
|
+
joinedload(Era.imp_llfc).joinedload(Llfc.voltage_level),
|
|
543
|
+
joinedload(Era.imp_supplier_contract),
|
|
544
|
+
joinedload(Era.mop_contract),
|
|
545
|
+
joinedload(Era.mtc_participant).joinedload(MtcParticipant.meter_type),
|
|
546
|
+
joinedload(Era.pc),
|
|
547
|
+
joinedload(Era.supply).joinedload(Supply.dno),
|
|
548
|
+
joinedload(Era.supply).joinedload(Supply.gsp_group),
|
|
549
|
+
joinedload(Era.supply).joinedload(Supply.source),
|
|
550
|
+
)
|
|
551
|
+
).unique():
|
|
552
|
+
first_era = era
|
|
553
|
+
chunk_start = hh_max(period_start, era.start_date)
|
|
554
|
+
chunk_finish = hh_min(period_finish, era.finish_date)
|
|
555
|
+
|
|
556
|
+
if contract not in (
|
|
557
|
+
era.mop_contract,
|
|
558
|
+
era.dc_contract,
|
|
559
|
+
era.imp_supplier_contract,
|
|
560
|
+
era.exp_supplier_contract,
|
|
561
|
+
):
|
|
562
|
+
virtual_bill["problem"] += (
|
|
563
|
+
f"From {hh_format(chunk_start)} to {hh_format(chunk_finish)} "
|
|
564
|
+
f"the contract of the era doesn't match the contract of the bill."
|
|
565
|
+
)
|
|
566
|
+
continue
|
|
596
567
|
|
|
597
|
-
|
|
568
|
+
if contract.market_role.code == "X":
|
|
569
|
+
polarity = contract != era.exp_supplier_contract
|
|
570
|
+
else:
|
|
571
|
+
polarity = era.imp_supplier_contract is not None
|
|
572
|
+
|
|
573
|
+
data_source = SupplySource(
|
|
574
|
+
sess,
|
|
575
|
+
chunk_start,
|
|
576
|
+
chunk_finish,
|
|
577
|
+
forecast_date,
|
|
578
|
+
era,
|
|
579
|
+
polarity,
|
|
580
|
+
caches,
|
|
581
|
+
bill=True,
|
|
582
|
+
)
|
|
583
|
+
vbf(data_source)
|
|
584
|
+
|
|
585
|
+
match market_role_code:
|
|
586
|
+
case "X":
|
|
598
587
|
vb = data_source.supplier_bill
|
|
599
|
-
|
|
600
|
-
elif market_role_code == "C":
|
|
588
|
+
case "C":
|
|
601
589
|
vb = data_source.dc_bill
|
|
602
|
-
|
|
603
|
-
elif market_role_code == "M":
|
|
590
|
+
case "M":
|
|
604
591
|
vb = data_source.mop_bill
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
raise BadRequest("Odd market role.")
|
|
592
|
+
case _:
|
|
593
|
+
raise BadRequest(f"Odd market role {market_role_code}")
|
|
608
594
|
|
|
609
|
-
|
|
595
|
+
for k, v in vb.items():
|
|
596
|
+
if k.endswith("-gbp") and k not in ("net-gbp", "vat-gbp", "gross-gbp"):
|
|
597
|
+
vel_name = k[:-4]
|
|
610
598
|
try:
|
|
611
|
-
|
|
612
|
-
virtual_bill[k].update(v)
|
|
613
|
-
else:
|
|
614
|
-
virtual_bill[k] += v
|
|
599
|
+
vel = vels[vel_name]
|
|
615
600
|
except KeyError:
|
|
616
|
-
|
|
617
|
-
except TypeError as detail:
|
|
618
|
-
raise BadRequest(f"For key {k} and value {v}. {detail}")
|
|
619
|
-
|
|
620
|
-
for dt, bl in vb_hhs.items():
|
|
621
|
-
for k, v in bl.items():
|
|
622
|
-
if k.endswith("-gbp") and v != 0:
|
|
623
|
-
add_gap(caches, gaps, k[:-4], dt, dt, True, v)
|
|
624
|
-
|
|
625
|
-
for k in virtual_bill.keys():
|
|
626
|
-
if k.endswith("-gbp"):
|
|
627
|
-
vb_elems.add(k[:-4])
|
|
628
|
-
|
|
629
|
-
long_map = {}
|
|
630
|
-
vb_keys = set(virtual_bill.keys())
|
|
631
|
-
for elem in sorted(vb_elems, key=len, reverse=True):
|
|
632
|
-
els = long_map[elem] = set()
|
|
633
|
-
for k in tuple(vb_keys):
|
|
634
|
-
if k.startswith(elem + "-"):
|
|
635
|
-
els.add(k)
|
|
636
|
-
vb_keys.remove(k)
|
|
637
|
-
|
|
638
|
-
for elem in vb_elems.difference(covered_elems):
|
|
639
|
-
for k in long_map[elem]:
|
|
640
|
-
del virtual_bill[k]
|
|
641
|
-
|
|
642
|
-
for elem in covered_elems.difference(vb_elems):
|
|
643
|
-
covered_bdown["problem"] += (
|
|
644
|
-
f"The element {elem} is in the covered bills, but not in the "
|
|
645
|
-
f"virtual bill. "
|
|
646
|
-
)
|
|
601
|
+
vel = vels[vel_name] = {"parts": {}, "elements": []}
|
|
647
602
|
|
|
648
|
-
|
|
649
|
-
virtual_bill.pop("gross-gbp", None)
|
|
650
|
-
virtual_bill["net-gbp"] = sum(
|
|
651
|
-
v for k, v in virtual_bill.items() if k.endswith("-gbp") and k != "vat-gbp"
|
|
652
|
-
)
|
|
653
|
-
virtual_bill["gross-gbp"] = virtual_bill["net-gbp"] + virtual_bill.get(
|
|
654
|
-
"vat-gbp", 0
|
|
655
|
-
)
|
|
603
|
+
vals["virtual_net_gbp"] += v
|
|
656
604
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
bill = cbill
|
|
680
|
-
|
|
681
|
-
values = {
|
|
682
|
-
"batch": bill.batch.reference,
|
|
683
|
-
"bill-reference": bill.reference,
|
|
684
|
-
"bill-type": bill.bill_type.code,
|
|
685
|
-
"bill-kwh": bill.kwh,
|
|
686
|
-
"bill-net-gbp": bill.net,
|
|
687
|
-
"bill-vat-gbp": bill.vat,
|
|
688
|
-
"bill-gross-gbp": bill.gross,
|
|
689
|
-
"bill-start-date": bill_start,
|
|
690
|
-
"bill-finish-date": bill_finish,
|
|
691
|
-
"imp-mpan-core": imp_mpan_core,
|
|
692
|
-
"exp-mpan-core": exp_mpan_core,
|
|
693
|
-
"site-code": site_code,
|
|
694
|
-
"site-name": site_name,
|
|
695
|
-
"covered-from": covered_start,
|
|
696
|
-
"covered-to": covered_finish,
|
|
697
|
-
"covered-bills": sorted(covered_bills.keys()),
|
|
698
|
-
"metered-kwh": metered_kwh,
|
|
699
|
-
}
|
|
700
|
-
for title in virtual_bill_titles:
|
|
605
|
+
for k, v in vb.items():
|
|
606
|
+
if k == "problem":
|
|
607
|
+
virtual_bill["problem"] += v
|
|
608
|
+
else:
|
|
609
|
+
for vel_name in sorted(vels.keys(), key=len, reverse=True):
|
|
610
|
+
pref = f"{vel_name}-"
|
|
611
|
+
if k.startswith(pref):
|
|
612
|
+
vel = vels[vel_name]["parts"]
|
|
613
|
+
vel_k = k[len(pref) :]
|
|
614
|
+
try:
|
|
615
|
+
if isinstance(vel[vel_k], set):
|
|
616
|
+
vel[vel_k].update(v)
|
|
617
|
+
else:
|
|
618
|
+
vel[vel_k] += v
|
|
619
|
+
except KeyError:
|
|
620
|
+
vel[vel_k] = v
|
|
621
|
+
except TypeError as detail:
|
|
622
|
+
raise BadRequest(f"For key {vel_k} and value {v}. {detail}")
|
|
623
|
+
|
|
624
|
+
break
|
|
625
|
+
for typ, els in (("virtual", vels), ("actual", actual_elems)):
|
|
626
|
+
for el_k, el in els.items():
|
|
701
627
|
try:
|
|
702
|
-
|
|
703
|
-
del covered_bdown[title]
|
|
628
|
+
val_elem = val_elems[el_k]
|
|
704
629
|
except KeyError:
|
|
705
|
-
|
|
630
|
+
val_elem = val_elems[el_k] = {}
|
|
706
631
|
|
|
707
|
-
|
|
632
|
+
for k, v in el["parts"].items():
|
|
633
|
+
try:
|
|
634
|
+
val_parts = val_elem["parts"]
|
|
635
|
+
except KeyError:
|
|
636
|
+
val_parts = val_elem["parts"] = {}
|
|
708
637
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
virt_val = None
|
|
638
|
+
try:
|
|
639
|
+
val_part = val_parts[k]
|
|
640
|
+
except KeyError:
|
|
641
|
+
val_part = val_parts[k] = {}
|
|
714
642
|
|
|
715
|
-
|
|
643
|
+
val_part[typ] = v
|
|
716
644
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
report_run_titles.append(cov_title)
|
|
739
|
-
if title.endswith("-gbp"):
|
|
740
|
-
if isinstance(virt_val, (int, float, Decimal)):
|
|
741
|
-
if isinstance(cov_val, (int, float, Decimal)):
|
|
742
|
-
diff_val = float(cov_val) - float(virt_val)
|
|
743
|
-
else:
|
|
744
|
-
diff_val = 0 - float(virt_val)
|
|
745
|
-
else:
|
|
746
|
-
diff_val = 0
|
|
747
|
-
|
|
748
|
-
values[f"difference-{title}"] = diff_val
|
|
749
|
-
|
|
750
|
-
t = "difference-tpr-gbp"
|
|
751
|
-
try:
|
|
752
|
-
values[t] += diff_val
|
|
753
|
-
except KeyError:
|
|
754
|
-
values[t] = diff_val
|
|
755
|
-
report_run_titles.append(t)
|
|
756
|
-
|
|
757
|
-
csv_row = []
|
|
758
|
-
for t in titles:
|
|
759
|
-
v = values[t]
|
|
760
|
-
if t == "covered-bills":
|
|
761
|
-
val = " | ".join(str(b) for b in v)
|
|
645
|
+
for el in el["elements"]:
|
|
646
|
+
try:
|
|
647
|
+
elements = val_elem[f"{typ}_elements"]
|
|
648
|
+
except KeyError:
|
|
649
|
+
elements = val_elem[f"{typ}_elements"] = []
|
|
650
|
+
|
|
651
|
+
elements.append(el)
|
|
652
|
+
|
|
653
|
+
for elname, val_elem in val_elems.items():
|
|
654
|
+
for part_name, part in val_elem["parts"].items():
|
|
655
|
+
virtual_part = part.get("virtual", 0)
|
|
656
|
+
actual_part = part.get("actual", 0)
|
|
657
|
+
if isinstance(virtual_part, set) and len(virtual_part) == 1:
|
|
658
|
+
virtual_part = next(iter(virtual_part))
|
|
659
|
+
if isinstance(actual_part, set) and len(actual_part) == 1:
|
|
660
|
+
actual_part = next(iter(actual_part))
|
|
661
|
+
|
|
662
|
+
if virtual_part is None or actual_part is None:
|
|
663
|
+
diff = None
|
|
664
|
+
elif isinstance(virtual_part, Number) and isinstance(actual_part, Number):
|
|
665
|
+
diff = float(actual_part) - float(virtual_part)
|
|
762
666
|
else:
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
csv_row.append(val)
|
|
766
|
-
|
|
767
|
-
for t in report_run_titles:
|
|
768
|
-
if t not in titles:
|
|
769
|
-
csv_row.append(t)
|
|
770
|
-
csv_row.append(csv_make_val(values[t]))
|
|
771
|
-
|
|
772
|
-
writer.writerow(csv_row)
|
|
773
|
-
|
|
774
|
-
values["bill_id"] = bill.id
|
|
775
|
-
values["batch_id"] = bill.batch.id
|
|
776
|
-
values["supply_id"] = supply.id
|
|
777
|
-
values["site_id"] = None if site_code is None else site.id
|
|
778
|
-
for key in tuple(values.keys()):
|
|
779
|
-
for element in sorted(long_map.keys(), key=len, reverse=True):
|
|
780
|
-
if not key.endswith("-gbp"):
|
|
781
|
-
covered_prefix = f"covered-{element}-"
|
|
782
|
-
virtual_prefix = f"virtual-{element}-"
|
|
783
|
-
if key.startswith(covered_prefix):
|
|
784
|
-
part_name = key[len(covered_prefix) :]
|
|
785
|
-
elif key.startswith(virtual_prefix):
|
|
786
|
-
part_name = key[len(virtual_prefix) :]
|
|
787
|
-
else:
|
|
788
|
-
continue
|
|
789
|
-
virtual_part = values.get(f"virtual-{element}-{part_name}", {0})
|
|
790
|
-
covered_part = values.get(f"covered-{element}-{part_name}", {0})
|
|
791
|
-
if isinstance(virtual_part, set) and len(virtual_part) == 1:
|
|
792
|
-
virtual_part = next(iter(virtual_part))
|
|
793
|
-
if isinstance(covered_part, set) and len(covered_part) == 1:
|
|
794
|
-
covered_part = next(iter(covered_part))
|
|
795
|
-
|
|
796
|
-
if isinstance(virtual_part, Number) and isinstance(
|
|
797
|
-
covered_part, Number
|
|
798
|
-
):
|
|
799
|
-
diff = float(covered_part) - float(virtual_part)
|
|
800
|
-
else:
|
|
801
|
-
diff = None
|
|
802
|
-
|
|
803
|
-
values[f"difference-{element}-{part_name}"] = diff
|
|
804
|
-
break
|
|
805
|
-
ReportRun.w_insert_row(
|
|
806
|
-
report_run_id, "", report_run_titles, values, {"is_checked": False}
|
|
807
|
-
)
|
|
667
|
+
diff = "✔" if virtual_part == actual_part else "❌"
|
|
808
668
|
|
|
809
|
-
|
|
810
|
-
Bill.supply == supply,
|
|
811
|
-
Bill.start_date <= covered_finish,
|
|
812
|
-
Bill.finish_date >= covered_start,
|
|
813
|
-
):
|
|
814
|
-
for k, v in loads(bill.breakdown).items():
|
|
815
|
-
if k.endswith("-gbp"):
|
|
816
|
-
add_gap(
|
|
817
|
-
caches,
|
|
818
|
-
gaps,
|
|
819
|
-
k[:-4],
|
|
820
|
-
bill.start_date,
|
|
821
|
-
bill.finish_date,
|
|
822
|
-
False,
|
|
823
|
-
v,
|
|
824
|
-
)
|
|
669
|
+
part["difference"] = diff
|
|
825
670
|
|
|
826
|
-
|
|
827
|
-
|
|
671
|
+
if first_era is None:
|
|
672
|
+
site = None
|
|
673
|
+
else:
|
|
674
|
+
site = first_era.get_physical_site(sess)
|
|
828
675
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
clumps[-1]["finish_date"] + HH == start_date,
|
|
837
|
-
)
|
|
838
|
-
):
|
|
839
|
-
clumps.append(
|
|
840
|
-
{
|
|
841
|
-
"element": element,
|
|
842
|
-
"start_date": start_date,
|
|
843
|
-
"finish_date": start_date,
|
|
844
|
-
"gbp": hhgap["gbp"],
|
|
845
|
-
}
|
|
846
|
-
)
|
|
847
|
-
else:
|
|
848
|
-
clumps[-1]["finish_date"] = start_date
|
|
676
|
+
vals["site_id"] = None if site is None else site.id
|
|
677
|
+
vals["site_code"] = None if site is None else site.code
|
|
678
|
+
vals["site_name"] = None if site is None else site.name
|
|
679
|
+
vals["imp_mpan_core"] = None if first_era is None else era.imp_mpan_core
|
|
680
|
+
vals["exp_mpan_core"] = None if first_era is None else era.exp_mpan_core
|
|
681
|
+
vals["difference_net_gbp"] = vals["actual_net_gbp"] - vals["virtual_net_gbp"]
|
|
682
|
+
vals["problem"] += virtual_bill["problem"]
|
|
849
683
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
684
|
+
return vals
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _process_supply(sess, caches, supply_id, bill_ids, forecast_date, contract, vbf):
|
|
688
|
+
gaps = {}
|
|
689
|
+
bill_statuses = {}
|
|
690
|
+
supply = Supply.get_by_id(sess, supply_id)
|
|
691
|
+
market_role_code = contract.market_role.code # noqa: F841
|
|
692
|
+
|
|
693
|
+
# Find seed gaps
|
|
694
|
+
while len(bill_ids) > 0:
|
|
695
|
+
bill_id = list(sorted(bill_ids))[0]
|
|
696
|
+
bill_ids.remove(bill_id)
|
|
697
|
+
bill = Bill.get_by_id(sess, bill_id)
|
|
698
|
+
if _get_bill_status(sess, bill_statuses, bill) is not None:
|
|
699
|
+
_add_gap(caches, gaps, bill.start_date, bill.finish_date)
|
|
700
|
+
for element in sess.scalars(
|
|
701
|
+
select(Element)
|
|
702
|
+
.join(Bill)
|
|
703
|
+
.join(Batch)
|
|
704
|
+
.where(
|
|
705
|
+
Batch.contract == contract,
|
|
706
|
+
Bill.supply == supply,
|
|
707
|
+
Bill.start_date <= bill.finish_date,
|
|
708
|
+
Bill.finish_date >= bill.start_date,
|
|
709
|
+
)
|
|
710
|
+
):
|
|
711
|
+
_add_gap(caches, gaps, element.start_date, element.finish_date)
|
|
712
|
+
|
|
713
|
+
# Find enlarged gaps
|
|
714
|
+
enlarged = True
|
|
715
|
+
while enlarged:
|
|
716
|
+
enlarged = False
|
|
717
|
+
for gap_start, gap_finish in find_gaps(gaps):
|
|
718
|
+
for element in sess.scalars(
|
|
719
|
+
select(Element)
|
|
720
|
+
.join(Bill)
|
|
721
|
+
.join(Batch)
|
|
722
|
+
.where(
|
|
723
|
+
Bill.supply == supply,
|
|
724
|
+
Bill.start_date <= gap_finish,
|
|
725
|
+
Bill.finish_date >= gap_start,
|
|
726
|
+
Batch.contract == contract,
|
|
727
|
+
)
|
|
728
|
+
):
|
|
729
|
+
if _add_gap(caches, gaps, element.start_date, element.finish_date):
|
|
730
|
+
enlarged = True
|
|
731
|
+
|
|
732
|
+
for period_start, period_finish in find_gaps(gaps):
|
|
733
|
+
yield _process_period(
|
|
734
|
+
sess,
|
|
735
|
+
caches,
|
|
736
|
+
supply,
|
|
737
|
+
contract,
|
|
738
|
+
bill_statuses,
|
|
739
|
+
forecast_date,
|
|
740
|
+
vbf,
|
|
741
|
+
period_start,
|
|
742
|
+
period_finish,
|
|
867
743
|
)
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
vals["bill-start-date"] = hh_format(clump["start_date"])
|
|
872
|
-
vals["bill-finish-date"] = hh_format(clump["finish_date"])
|
|
873
|
-
vals["difference-net-gbp"] = clump["gbp"]
|
|
874
|
-
writer.writerow(csv_make_val(vals[title]) for title in titles)
|
|
875
|
-
|
|
876
|
-
vals["bill_id"] = None
|
|
877
|
-
vals["batch_id"] = None
|
|
878
|
-
vals["supply_id"] = supply.id
|
|
879
|
-
vals["site_id"] = None if site_code is None else site.id
|
|
880
|
-
|
|
881
|
-
ReportRun.w_insert_row(report_run_id, "", titles, vals, {"is_checked": False})
|
|
882
|
-
|
|
883
|
-
# Avoid long-running transactions
|
|
884
|
-
sess.rollback()
|
|
744
|
+
|
|
745
|
+
# Avoid long-running transactions
|
|
746
|
+
sess.rollback()
|