chellow 1757320031.0.0__py3-none-any.whl → 1759411815.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 +514 -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 +187 -179
- chellow/views.py +57 -64
- {chellow-1757320031.0.0.dist-info → chellow-1759411815.0.0.dist-info}/METADATA +2 -2
- {chellow-1757320031.0.0.dist-info → chellow-1759411815.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-1759411815.0.0.dist-info}/WHEEL +0 -0
chellow/reports/report_111.py
CHANGED
|
@@ -4,7 +4,7 @@ import threading
|
|
|
4
4
|
import traceback
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from datetime import datetime as Datetime
|
|
7
|
-
from decimal import Decimal
|
|
7
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
8
8
|
from itertools import combinations
|
|
9
9
|
from numbers import Number
|
|
10
10
|
|
|
@@ -14,11 +14,12 @@ 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
|
|
21
|
+
from zish import ZishException
|
|
22
|
+
|
|
22
23
|
|
|
23
24
|
from chellow.dloads import open_file
|
|
24
25
|
from chellow.e.computer import SupplySource, contract_func
|
|
@@ -26,14 +27,13 @@ from chellow.models import (
|
|
|
26
27
|
Batch,
|
|
27
28
|
Bill,
|
|
28
29
|
Contract,
|
|
30
|
+
Element,
|
|
29
31
|
Era,
|
|
30
32
|
Llfc,
|
|
31
33
|
MtcParticipant,
|
|
32
34
|
RSession,
|
|
33
35
|
RegisterRead,
|
|
34
36
|
ReportRun,
|
|
35
|
-
Site,
|
|
36
|
-
SiteEra,
|
|
37
37
|
Supply,
|
|
38
38
|
User,
|
|
39
39
|
)
|
|
@@ -52,41 +52,49 @@ from chellow.utils import (
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def _add_gap_hh(gaps, hh_start, gap_type):
|
|
56
56
|
try:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
hh = gaps[hh_start]
|
|
58
|
+
match (hh, gap_type):
|
|
59
|
+
case ("middle", _):
|
|
60
|
+
pass
|
|
61
|
+
case (_, "middle"):
|
|
62
|
+
gaps[hh_start] = "middle"
|
|
63
|
+
case ("start", "start"):
|
|
64
|
+
pass
|
|
65
|
+
case ("start", "finish"):
|
|
66
|
+
gaps[hh_start] = "start_finish"
|
|
67
|
+
case ("finish", "finish"):
|
|
68
|
+
pass
|
|
69
|
+
case ("finish", "start"):
|
|
70
|
+
gaps[hh_start] = "start_finish"
|
|
71
|
+
case ("start_finish", "finish"):
|
|
72
|
+
pass
|
|
73
|
+
case ("start_finish", "start"):
|
|
74
|
+
pass
|
|
75
|
+
case _:
|
|
76
|
+
raise BadRequest(f"Gap combination ({hh}, {gap_type}) not recognized.")
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
except KeyError:
|
|
79
|
+
hh = gaps[hh_start] = gap_type
|
|
63
80
|
|
|
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
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
def _add_gap(caches, gaps, start_date, finish_date):
|
|
83
|
+
hhs = hh_range(caches, start_date, finish_date)
|
|
84
|
+
_add_gap_hh(gaps, hhs[0], "start")
|
|
85
|
+
_add_gap_hh(gaps, hhs[-1] + HH, "finish")
|
|
86
|
+
for hh_start in hhs[1:]:
|
|
87
|
+
_add_gap_hh(gaps, hh_start, "middle")
|
|
79
88
|
|
|
80
89
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
+
def find_gaps(gaps):
|
|
91
|
+
if len(gaps) > 0:
|
|
92
|
+
gap_start = None
|
|
93
|
+
for ghh, gtype in sorted(gaps.items()):
|
|
94
|
+
if "finish" in gtype:
|
|
95
|
+
yield gap_start, ghh - HH
|
|
96
|
+
if "start" in gtype:
|
|
97
|
+
gap_start = ghh
|
|
90
98
|
|
|
91
99
|
|
|
92
100
|
def content(
|
|
@@ -112,8 +120,8 @@ def content(
|
|
|
112
120
|
)
|
|
113
121
|
writer = csv.writer(tmp_file, lineterminator="\n")
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
bills_q = (
|
|
124
|
+
select(Bill)
|
|
117
125
|
.order_by(Bill.supply_id, Bill.reference)
|
|
118
126
|
.options(
|
|
119
127
|
joinedload(Bill.supply),
|
|
@@ -125,30 +133,30 @@ def content(
|
|
|
125
133
|
|
|
126
134
|
if len(mpan_cores) > 0:
|
|
127
135
|
mpan_cores = list(map(parse_mpan_core, mpan_cores))
|
|
128
|
-
supply_ids =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.filter(
|
|
136
|
+
supply_ids = sess.scalars(
|
|
137
|
+
select(Era.supply_id)
|
|
138
|
+
.where(
|
|
132
139
|
or_(
|
|
133
140
|
Era.imp_mpan_core.in_(mpan_cores),
|
|
134
141
|
Era.exp_mpan_core.in_(mpan_cores),
|
|
135
142
|
)
|
|
136
143
|
)
|
|
137
144
|
.distinct()
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
).all()
|
|
146
|
+
|
|
147
|
+
bills_q = bills_q.join(Supply).where(Supply.id.in_(supply_ids))
|
|
140
148
|
|
|
141
149
|
if batch_id is not None:
|
|
142
150
|
batch = Batch.get_by_id(sess, batch_id)
|
|
143
|
-
|
|
151
|
+
bills_q = bills_q.where(Bill.batch == batch)
|
|
144
152
|
contract = batch.contract
|
|
145
153
|
elif bill_id is not None:
|
|
146
154
|
bill = Bill.get_by_id(sess, bill_id)
|
|
147
|
-
|
|
155
|
+
bills_q = bills_q.where(Bill.id == bill.id)
|
|
148
156
|
contract = bill.batch.contract
|
|
149
157
|
elif contract_id is not None:
|
|
150
158
|
contract = Contract.get_by_id(sess, contract_id)
|
|
151
|
-
|
|
159
|
+
bills_q = bills_q.join(Batch).where(
|
|
152
160
|
Batch.contract == contract,
|
|
153
161
|
Bill.start_date <= finish_date,
|
|
154
162
|
Bill.finish_date >= start_date,
|
|
@@ -171,51 +179,53 @@ def content(
|
|
|
171
179
|
)
|
|
172
180
|
virtual_bill_titles = virtual_bill_titles_func()
|
|
173
181
|
|
|
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",
|
|
182
|
+
titles = []
|
|
183
|
+
header_titles = [
|
|
184
|
+
"imp_mpan_core",
|
|
185
|
+
"exp_mpan_core",
|
|
186
|
+
"site_code",
|
|
187
|
+
"site_name",
|
|
188
|
+
"period_start",
|
|
189
|
+
"period_finish",
|
|
190
|
+
"actual_net_gbp",
|
|
191
|
+
"virtual_net_gbp",
|
|
192
|
+
"difference_net_gbp",
|
|
192
193
|
]
|
|
194
|
+
titles.extend(header_titles)
|
|
193
195
|
for t in virtual_bill_titles:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
if t not in ("net-gbp", "vat-gbp", "gross-gbp"):
|
|
197
|
+
titles.append("actual-" + t)
|
|
198
|
+
titles.append("virtual-" + t)
|
|
199
|
+
if t.endswith("-gbp"):
|
|
200
|
+
titles.append("difference-" + t)
|
|
198
201
|
|
|
199
202
|
writer.writerow(titles)
|
|
200
203
|
|
|
201
204
|
bill_map = defaultdict(set, {})
|
|
202
|
-
for bill in
|
|
205
|
+
for bill in sess.scalars(bills_q):
|
|
203
206
|
bill_map[bill.supply.id].add(bill.id)
|
|
204
207
|
|
|
205
208
|
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
|
-
|
|
209
|
+
for data in _process_supply(
|
|
210
|
+
sess, caches, supply_id, bill_ids, forecast_date, contract, vbf
|
|
211
|
+
):
|
|
212
|
+
vals = {}
|
|
213
|
+
for title in header_titles:
|
|
214
|
+
vals[title] = data[title]
|
|
215
|
+
for el_name, el in data["elements"].items():
|
|
216
|
+
for part_name, part in el["parts"].items():
|
|
217
|
+
for typ, value in part.items():
|
|
218
|
+
vals[f"{typ}-{el_name}-{part_name}"] = value
|
|
219
|
+
|
|
220
|
+
writer.writerow(csv_make_val(vals.get(title)) for title in titles)
|
|
221
|
+
ReportRun.w_insert_row(
|
|
222
|
+
report_run_id,
|
|
223
|
+
"",
|
|
224
|
+
titles,
|
|
225
|
+
vals,
|
|
226
|
+
{"is_checked": False},
|
|
227
|
+
data=data,
|
|
228
|
+
)
|
|
219
229
|
ReportRun.w_update(report_run_id, "finished")
|
|
220
230
|
|
|
221
231
|
except BadRequest as e:
|
|
@@ -301,584 +311,472 @@ def do_post(sess):
|
|
|
301
311
|
return redirect(f"/report_runs/{report_run.id}", 303)
|
|
302
312
|
|
|
303
313
|
|
|
304
|
-
def
|
|
314
|
+
def _get_bill_status(sess, bill_statuses, bill):
|
|
315
|
+
try:
|
|
316
|
+
bill_status = bill_statuses[bill.id]
|
|
317
|
+
except KeyError:
|
|
318
|
+
covered_bills = dict(
|
|
319
|
+
(b.id, b)
|
|
320
|
+
for b in sess.scalars(
|
|
321
|
+
select(Bill)
|
|
322
|
+
.join(Batch)
|
|
323
|
+
.join(Contract)
|
|
324
|
+
.where(
|
|
325
|
+
Bill.supply == bill.supply,
|
|
326
|
+
Bill.start_date <= bill.finish_date,
|
|
327
|
+
Bill.finish_date >= bill.start_date,
|
|
328
|
+
Contract.market_role == bill.batch.contract.market_role,
|
|
329
|
+
)
|
|
330
|
+
.order_by(Bill.start_date, Bill.issue_date)
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
while True:
|
|
334
|
+
to_del = None
|
|
335
|
+
for a, b in combinations(covered_bills.values(), 2):
|
|
336
|
+
if all(
|
|
337
|
+
(
|
|
338
|
+
a.start_date == b.start_date,
|
|
339
|
+
a.finish_date == b.finish_date,
|
|
340
|
+
a.net == -1 * b.net,
|
|
341
|
+
a.vat == -1 * b.vat,
|
|
342
|
+
a.gross == -1 * b.gross,
|
|
343
|
+
)
|
|
344
|
+
):
|
|
345
|
+
to_del = (a.id, b.id)
|
|
346
|
+
break
|
|
347
|
+
if to_del is None:
|
|
348
|
+
break
|
|
349
|
+
else:
|
|
350
|
+
for k in to_del:
|
|
351
|
+
del covered_bills[k]
|
|
352
|
+
bill_statuses[k] = None
|
|
353
|
+
|
|
354
|
+
for k, v in covered_bills.items():
|
|
355
|
+
bill_statuses[k] = v
|
|
356
|
+
|
|
357
|
+
bill_status = bill_statuses[bill.id]
|
|
358
|
+
|
|
359
|
+
return bill_status
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _process_period(
|
|
305
363
|
sess,
|
|
306
364
|
caches,
|
|
307
|
-
|
|
308
|
-
bill_ids,
|
|
309
|
-
forecast_date,
|
|
365
|
+
supply,
|
|
310
366
|
contract,
|
|
367
|
+
bill_statuses,
|
|
368
|
+
forecast_date,
|
|
311
369
|
vbf,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
titles,
|
|
315
|
-
report_run_id,
|
|
370
|
+
period_start,
|
|
371
|
+
period_finish,
|
|
316
372
|
):
|
|
317
|
-
|
|
318
|
-
|
|
373
|
+
actual_elems = {}
|
|
374
|
+
vels = {}
|
|
375
|
+
val_elems = {}
|
|
376
|
+
virtual_bill = {"problem": "", "elements": vels}
|
|
319
377
|
market_role_code = contract.market_role.code
|
|
320
378
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
379
|
+
vals = {
|
|
380
|
+
"supply_id": supply.id,
|
|
381
|
+
"period_start": period_start,
|
|
382
|
+
"period_finish": period_finish,
|
|
383
|
+
"contract_id": contract.id,
|
|
384
|
+
"contract_name": contract.name,
|
|
385
|
+
"market_role_code": contract.market_role.code,
|
|
386
|
+
"elements": val_elems,
|
|
387
|
+
"virtual_net_gbp": 0,
|
|
388
|
+
"actual_net_gbp": 0,
|
|
389
|
+
"actual_bills": [],
|
|
390
|
+
"problem": "",
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
for bill in sess.scalars(
|
|
394
|
+
select(Bill)
|
|
395
|
+
.join(Batch)
|
|
396
|
+
.where(
|
|
397
|
+
Bill.supply == supply,
|
|
398
|
+
Bill.start_date <= period_finish,
|
|
399
|
+
Bill.finish_date >= period_start,
|
|
400
|
+
Batch.contract == contract,
|
|
335
401
|
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
402
|
+
):
|
|
403
|
+
if _get_bill_status(sess, bill_statuses, bill) is not None:
|
|
404
|
+
actual_bill = {
|
|
405
|
+
"id": bill.id,
|
|
406
|
+
"start_date": bill.start_date,
|
|
407
|
+
"finish_date": bill.finish_date,
|
|
408
|
+
"problem": "",
|
|
409
|
+
"net": bill.net,
|
|
410
|
+
"vat": bill.vat,
|
|
411
|
+
"gross": bill.gross,
|
|
412
|
+
"kwh": bill.kwh,
|
|
413
|
+
"breakdown": bill.breakdown,
|
|
414
|
+
"batch_id": bill.batch_id,
|
|
415
|
+
"batch_reference": bill.batch.reference,
|
|
416
|
+
}
|
|
349
417
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
418
|
+
read_dict = {}
|
|
419
|
+
for read in bill.reads:
|
|
420
|
+
gen_start = read.present_date.replace(hour=0).replace(minute=0)
|
|
421
|
+
gen_finish = gen_start + relativedelta(days=1) - HH
|
|
422
|
+
msn_match = False
|
|
423
|
+
read_msn = read.msn
|
|
424
|
+
for read_era in supply.find_eras(sess, gen_start, gen_finish):
|
|
425
|
+
if read_msn == read_era.msn:
|
|
426
|
+
msn_match = True
|
|
427
|
+
break
|
|
355
428
|
|
|
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,
|
|
429
|
+
if not msn_match:
|
|
430
|
+
virtual_bill["problem"] += (
|
|
431
|
+
f"The MSN {read_msn} of the register read {read.id} "
|
|
432
|
+
f"doesn't match the MSN of the era."
|
|
398
433
|
)
|
|
399
|
-
|
|
434
|
+
|
|
435
|
+
for dt, typ in [
|
|
436
|
+
(read.present_date, read.present_type),
|
|
437
|
+
(read.previous_date, read.previous_type),
|
|
438
|
+
]:
|
|
439
|
+
key = f"{dt}-{read.msn}"
|
|
440
|
+
try:
|
|
441
|
+
if typ != read_dict[key]:
|
|
442
|
+
virtual_bill[
|
|
443
|
+
"problem"
|
|
444
|
+
] += f" Reads taken on {dt} have differing read types."
|
|
445
|
+
except KeyError:
|
|
446
|
+
read_dict[key] = typ
|
|
447
|
+
|
|
448
|
+
element_net = sum(el.net for el in bill.elements)
|
|
449
|
+
vals["actual_bills"].append(actual_bill)
|
|
450
|
+
if element_net != bill.net:
|
|
451
|
+
actual_bill["problem"] += (
|
|
452
|
+
f"The Net GBP total of the elements is {element_net} doesn't "
|
|
453
|
+
f"match the bill Net GBP value of {bill.net}. "
|
|
454
|
+
)
|
|
455
|
+
if bill.gross != bill.vat + bill.net:
|
|
456
|
+
actual_bill["problem"] += (
|
|
457
|
+
f"The Gross GBP ({bill.gross}) of the bill isn't equal to "
|
|
458
|
+
f"the Net GBP ({bill.net}) + VAT GBP ({bill.vat}) of the bill."
|
|
400
459
|
)
|
|
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
460
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
enlarged = True
|
|
435
|
-
break
|
|
461
|
+
vat_net = Decimal("0.00")
|
|
462
|
+
vat_vat = Decimal("0.00")
|
|
436
463
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
464
|
+
try:
|
|
465
|
+
bd = bill.bd
|
|
466
|
+
|
|
467
|
+
if "vat" in bd:
|
|
468
|
+
for vat_percentage, vat_vals in bd["vat"].items():
|
|
469
|
+
calc_vat = Decimal(
|
|
470
|
+
float(vat_percentage) / 100 * float(vat_vals["net"])
|
|
471
|
+
).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
472
|
+
if calc_vat != vat_vals["vat"]:
|
|
473
|
+
actual_bill["problem"] += (
|
|
474
|
+
f"The VAT at {vat_percentage}% on the net amount "
|
|
475
|
+
f"{vat_vals['net']} is calculated to be {calc_vat}, "
|
|
476
|
+
f"which is different from the value in the bill of "
|
|
477
|
+
f"{vat_vals['vat']}"
|
|
478
|
+
)
|
|
479
|
+
vat_net += vat_vals["net"]
|
|
480
|
+
vat_vat += vat_vals["vat"]
|
|
481
|
+
except ZishException as e:
|
|
482
|
+
actual_bill["problem"] += f"Problem parsing the breakdown: {e}"
|
|
483
|
+
|
|
484
|
+
if vat_net != bill.net:
|
|
485
|
+
actual_bill["problem"] += (
|
|
486
|
+
f"The total 'net' {vat_net} in the VAT breakdown doesn't "
|
|
487
|
+
f"match the 'net' {bill.net} of the bill."
|
|
488
|
+
)
|
|
489
|
+
if vat_vat != bill.vat:
|
|
490
|
+
actual_bill["problem"] += (
|
|
491
|
+
f"The total VAT {vat_vat} in the VAT breakdown doesn't "
|
|
492
|
+
f"match the VAT {bill.vat} of the bill."
|
|
493
|
+
)
|
|
441
494
|
|
|
442
|
-
|
|
443
|
-
|
|
495
|
+
if len(actual_bill["problem"]) > 0:
|
|
496
|
+
vals["problem"] += "Bills have problems. "
|
|
444
497
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
gaps,
|
|
459
|
-
elem,
|
|
460
|
-
covered_bill.start_date,
|
|
461
|
-
covered_bill.finish_date,
|
|
462
|
-
False,
|
|
463
|
-
val,
|
|
464
|
-
)
|
|
465
|
-
for k, v in loads(covered_bill.breakdown).items():
|
|
466
|
-
if k in ("raw_lines", "raw-lines", "vat"):
|
|
467
|
-
continue
|
|
498
|
+
for element in sess.scalars(
|
|
499
|
+
select(Element)
|
|
500
|
+
.join(Bill)
|
|
501
|
+
.join(Batch)
|
|
502
|
+
.where(
|
|
503
|
+
Bill.supply == supply,
|
|
504
|
+
Element.start_date <= period_finish,
|
|
505
|
+
Element.finish_date >= period_start,
|
|
506
|
+
Batch.contract == contract,
|
|
507
|
+
)
|
|
508
|
+
):
|
|
509
|
+
if _get_bill_status(sess, bill_statuses, element.bill) is None:
|
|
510
|
+
continue
|
|
468
511
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
512
|
+
try:
|
|
513
|
+
actual_elem = actual_elems[element.name]
|
|
514
|
+
except KeyError:
|
|
515
|
+
actual_elem = actual_elems[element.name] = {
|
|
516
|
+
"parts": {"gbp": Decimal("0.00")},
|
|
517
|
+
"elements": [],
|
|
518
|
+
}
|
|
519
|
+
parts = actual_elem["parts"]
|
|
520
|
+
actual_elem["elements"].append(
|
|
521
|
+
{
|
|
522
|
+
"id": element.id,
|
|
523
|
+
"start_date": element.start_date,
|
|
524
|
+
"finish_date": element.finish_date,
|
|
525
|
+
"net": element.net,
|
|
526
|
+
"breakdown": element.breakdown,
|
|
527
|
+
"bill": {
|
|
528
|
+
"id": element.bill.id,
|
|
529
|
+
"batch": {
|
|
530
|
+
"id": element.bill.batch.id,
|
|
531
|
+
"reference": element.bill.batch.reference,
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
}
|
|
535
|
+
)
|
|
493
536
|
|
|
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
|
-
)
|
|
537
|
+
parts["gbp"] += element.net
|
|
538
|
+
vals["actual_net_gbp"] += float(element.net)
|
|
506
539
|
|
|
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
|
|
540
|
+
for k, v in element.bd.items():
|
|
541
|
+
if isinstance(v, Decimal):
|
|
542
|
+
v = float(v)
|
|
554
543
|
|
|
555
|
-
if
|
|
556
|
-
|
|
557
|
-
else:
|
|
558
|
-
polarity = era.imp_supplier_contract is not None
|
|
544
|
+
if isinstance(v, list):
|
|
545
|
+
v = set(v)
|
|
559
546
|
|
|
560
547
|
try:
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
era.id,
|
|
566
|
-
polarity,
|
|
567
|
-
primary_covered_bill.id,
|
|
568
|
-
)
|
|
569
|
-
data_source = data_sources[ds_key]
|
|
548
|
+
if isinstance(v, set):
|
|
549
|
+
parts[k].update(v)
|
|
550
|
+
else:
|
|
551
|
+
parts[k] += v
|
|
570
552
|
except KeyError:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
era,
|
|
577
|
-
polarity,
|
|
578
|
-
caches,
|
|
579
|
-
primary_covered_bill,
|
|
553
|
+
parts[k] = v
|
|
554
|
+
except TypeError as detail:
|
|
555
|
+
raise BadRequest(
|
|
556
|
+
f"For key {k} in {element.bd} the value {v} can't be added to "
|
|
557
|
+
f"the existing value {parts[k]}. {detail}"
|
|
580
558
|
)
|
|
581
|
-
vbf(data_source)
|
|
582
559
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
560
|
+
first_era = None
|
|
561
|
+
for era in sess.scalars(
|
|
562
|
+
select(Era)
|
|
563
|
+
.where(
|
|
564
|
+
Era.supply == supply,
|
|
565
|
+
Era.start_date <= period_finish,
|
|
566
|
+
or_(Era.finish_date == null(), Era.finish_date >= period_start),
|
|
567
|
+
)
|
|
568
|
+
.order_by(Era.start_date)
|
|
569
|
+
.distinct()
|
|
570
|
+
.options(
|
|
571
|
+
joinedload(Era.channels),
|
|
572
|
+
joinedload(Era.cop),
|
|
573
|
+
joinedload(Era.dc_contract),
|
|
574
|
+
joinedload(Era.exp_llfc),
|
|
575
|
+
joinedload(Era.exp_llfc).joinedload(Llfc.voltage_level),
|
|
576
|
+
joinedload(Era.exp_supplier_contract),
|
|
577
|
+
joinedload(Era.imp_llfc),
|
|
578
|
+
joinedload(Era.imp_llfc).joinedload(Llfc.voltage_level),
|
|
579
|
+
joinedload(Era.imp_supplier_contract),
|
|
580
|
+
joinedload(Era.mop_contract),
|
|
581
|
+
joinedload(Era.mtc_participant).joinedload(MtcParticipant.meter_type),
|
|
582
|
+
joinedload(Era.pc),
|
|
583
|
+
joinedload(Era.supply).joinedload(Supply.dno),
|
|
584
|
+
joinedload(Era.supply).joinedload(Supply.gsp_group),
|
|
585
|
+
joinedload(Era.supply).joinedload(Supply.source),
|
|
586
|
+
)
|
|
587
|
+
).unique():
|
|
588
|
+
first_era = era
|
|
589
|
+
chunk_start = hh_max(period_start, era.start_date)
|
|
590
|
+
chunk_finish = hh_min(period_finish, era.finish_date)
|
|
591
|
+
|
|
592
|
+
if contract not in (
|
|
593
|
+
era.mop_contract,
|
|
594
|
+
era.dc_contract,
|
|
595
|
+
era.imp_supplier_contract,
|
|
596
|
+
era.exp_supplier_contract,
|
|
597
|
+
):
|
|
598
|
+
virtual_bill["problem"] += (
|
|
599
|
+
f"From {hh_format(chunk_start)} to {hh_format(chunk_finish)} "
|
|
600
|
+
f"the contract of the era doesn't match the contract of the bill."
|
|
601
|
+
)
|
|
602
|
+
continue
|
|
603
|
+
|
|
604
|
+
if contract.market_role.code == "X":
|
|
605
|
+
polarity = contract != era.exp_supplier_contract
|
|
606
|
+
else:
|
|
607
|
+
polarity = era.imp_supplier_contract is not None
|
|
608
|
+
|
|
609
|
+
data_source = SupplySource(
|
|
610
|
+
sess,
|
|
611
|
+
chunk_start,
|
|
612
|
+
chunk_finish,
|
|
613
|
+
forecast_date,
|
|
614
|
+
era,
|
|
615
|
+
polarity,
|
|
616
|
+
caches,
|
|
617
|
+
bill=True,
|
|
618
|
+
)
|
|
619
|
+
vbf(data_source)
|
|
596
620
|
|
|
597
|
-
|
|
621
|
+
match market_role_code:
|
|
622
|
+
case "X":
|
|
598
623
|
vb = data_source.supplier_bill
|
|
599
|
-
|
|
600
|
-
elif market_role_code == "C":
|
|
624
|
+
case "C":
|
|
601
625
|
vb = data_source.dc_bill
|
|
602
|
-
|
|
603
|
-
elif market_role_code == "M":
|
|
626
|
+
case "M":
|
|
604
627
|
vb = data_source.mop_bill
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
raise BadRequest("Odd market role.")
|
|
628
|
+
case _:
|
|
629
|
+
raise BadRequest(f"Odd market role {market_role_code}")
|
|
608
630
|
|
|
609
|
-
|
|
631
|
+
for k, v in vb.items():
|
|
632
|
+
if k.endswith("-gbp") and k not in ("net-gbp", "vat-gbp", "gross-gbp"):
|
|
633
|
+
vel_name = k[:-4]
|
|
610
634
|
try:
|
|
611
|
-
|
|
612
|
-
virtual_bill[k].update(v)
|
|
613
|
-
else:
|
|
614
|
-
virtual_bill[k] += v
|
|
635
|
+
vel = vels[vel_name]
|
|
615
636
|
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
|
-
)
|
|
637
|
+
vel = vels[vel_name] = {"parts": {}, "elements": []}
|
|
647
638
|
|
|
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
|
-
)
|
|
639
|
+
vals["virtual_net_gbp"] += v
|
|
656
640
|
|
|
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:
|
|
641
|
+
for k, v in vb.items():
|
|
642
|
+
if k == "problem":
|
|
643
|
+
virtual_bill["problem"] += v
|
|
644
|
+
else:
|
|
645
|
+
for vel_name in sorted(vels.keys(), key=len, reverse=True):
|
|
646
|
+
pref = f"{vel_name}-"
|
|
647
|
+
if k.startswith(pref):
|
|
648
|
+
vel = vels[vel_name]["parts"]
|
|
649
|
+
vel_k = k[len(pref) :]
|
|
650
|
+
try:
|
|
651
|
+
if isinstance(vel[vel_k], set):
|
|
652
|
+
vel[vel_k].update(v)
|
|
653
|
+
else:
|
|
654
|
+
vel[vel_k] += v
|
|
655
|
+
except KeyError:
|
|
656
|
+
vel[vel_k] = v
|
|
657
|
+
except TypeError as detail:
|
|
658
|
+
raise BadRequest(f"For key {vel_k} and value {v}. {detail}")
|
|
659
|
+
|
|
660
|
+
break
|
|
661
|
+
for typ, els in (("virtual", vels), ("actual", actual_elems)):
|
|
662
|
+
for el_k, el in els.items():
|
|
701
663
|
try:
|
|
702
|
-
|
|
703
|
-
del covered_bdown[title]
|
|
664
|
+
val_elem = val_elems[el_k]
|
|
704
665
|
except KeyError:
|
|
705
|
-
|
|
666
|
+
val_elem = val_elems[el_k] = {}
|
|
706
667
|
|
|
707
|
-
|
|
668
|
+
for k, v in el["parts"].items():
|
|
669
|
+
try:
|
|
670
|
+
val_parts = val_elem["parts"]
|
|
671
|
+
except KeyError:
|
|
672
|
+
val_parts = val_elem["parts"] = {}
|
|
708
673
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
virt_val = None
|
|
674
|
+
try:
|
|
675
|
+
val_part = val_parts[k]
|
|
676
|
+
except KeyError:
|
|
677
|
+
val_part = val_parts[k] = {}
|
|
714
678
|
|
|
715
|
-
|
|
679
|
+
val_part[typ] = v
|
|
716
680
|
|
|
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)
|
|
681
|
+
for el in el["elements"]:
|
|
682
|
+
try:
|
|
683
|
+
elements = val_elem[f"{typ}_elements"]
|
|
684
|
+
except KeyError:
|
|
685
|
+
elements = val_elem[f"{typ}_elements"] = []
|
|
686
|
+
|
|
687
|
+
elements.append(el)
|
|
688
|
+
|
|
689
|
+
for elname, val_elem in val_elems.items():
|
|
690
|
+
for part_name, part in val_elem["parts"].items():
|
|
691
|
+
virtual_part = part.get("virtual", 0)
|
|
692
|
+
actual_part = part.get("actual", 0)
|
|
693
|
+
if isinstance(virtual_part, set) and len(virtual_part) == 1:
|
|
694
|
+
virtual_part = next(iter(virtual_part))
|
|
695
|
+
if isinstance(actual_part, set) and len(actual_part) == 1:
|
|
696
|
+
actual_part = next(iter(actual_part))
|
|
697
|
+
|
|
698
|
+
if virtual_part is None or actual_part is None:
|
|
699
|
+
diff = None
|
|
700
|
+
elif isinstance(virtual_part, Number) and isinstance(actual_part, Number):
|
|
701
|
+
diff = float(actual_part) - float(virtual_part)
|
|
762
702
|
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
|
-
)
|
|
703
|
+
diff = "✔" if virtual_part == actual_part else "❌"
|
|
808
704
|
|
|
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
|
-
)
|
|
705
|
+
part["difference"] = diff
|
|
825
706
|
|
|
826
|
-
|
|
827
|
-
|
|
707
|
+
if first_era is None:
|
|
708
|
+
site = None
|
|
709
|
+
else:
|
|
710
|
+
site = first_era.get_physical_site(sess)
|
|
828
711
|
|
|
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
|
|
712
|
+
vals["site_id"] = None if site is None else site.id
|
|
713
|
+
vals["site_code"] = None if site is None else site.code
|
|
714
|
+
vals["site_name"] = None if site is None else site.name
|
|
715
|
+
vals["imp_mpan_core"] = None if first_era is None else era.imp_mpan_core
|
|
716
|
+
vals["exp_mpan_core"] = None if first_era is None else era.exp_mpan_core
|
|
717
|
+
vals["difference_net_gbp"] = vals["actual_net_gbp"] - vals["virtual_net_gbp"]
|
|
718
|
+
vals["problem"] += virtual_bill["problem"]
|
|
849
719
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
720
|
+
return vals
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _process_supply(sess, caches, supply_id, bill_ids, forecast_date, contract, vbf):
|
|
724
|
+
gaps = {}
|
|
725
|
+
bill_statuses = {}
|
|
726
|
+
supply = Supply.get_by_id(sess, supply_id)
|
|
727
|
+
market_role_code = contract.market_role.code # noqa: F841
|
|
728
|
+
|
|
729
|
+
# Find seed gaps
|
|
730
|
+
while len(bill_ids) > 0:
|
|
731
|
+
bill_id = list(sorted(bill_ids))[0]
|
|
732
|
+
bill_ids.remove(bill_id)
|
|
733
|
+
bill = Bill.get_by_id(sess, bill_id)
|
|
734
|
+
if _get_bill_status(sess, bill_statuses, bill) is not None:
|
|
735
|
+
_add_gap(caches, gaps, bill.start_date, bill.finish_date)
|
|
736
|
+
for element in sess.scalars(
|
|
737
|
+
select(Element)
|
|
738
|
+
.join(Bill)
|
|
739
|
+
.join(Batch)
|
|
740
|
+
.where(
|
|
741
|
+
Batch.contract == contract,
|
|
742
|
+
Bill.supply == supply,
|
|
743
|
+
Bill.start_date <= bill.finish_date,
|
|
744
|
+
Bill.finish_date >= bill.start_date,
|
|
745
|
+
)
|
|
746
|
+
):
|
|
747
|
+
_add_gap(caches, gaps, element.start_date, element.finish_date)
|
|
748
|
+
|
|
749
|
+
# Find enlarged gaps
|
|
750
|
+
enlarged = True
|
|
751
|
+
while enlarged:
|
|
752
|
+
enlarged = False
|
|
753
|
+
for gap_start, gap_finish in find_gaps(gaps):
|
|
754
|
+
for element in sess.scalars(
|
|
755
|
+
select(Element)
|
|
756
|
+
.join(Bill)
|
|
757
|
+
.join(Batch)
|
|
758
|
+
.where(
|
|
759
|
+
Bill.supply == supply,
|
|
760
|
+
Bill.start_date <= gap_finish,
|
|
761
|
+
Bill.finish_date >= gap_start,
|
|
762
|
+
Batch.contract == contract,
|
|
763
|
+
)
|
|
764
|
+
):
|
|
765
|
+
if _add_gap(caches, gaps, element.start_date, element.finish_date):
|
|
766
|
+
enlarged = True
|
|
767
|
+
|
|
768
|
+
for period_start, period_finish in find_gaps(gaps):
|
|
769
|
+
yield _process_period(
|
|
770
|
+
sess,
|
|
771
|
+
caches,
|
|
772
|
+
supply,
|
|
773
|
+
contract,
|
|
774
|
+
bill_statuses,
|
|
775
|
+
forecast_date,
|
|
776
|
+
vbf,
|
|
777
|
+
period_start,
|
|
778
|
+
period_finish,
|
|
867
779
|
)
|
|
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()
|
|
780
|
+
|
|
781
|
+
# Avoid long-running transactions
|
|
782
|
+
sess.rollback()
|