chellow 1738691248.0.0__py3-none-any.whl → 1740066311.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/computer.py CHANGED
@@ -119,11 +119,15 @@ def contract_func(caches, contract, func_name):
119
119
  return ns.get(func_name, None)
120
120
 
121
121
 
122
- def non_core_rate(sess, caches, contract_id_or_name, date):
123
- return hh_rate(sess, caches, contract_id_or_name, date, market_role_code="Z")
122
+ def non_core_rate(sess, caches, contract_id_or_name, date, exact=False):
123
+ return hh_rate(
124
+ sess, caches, contract_id_or_name, date, market_role_code="Z", exact=exact
125
+ )
124
126
 
125
127
 
126
- def hh_rate(sess, caches, contract_id_or_name, date, market_role_code=None):
128
+ def hh_rate(
129
+ sess, caches, contract_id_or_name, date, market_role_code=None, exact=False
130
+ ):
127
131
  try:
128
132
  ccache = caches["computer"]
129
133
  except KeyError:
@@ -181,18 +185,20 @@ def hh_rate(sess, caches, contract_id_or_name, date, market_role_code=None):
181
185
  )
182
186
 
183
187
  if rs is None:
184
- rs = (
188
+ last_rs = (
185
189
  sess.query(RateScript)
186
190
  .filter(RateScript.contract == contract)
187
191
  .order_by(RateScript.start_date.desc())
188
192
  .first()
189
193
  )
190
- if date < rs.start_date:
194
+ if date < last_rs.start_date:
191
195
  cstart = year_before
192
- cfinish = min(year_after, rs.start_date - HH)
196
+ cfinish = min(year_after, last_rs.start_date - HH)
193
197
  else:
194
- cstart = max(rs.finish_date + HH, year_before)
198
+ cstart = max(last_rs.finish_date + HH, year_before)
195
199
  cfinish = year_after
200
+ if not exact:
201
+ rs = last_rs
196
202
  else:
197
203
  cstart = max(rs.start_date, year_before)
198
204
  if rs.finish_date is None:
@@ -200,23 +206,26 @@ def hh_rate(sess, caches, contract_id_or_name, date, market_role_code=None):
200
206
  else:
201
207
  cfinish = min(rs.finish_date, year_after)
202
208
 
203
- market_role_code = rs.contract.market_role.code
204
- if market_role_code == "M":
205
- seg = "/e/mop_rate_scripts/"
206
- elif market_role_code == "C":
207
- seg = "/e/dc_rate_scripts/"
208
- elif market_role_code == "X":
209
- seg = "/e/supplier_rate_scripts/"
210
- elif market_role_code == "Z":
211
- seg = "/non_core_rate_scripts/"
212
- elif market_role_code == "R":
213
- seg = "/e/dno_rate_scripts/"
209
+ if rs is None:
210
+ vals = None
214
211
  else:
215
- raise Exception(
216
- f"The market role code {market_role_code} isn't recognized."
217
- )
212
+ market_role_code = rs.contract.market_role.code
213
+ if market_role_code == "M":
214
+ seg = "/e/mop_rate_scripts/"
215
+ elif market_role_code == "C":
216
+ seg = "/e/dc_rate_scripts/"
217
+ elif market_role_code == "X":
218
+ seg = "/e/supplier_rate_scripts/"
219
+ elif market_role_code == "Z":
220
+ seg = "/non_core_rate_scripts/"
221
+ elif market_role_code == "R":
222
+ seg = "/e/dno_rate_scripts/"
223
+ else:
224
+ raise Exception(
225
+ f"The market role code {market_role_code} isn't recognized."
226
+ )
218
227
 
219
- vals = PropDict(f"the rate script {seg}{rs.id} ", loads(rs.script), [])
228
+ vals = PropDict(f"the rate script {seg}{rs.id} ", loads(rs.script), [])
220
229
  for dt in hh_range(caches, cstart, cfinish):
221
230
  if dt not in cont_cache:
222
231
  cont_cache[dt] = vals
@@ -572,8 +581,10 @@ class DataSource:
572
581
  self.rate_cache = self.caches["computer"]["rates"]
573
582
  return val
574
583
 
575
- def non_core_rate(self, contract_name_or_id, date):
576
- return non_core_rate(self.sess, self.caches, contract_name_or_id, date)
584
+ def non_core_rate(self, contract_name_or_id, date, exact=False):
585
+ return non_core_rate(
586
+ self.sess, self.caches, contract_name_or_id, date, exact=exact
587
+ )
577
588
 
578
589
  def _add_problem(self, problem):
579
590
  self.supplier_bill["problem"] += problem
chellow/e/tlms.py CHANGED
@@ -36,7 +36,11 @@ def hh(data_source, run="DF"):
36
36
  h["tlm"] = tlm = cache[h["start-date"]][gsp_group_code][run]
37
37
  except KeyError:
38
38
  h_start = h["start-date"]
39
- rates = data_source.non_core_rate("tlms", h_start)["tlms"]
39
+ vals = data_source.non_core_rate("tlms", h_start, exact=True)
40
+ if vals is None:
41
+ vals = data_source.non_core_rate("tlms", h["hist-start"])
42
+
43
+ rates = vals["tlms"]
40
44
 
41
45
  key = key_format(h_start)
42
46
  try:
chellow/gas/views.py CHANGED
@@ -170,20 +170,20 @@ def supply_get(g_supply_id):
170
170
  )
171
171
 
172
172
  g_bills = (
173
- g.sess.query(GBill)
174
- .filter(GBill.g_supply == g_supply)
173
+ select(GBill)
174
+ .where(GBill.g_supply == g_supply)
175
175
  .order_by(
176
176
  GBill.start_date.desc(),
177
177
  GBill.issue_date.desc(),
178
178
  GBill.reference.desc(),
179
179
  )
180
180
  )
181
- if g_era.finish_date is not None:
182
- g_bills = g_bills.filter(GBill.start_date <= g_era.finish_date)
181
+ if g_era != g_eras[0]:
182
+ g_bills = g_bills.where(GBill.start_date < g_era.finish_date)
183
183
  if g_era != g_eras[-1]:
184
- g_bills = g_bills.filter(GBill.start_date >= g_era.start_date)
184
+ g_bills = g_bills.where(GBill.start_date >= g_era.start_date)
185
185
 
186
- for g_bill in g_bills:
186
+ for g_bill in g.sess.scalars(g_bills):
187
187
  g_reads = (
188
188
  g.sess.query(GRegisterRead)
189
189
  .filter(GRegisterRead.g_bill == g_bill)
@@ -220,7 +220,7 @@ def supply_get(g_supply_id):
220
220
 
221
221
  RELATIVE_YEAR = relativedelta(years=1)
222
222
 
223
- now = Datetime.utcnow()
223
+ now = utc_datetime_now()
224
224
  triad_year = (now - RELATIVE_YEAR).year if now.month < 3 else now.year
225
225
  this_month_start = Datetime(now.year, now.month, 1)
226
226
  last_month_start = this_month_start - relativedelta(months=1)
chellow/models.py CHANGED
@@ -6416,6 +6416,13 @@ class ReportRun(Base, PersistentClass):
6416
6416
  report_run.update(state)
6417
6417
  wsess.commit()
6418
6418
 
6419
+ @staticmethod
6420
+ def w_update_data(report_run_id, data):
6421
+ with Session() as wsess:
6422
+ report_run = ReportRun.get_by_id(wsess, report_run_id)
6423
+ report_run.update_data(data)
6424
+ wsess.commit()
6425
+
6419
6426
  @staticmethod
6420
6427
  def w_insert_row(report_run_id, tab, titles, values, properties):
6421
6428
  with Session() as wsess:
@@ -16,16 +16,34 @@ from werkzeug.exceptions import BadRequest
16
16
 
17
17
  import chellow.gas.engine
18
18
  from chellow.dloads import open_file
19
- from chellow.models import GBatch, GBill, GContract, GEra, Session, Site, SiteGEra, User
19
+ from chellow.models import (
20
+ GBatch,
21
+ GBill,
22
+ GContract,
23
+ GEra,
24
+ RSession,
25
+ ReportRun,
26
+ Site,
27
+ SiteGEra,
28
+ User,
29
+ )
20
30
  from chellow.utils import csv_make_val, hh_max, hh_min, req_date, req_int, to_utc
21
31
 
22
32
 
23
- def content(g_batch_id, g_bill_id, g_contract_id, start_date, finish_date, user_id):
33
+ def content(
34
+ g_batch_id,
35
+ g_bill_id,
36
+ g_contract_id,
37
+ start_date,
38
+ finish_date,
39
+ user_id,
40
+ report_run_id,
41
+ ):
24
42
  forecast_date = to_utc(Datetime.max)
25
43
  report_context = {}
26
44
  sess = tmp_file = None
27
45
  try:
28
- with Session() as sess:
46
+ with RSession() as sess:
29
47
  user = User.get_by_id(sess, user_id)
30
48
 
31
49
  tmp_file = open_file("g_bill_check.csv", user, mode="w")
@@ -108,13 +126,17 @@ def content(g_batch_id, g_bill_id, g_contract_id, start_date, finish_date, user_
108
126
  vbf,
109
127
  titles,
110
128
  csv_writer,
129
+ report_run_id,
111
130
  )
131
+ ReportRun.w_update(report_run_id, "finished")
112
132
  except BadRequest as e:
113
133
  tmp_file.write(f"Problem: {e.description}")
134
+ ReportRun.w_update(report_run_id, "problem")
114
135
  except BaseException:
115
136
  msg = traceback.format_exc()
116
137
  sys.stderr.write(msg + "\n")
117
138
  tmp_file.write(f"Problem {msg}")
139
+ ReportRun.w_update(report_run_id, "interrupted")
118
140
  finally:
119
141
  tmp_file.close()
120
142
 
@@ -129,6 +151,7 @@ def _process_g_bill_ids(
129
151
  vbf,
130
152
  titles,
131
153
  csv_writer,
154
+ report_run_id,
132
155
  ):
133
156
  g_bill_id = list(sorted(g_bill_ids))[0]
134
157
  g_bill_ids.remove(g_bill_id)
@@ -144,6 +167,7 @@ def _process_g_bill_ids(
144
167
  "covered_bill_ids": [],
145
168
  "virtual_problem": "",
146
169
  }
170
+ site_id = None
147
171
  read_dict = defaultdict(set)
148
172
  for g_read in g_bill.g_reads:
149
173
  if not all(
@@ -264,7 +288,7 @@ def _process_g_bill_ids(
264
288
  else:
265
289
  v = getattr(g_read, title)
266
290
  vals[k].add(v)
267
-
291
+ elements = set()
268
292
  for g_era in sess.execute(
269
293
  select(GEra).where(
270
294
  GEra.g_supply == g_supply,
@@ -283,6 +307,7 @@ def _process_g_bill_ids(
283
307
  .filter(SiteGEra.is_physical == true(), SiteGEra.g_era == g_era)
284
308
  .one()
285
309
  )
310
+ site_id = site.id
286
311
  vals["site_code"] = site.code
287
312
  vals["site_name"] = site.name
288
313
 
@@ -313,6 +338,9 @@ def _process_g_bill_ids(
313
338
  except TypeError as detail:
314
339
  raise BadRequest(f"For key {vk} and value {v}. {detail}")
315
340
 
341
+ if k.endswith("_gbp"):
342
+ elements.add(k[:-4])
343
+
316
344
  if g_bill.id not in covered_bills.keys():
317
345
  g_bill = covered_bills[sorted(covered_bills.keys())[0]]
318
346
 
@@ -324,22 +352,36 @@ def _process_g_bill_ids(
324
352
  vals["mprn"] = g_supply.mprn
325
353
  vals["supply_name"] = g_supply.name
326
354
 
327
- for k, v in vals.items():
328
- if k == "covered_bill_ids":
329
- vals[k] = " | ".join(str(b) for b in v)
330
- else:
331
- vals[k] = csv_make_val(v)
332
-
333
355
  for i, title in enumerate(titles):
334
356
  if title.startswith("difference_"):
335
- try:
336
- covered_val = float(vals[titles[i - 2]])
337
- virtual_val = float(vals[titles[i - 1]])
338
- vals[title] = covered_val - virtual_val
339
- except KeyError:
340
- vals[title] = None
357
+ covered_val = float(vals.get(titles[i - 2], 0))
358
+ virtual_val = float(vals.get(titles[i - 1], 0))
359
+ vals[title] = covered_val - virtual_val
341
360
 
342
361
  csv_writer.writerow([csv_make_val(vals.get(k)) for k in titles])
362
+ vals["g_bill_id"] = g_bill.id
363
+ vals["g_batch_id"] = g_bill.g_batch.id
364
+ vals["g_supply_id"] = g_supply.id
365
+ vals["site_id"] = site_id
366
+ for element in sorted(elements):
367
+ rate_name = f"difference_{element}_rate"
368
+ covered_rates = vals.get(f"covered_{element}_rate", [0])
369
+ virtual_rates = vals.get(f"virtual_{element}_rate", [0])
370
+ if len(covered_rates) == 1 and len(virtual_rates) == 1:
371
+ vals[rate_name] = (
372
+ float(covered_rates.pop()) * 100 - float(virtual_rates.pop()) * 100
373
+ )
374
+ else:
375
+ vals[rate_name] = 0
376
+
377
+ try:
378
+ covered_kwh = float(vals["covered_kwh"])
379
+ virtual_kwh = float(vals["virtual_kwh"])
380
+ vals["difference_kwh"] = covered_kwh - virtual_kwh
381
+ except KeyError:
382
+ vals["difference_kwh"] = None
383
+
384
+ ReportRun.w_insert_row(report_run_id, "", titles, vals, {"is_checked": False})
343
385
 
344
386
 
345
387
  def do_get(sess):
@@ -347,17 +389,39 @@ def do_get(sess):
347
389
 
348
390
  if "g_batch_id" in request.values:
349
391
  g_batch_id = req_int("g_batch_id")
392
+ g_batch = GBatch.get_by_id(g.sess, g_batch_id)
393
+ run_g_contract_id = g_batch.g_contract.id
350
394
  elif "g_bill_id" in request.values:
351
395
  g_bill_id = req_int("g_bill_id")
396
+ g_bill = GBill.get_by_id(g.sess, g_bill_id)
397
+ run_g_contract_id = g_bill.g_batch.g_contract.id
352
398
  elif "g_contract_id" in request.values:
353
399
  g_contract_id = req_int("g_contract_id")
354
400
  start_date = req_date("start_date")
355
401
  finish_date = req_date("finish_date")
402
+ run_g_contract_id = g_contract_id
356
403
  else:
357
404
  raise BadRequest(
358
405
  "The bill check needs a g_batch_id, g_bill_id or g_contract_id."
359
406
  )
360
407
 
361
- args = g_batch_id, g_bill_id, g_contract_id, start_date, finish_date, g.user.id
408
+ report_run = ReportRun.insert(
409
+ sess,
410
+ "g_bill_check",
411
+ g.user,
412
+ "g_bill_check",
413
+ {"g_contract_id": run_g_contract_id},
414
+ )
415
+ sess.commit()
416
+
417
+ args = (
418
+ g_batch_id,
419
+ g_bill_id,
420
+ g_contract_id,
421
+ start_date,
422
+ finish_date,
423
+ g.user.id,
424
+ report_run.id,
425
+ )
362
426
  threading.Thread(target=content, args=args).start()
363
- return redirect("/downloads", 303)
427
+ return redirect(f"/report_runs/{report_run.id}", 303)
@@ -230,7 +230,7 @@ table.sticky {
230
230
  position: relative;
231
231
  }
232
232
 
233
- table.sticky th {
233
+ table.sticky thead {
234
234
  position: sticky;
235
235
  top: 0;
236
236
  background: white;
@@ -0,0 +1,182 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Report Runs &raquo; {{run.id}}
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/report_runs">Report Runs</a> &raquo; {{run.id}}
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ {% if request.method == "GET" and request.values.delete %}
13
+ <form method="post" action="/report_runs/{{run.id}}">
14
+ <fieldset>
15
+ <legend>Are you sure you want to delete this report run?</legend>
16
+ <input type="submit" name="delete" value="delete">
17
+ <a href="/report_runs/{{run.id}}">Cancel</a>
18
+ </fieldset>
19
+ </form>
20
+ {% else %}
21
+ <table>
22
+ <caption>Bill Check</caption>
23
+ <thead>
24
+ <tr>
25
+ <th>Date Created</th>
26
+ <th>Created By</th>
27
+ <th>Title</th>
28
+ <th>State</th>
29
+ <th>Number Of Rows</th>
30
+ <th>Sum Of Difference GBP</th>
31
+ <th>Delete</th>
32
+ <th>Download Spreadsheet</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ <tr>
37
+ <td>{{run.date_created|hh_format}}</td>
38
+ <td>{{run.creator}}</td>
39
+ <td>{{run.title}}</td>
40
+ <td>{{run.state}}</td>
41
+ <td>{{rows|length}} (truncated at {{ROW_LIMIT}})</td>
42
+ <td>
43
+ {% if 'sum_difference' in summary and summary.sum_difference is not none %}
44
+ {{"%.2f"|format(summary.sum_difference)}}</td>
45
+ {% endif %}
46
+ <td>
47
+ <form action="/report_runs/{{run.id}}">
48
+ <fieldset style="border: none;">
49
+ <input type="submit" name="delete" value="Delete">
50
+ </fieldset>
51
+ </form>
52
+ </td>
53
+ <td><a href="/report_runs/{{run.id}}/spreadsheet">Download</a></td>
54
+ </tr>
55
+ </tbody>
56
+ </table>
57
+
58
+ {% if summary.missing_bills|length > 0 %}
59
+ <table class="sticky">
60
+ <caption>
61
+ Some Missing Bills (<a href="/report_runs/{{report_run}}">x</a>)
62
+ </caption>
63
+ <thead>
64
+ <tr>
65
+ <th>View</th>
66
+ <th>Import MPAN Core</th>
67
+ <th>Export MPAN Core</th>
68
+ <th>Start</th>
69
+ <th>Finish</th>
70
+ <th>Problem</th>
71
+ <th>Estimated Difference GBP</th>
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ {% for row in rows %}
76
+ <tr>
77
+ <td><a href="/report_run_rows/{{row.id}}">View</a></td>
78
+ {% for title in row.data.titles %}
79
+ <td>{{row.data['values'][title]}}</td>
80
+ {% endfor %}
81
+ </tr>
82
+ {% endfor %}
83
+ </tbody>
84
+ </table>
85
+ {% endif %}
86
+
87
+ {% if show_elements %}
88
+ <table class="sticky">
89
+ <caption>Elements</caption>
90
+ <thead>
91
+ <tr>
92
+ <th>Element</th>
93
+ {% for elname, diff in elements %}
94
+ <th>{{elname}}</th>
95
+ {% endfor %}
96
+ </tr>
97
+ </thead>
98
+ <tbody>
99
+ <tr>
100
+ <td>Sum of differences (GBP)</td>
101
+ {% for elname, diff in elements %}
102
+ {% if diff is not none %}
103
+ <td>{{"%.2f"|format(diff)}}</td>
104
+ {% endif %}
105
+ {% endfor %}
106
+ </tr>
107
+ </tbody>
108
+ </table>
109
+ {% endif %}
110
+
111
+ <table class="sticky">
112
+ <caption>
113
+ Rows - Ordered By
114
+ {% if sort_absolute %}
115
+ Absolute
116
+ {% endif %}
117
+ {{order_by}}
118
+ </caption>
119
+ <thead>
120
+ <tr>
121
+ <th rowspan="2">View</th>
122
+ <th rowspan="2">Batch</th>
123
+ <th rowspan="2">Site</th>
124
+ <th rowspan="2">MPRN</th>
125
+ <th rowspan="2">Covered Bills</th>
126
+ <th rowspan="2">Covered Start</th>
127
+ <th rowspan="2">Covered Finish</th>
128
+ <th colspan="{{columns|length}}">Difference</th>
129
+ <th rowspan="2">Problem</th>
130
+ </tr>
131
+ <tr>
132
+ {% for column in columns %}
133
+ <th>
134
+ {{' '.join(column.split('_')[1:])}}
135
+ {% if order_by == column %}
136
+
137
+ {% else %}
138
+ <a href="/report_runs/{{run.id}}?order_by={{column}}">↑</a>
139
+ {% endif %}
140
+ </th>
141
+ {% endfor %}
142
+ </tr>
143
+ </thead>
144
+ <tbody>
145
+ {% for row in rows %}
146
+ {% set values = row.data['values'] %}
147
+ <tr>
148
+ <td><a href="/report_run_rows/{{row.id}}">View</a></td>
149
+ <td><a href="/g/batches/{{values.g_batch_id}}">{{values.batch}}</a></td>
150
+ <td>
151
+ <a href="/sites/{{values.site_id}}" title="{{values.site_name}}">{{values.site_code}}</a>
152
+ </td>
153
+ <td>
154
+ <a href="/g/supplies/{{values.g_supply_id}}">{{values.mprn}}</a>
155
+ </td>
156
+ <td>
157
+ {% if values['covered_bill_ids'] is not none %}
158
+ {% for bill_id in values['covered_bill_ids'] %}
159
+ <a href="/g/bills/{{bill_id}}">{{bill_id}}</a>
160
+ {% if bill_id == values.g_bill_id %}
161
+ {% endif %}
162
+ {% endfor %}
163
+ {% endif %}
164
+ </td>
165
+ <td><span title="{{values.covered_start}}">{{values.covered_start[:10]}}</span></td>
166
+ <td><span title="{{values.covered_finish}}">{{values.covered_finish[:10]}}</span></td>
167
+ {% for title in columns %}
168
+ <td>
169
+ {% if title in values and values[title] is not none %}
170
+ {{"%.2f"|format(values[title])}}
171
+ {% endif %}
172
+ </td>
173
+ {% endfor %}
174
+ <td>
175
+ {{ values.covered_problem }} {{ values.virtual_problem }}
176
+ </td>
177
+ </tr>
178
+ {% endfor %}
179
+ </tbody>
180
+ </table>
181
+ {% endif %}
182
+ {% endblock %}
@@ -0,0 +1,191 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Report Runs &raquo; {{row.report_run.id}} &raquo; Row {{row.id}}
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/report_runs">Report Runs</a> &raquo;
9
+ <a href="/report_runs/{{row.report_run.id}}">{{row.report_run.id}}</a>
10
+ &raquo; Row {{row.id}}
11
+ {% endblock %}
12
+
13
+ {% block content %}
14
+ {% set values = row.data['values'] %}
15
+ {% set properties = row.data.get('properties', {}) %}
16
+ {% if row.report_run.name == 'bill_check' %}
17
+ <table>
18
+ <caption>Bill Check</caption>
19
+ <thead>
20
+ <tr>
21
+ <th rowspan="2">Batch</th>
22
+ <th colspan="8">Bill</th>
23
+ <th colspan="2">MPAN Core</th>
24
+ <th colspan="2">Site</th>
25
+ <th rowspan="2">Checking</th>
26
+ </tr>
27
+ <tr>
28
+ <th>Reference</th>
29
+ <th>Type</th>
30
+ <th>kWh</th>
31
+ <th>Net GBP</th>
32
+ <th>VAT GBP</th>
33
+ <th>Gross GBP</th>
34
+ <th>Start</th>
35
+ <th>Finish</th>
36
+ <th>Import</th>
37
+ <th>Export</th>
38
+ <th>Code</th>
39
+ <th>Name</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ <tr>
44
+ <td>
45
+ {% if values.batch_id != None %}
46
+ <a href="/e/supplier_batches/{{values.batch_id}}">{{values['batch']}}</a>
47
+ {% endif %}
48
+ </td>
49
+ <td>
50
+ {% if values.bill_id != None %}
51
+ <a href="/e/supplier_bills/{{values.bill_id}}">{{
52
+ values['bill-reference']}}</a>
53
+ {% endif %}
54
+ </td>
55
+ <td>{{values['bill-type']}}</td>
56
+ <td>{{values['bill-kwh']}}</td>
57
+ <td>{{values['bill-net-gbp']}}</td>
58
+ <td>{{values['bill-vat-gbp']}}</td>
59
+ <td>{{values['bill-gross-gbp']}}</td>
60
+ <td>{{values['bill-start-date']}}</td>
61
+ <td>{{values['bill-finish-date']}}</td>
62
+ <td>
63
+ {% if values['imp-mpan-core'] != None %}
64
+ <a href="/e/supplies/{{values.supply_id}}">{{
65
+ values['imp-mpan-core']}}</a>
66
+ {% endif %}
67
+ </td>
68
+ <td>
69
+ {% if values['exp-mpan-core'] != None %}
70
+ <a href="/e/supplies/{{values.supply_id}}">{{
71
+ values['exp-mpan-core']}}</a>
72
+ {% endif %}
73
+ </td>
74
+ <td>
75
+ {% if values.site_id != None %}
76
+ <a href="/sites/{{values.site_id}}">{{
77
+ values['site-code']}}</a>
78
+ {% endif %}
79
+ </td>
80
+ <td>{{values['site-name']}}</td>
81
+ <td>
82
+ <form action="/report_run_rows/{{row.id}}" method="post">
83
+ <fieldset>
84
+ {{input_textarea(
85
+ 'note', properties.note, 5, 40,
86
+ placeholder='Notes on checking go here...')}}
87
+ <br>
88
+ <br>
89
+ <label>Checked?
90
+ {{input_checkbox('is_checked', properties.is_checked)}}
91
+ </label>
92
+ <input type="submit" name="update" value="Update">
93
+ </fieldset>
94
+ </form>
95
+ </td>
96
+ </tr>
97
+ </tbody>
98
+ </table>
99
+
100
+ <table>
101
+ <caption>Covered</caption>
102
+ <thead>
103
+ <tr>
104
+ <th>From</th>
105
+ <th>To</th>
106
+ <th>Bills</th>
107
+ <th>Metered kWh</th>
108
+ <th>Net GBP</th>
109
+ <th>Virtual Net GBP</th>
110
+ <th>Difference GBP</th>
111
+ <th>Problem</th>
112
+ <th>Virtual Problem</th>
113
+ </tr>
114
+ </thead>
115
+ <tbody>
116
+ <tr>
117
+ <td>{{values['covered-from']}}</td>
118
+ <td>{{values['covered-to']}}</td>
119
+ <td>
120
+ {% if values['covered-bills'] != None %}
121
+ <ul>
122
+ {% for bill_id in values['covered-bills'] %}
123
+ <li>
124
+ <a href="/e/supplier_bills/{{bill_id}}">{{bill_id}}</a>
125
+ {% if bill_id == values.bill_id %}
126
+ (This bill)
127
+ {% endif %}
128
+ </li>
129
+ {% endfor %}
130
+ </ul>
131
+ {% endif %}
132
+ </td>
133
+ <td>
134
+ {% if values['metered-kwh'] != None %}
135
+ {{"%.0f"|format(values['metered-kwh'])}}
136
+ {% endif %}
137
+ </td>
138
+ <td>{{values['covered-net-gbp']}}</td>
139
+ <td>
140
+ {% if values['virtual-net-gbp'] != None %}
141
+ {{"%.2f"|format(values['virtual-net-gbp'])}}
142
+ {% endif %}
143
+ </td>
144
+ <td>
145
+ {% if values['difference-net-gbp'] != None %}
146
+ {{"%.2f"|format(values['difference-net-gbp'])}}
147
+ {% endif %}
148
+ </td>
149
+ <td>{{values['covered-problem']}}</td>
150
+ <td>{{values['virtual-problem']}}</td>
151
+ </tr>
152
+ </tbody>
153
+ </table>
154
+
155
+ {% for table in tables %}
156
+ <table>
157
+ <caption>{{table.name}}</caption>
158
+ <thead>
159
+ <tr>
160
+ {% for title in table.titles %}
161
+ <th>{{title}}</th>
162
+ {% endfor %}
163
+ </tr>
164
+ </thead>
165
+ <tbody>
166
+ <tr>
167
+ {% for v in table['values'] %}
168
+ <td>{{v}}</td>
169
+ {% endfor %}
170
+ </tr>
171
+ </tbody>
172
+ </table>
173
+ {% endfor %}
174
+
175
+ <h2>Raw</h2>
176
+ <pre>{{raw_data}}</pre>
177
+
178
+ {% else %}
179
+ <table>
180
+ <caption>Report Run Row</caption>
181
+ <tbody>
182
+ {% for title in row.data.titles %}
183
+ <tr>
184
+ <th>{{title}}</th>
185
+ <td>{{row.data['values'][title]}}</td>
186
+ </tr>
187
+ {% endfor %}
188
+ </tbody>
189
+ </table>
190
+ {% endif %}
191
+ {% endblock %}
chellow/views.py CHANGED
@@ -1365,6 +1365,74 @@ def report_run_get(run_id):
1365
1365
  hide_checked=hide_checked,
1366
1366
  ROW_LIMIT=ROW_LIMIT,
1367
1367
  )
1368
+ elif run.name == "g_bill_check":
1369
+ row = (
1370
+ g.sess.query(ReportRunRow)
1371
+ .filter(ReportRunRow.report_run == run)
1372
+ .order_by(ReportRunRow.id)
1373
+ .first()
1374
+ )
1375
+ elements = []
1376
+ summary = {}
1377
+ if row is None:
1378
+ pass
1379
+
1380
+ else:
1381
+ titles = row.data["titles"]
1382
+ diff_titles = [
1383
+ t for t in titles if t.startswith("difference_") and t.endswith("_gbp")
1384
+ ]
1385
+ diff_selects = [
1386
+ func.sum(ReportRunRow.data["values"][t].as_float()) for t in diff_titles
1387
+ ]
1388
+ sum_diffs = (
1389
+ g.sess.query(*diff_selects).filter(ReportRunRow.report_run == run).one()
1390
+ )
1391
+
1392
+ for t, sum_diff in zip(diff_titles, sum_diffs):
1393
+ elem = t[11:-4]
1394
+ sdiff = 0 if sum_diff is None else sum_diff
1395
+ if elem == "net":
1396
+ summary["sum_difference"] = sdiff
1397
+ else:
1398
+ elements.append((elem, sdiff))
1399
+
1400
+ elements.sort(key=lambda x: abs(x[1]), reverse=True)
1401
+ elements.insert(0, ("net", summary["sum_difference"]))
1402
+
1403
+ if "order_by" in request.values:
1404
+ order_by = req_str("order_by")
1405
+ else:
1406
+ order_by = "difference_net_gbp"
1407
+
1408
+ g_contract = GContract.get_by_id(g.sess, run.data["g_contract_id"])
1409
+ g_contract_props = g_contract.make_properties()
1410
+ props = g_contract_props.get("report_run", {})
1411
+ sort_absolute = props.get("sort_absolute", True)
1412
+ show_elements = props.get("show_elements", True)
1413
+ columns = props.get("columns", [f"difference_{el[0]}_gbp" for el in elements])
1414
+
1415
+ ROW_LIMIT = 200
1416
+ q = select(ReportRunRow).where(ReportRunRow.report_run == run).limit(ROW_LIMIT)
1417
+ if sort_absolute:
1418
+ q = q.order_by(
1419
+ func.abs(ReportRunRow.data["values"][order_by].as_float()).desc()
1420
+ )
1421
+ else:
1422
+ q = q.order_by(ReportRunRow.data["values"][order_by].as_float().desc())
1423
+ rows = g.sess.scalars(q).all()
1424
+ return render_template(
1425
+ "report_run_g_bill_check.html",
1426
+ run=run,
1427
+ rows=rows,
1428
+ summary=summary,
1429
+ elements=elements,
1430
+ order_by=order_by,
1431
+ ROW_LIMIT=ROW_LIMIT,
1432
+ sort_absolute=sort_absolute,
1433
+ show_elements=show_elements,
1434
+ columns=columns,
1435
+ )
1368
1436
 
1369
1437
  elif run.name == "asset_comparison":
1370
1438
  rows = (
@@ -1548,6 +1616,51 @@ def report_run_row_get(row_id):
1548
1616
  return render_template(
1549
1617
  "report_run_row_bill_check.html", row=row, raw_data=raw_data, tables=tables
1550
1618
  )
1619
+ elif row.report_run.name == "g_bill_check":
1620
+ values = row.data["values"]
1621
+ elements = {}
1622
+ for t in row.data["values"].keys():
1623
+
1624
+ if (
1625
+ t.startswith("covered_")
1626
+ or t.startswith("virtual_")
1627
+ or t.startswith("difference_")
1628
+ ) and t not in (
1629
+ "covered_from",
1630
+ "covered_to",
1631
+ "covered_bills",
1632
+ "covered_problem",
1633
+ "virtual_problem",
1634
+ ):
1635
+ toks = t.split("_")
1636
+ name = "_".join(toks[1:-1])
1637
+ try:
1638
+ table = elements[name]
1639
+ except KeyError:
1640
+ table = elements[name] = {"order": 0}
1641
+
1642
+ if "titles" not in table:
1643
+ table["titles"] = []
1644
+ table["titles"].append(toks[0] + "_" + "_".join(toks[2:]))
1645
+ if "values" not in table:
1646
+ table["values"] = []
1647
+ table["values"].append(values[t])
1648
+ if t.startswith("difference_") and t.endswith("-gbp"):
1649
+ table["order"] = abs(values[t])
1650
+
1651
+ for k, v in elements.items():
1652
+ if k == "net":
1653
+ continue
1654
+ v["name"] = k
1655
+ tables.append(v)
1656
+
1657
+ tables.sort(key=lambda t: t["order"], reverse=True)
1658
+ return render_template(
1659
+ "report_run_row_g_bill_check.html",
1660
+ row=row,
1661
+ raw_data=raw_data,
1662
+ tables=tables,
1663
+ )
1551
1664
 
1552
1665
  else:
1553
1666
  return render_template(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1738691248.0.0
3
+ Version: 1740066311.0.0
4
4
  Summary: Web Application for checking UK energy bills.
5
5
  Project-URL: Homepage, https://github.com/WessexWater/chellow
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -5,14 +5,14 @@ chellow/commands.py,sha256=ESBe9ZWj1c3vdZgqMZ9gFvYAB3hRag2R1PzOwuw9yFo,1302
5
5
  chellow/dloads.py,sha256=dixp-O0MF2_mlwrnKx3D9DH09Qu05BjTo0rZfigTjR4,5534
6
6
  chellow/edi_lib.py,sha256=alu20x9ZX06iPfnNI9dEJzuP6RIf4We3Y_M_bl7RrcY,51789
7
7
  chellow/general_import.py,sha256=tcYZK2N7HOFJSkYyy3MMxVBEvMFjfB9pjF0ksujtkFA,65113
8
- chellow/models.py,sha256=9LLROzS6gXZGICk0ITXhCyTtKaIY7HW4bMqWg4Mc-xw,244221
8
+ chellow/models.py,sha256=GVuPnDUGJqPbdD8Z4Ccoiw4ZLeuEXFgAkrTLS_znTQw,244452
9
9
  chellow/national_grid.py,sha256=czwIZqzJndSGhEMQ5YzI6hRBhvjkM6VRVYXybf4_KXg,4377
10
10
  chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
11
11
  chellow/rate_server.py,sha256=fg-Pf_9Hk3bXmC9riPQNGQxBvLvBa_WtNYdwDCjnCSg,5678
12
12
  chellow/rrun.py,sha256=1Kt2q_K9UoDG_nsZz-Q6XJiMNKroWqlqFdxn2M6Q8CA,2088
13
13
  chellow/testing.py,sha256=Od4HHH6pZrhJ_De118_F55RJEKmAvhUH2S24QE9qFQk,3635
14
14
  chellow/utils.py,sha256=Ej7dsbQ6Ee8X2aZ7B2Vs-hUFCsMABioAdOV1DJjwY-0,19293
15
- chellow/views.py,sha256=OlNGtFB9OatQPMzHiXTjBoGOSnFT7cC38GA2x7rtcFg,79490
15
+ chellow/views.py,sha256=82V1OtdscrAmbiT3B5MDZoCe50fDSJwfBF-PpbB5tm8,83513
16
16
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
18
18
  chellow/e/bill_importer.py,sha256=7UcnqNlKbJc2GhW9gy8sDp9GuqambJVpZLvbafOZztA,7411
@@ -20,7 +20,7 @@ chellow/e/bmarketidx.py,sha256=C0BaHn2RxIuWH2QzA-OmSP0fbUGvW9tqEUhLnzOEdmA,7968
20
20
  chellow/e/bsuos.py,sha256=hdP9vnOJSuZl46OAkJeUg1XJYvYIBj4J6Sqce1Hy9Vs,15542
21
21
  chellow/e/ccl.py,sha256=30dh_SvlgzsTQPPAJNZWILaMvbeDsv9-P-S1JxS5_SQ,3184
22
22
  chellow/e/cfd.py,sha256=Gm435c42LFwZx1n-1UY1Tdx6mL1is7E0vRLWQe8RPOo,14184
23
- chellow/e/computer.py,sha256=Fq4SpEvCLArZHOGgSGqvm7mdAVLiswnNWspuuft4h70,67460
23
+ chellow/e/computer.py,sha256=KkYNaD4W13s_BvtyPrYbfZ9MU73hKi-IIL-g4OOQxBA,67787
24
24
  chellow/e/dno_rate_parser.py,sha256=A5TP6KjyfT5lVWh7dX4SiXRi6wnf2lGv-H_T4Sod8CI,21731
25
25
  chellow/e/duos.py,sha256=nwviRjz-qIt3GxIMHk0hItIT4dtKsxOWq9TUC1z-hO8,30864
26
26
  chellow/e/elexon.py,sha256=ALhXS9Es7PV0z9ukPbIramn3cf3iLyFi-PMWPSm5iOs,5487
@@ -40,7 +40,7 @@ chellow/e/rcrc.py,sha256=92CA1uIotIHd1epQ_jEPdJKzXqDFV-AoJOJeRO6MEyA,4274
40
40
  chellow/e/ro.py,sha256=dZKZv_9wXSWuwcb3jiKavoD_9ot-PZseNVeEEe0siLo,596
41
41
  chellow/e/scenario.py,sha256=FLgh03r_SgXx0hMWFbAvwsz2ScDL8LUwYWSWVv2rQlg,24973
42
42
  chellow/e/system_price.py,sha256=6w5J7bzwFAZubE2zdOFRiS8IIrVP8hkoIOaG2yCt-Ic,6232
43
- chellow/e/tlms.py,sha256=M33D6YpMixu2KkwSCzDRM3kThLgShg8exp63Obo75l8,8905
43
+ chellow/e/tlms.py,sha256=Wb9ZuxscMzxXs0FT06Iu5YXeccmO6ai_mUjnJGJwTM4,9045
44
44
  chellow/e/tnuos.py,sha256=NBmc-f3oezrl4gviAKobljHfICTpBKxxxEGBGJi_lRk,4927
45
45
  chellow/e/triad.py,sha256=lIQj7EdUrcFwEqleuHZXYU_bfzIwNOqUVVxB3NPQt4A,13710
46
46
  chellow/e/views.py,sha256=ZDVUirf0fMLO8aS5gF3xpPnRDmNqYxxGHkGjxm0_AY4,220901
@@ -72,7 +72,7 @@ chellow/gas/cv.py,sha256=4cdYYQ8Qak6NeYdBCB4YaQ0jX8-UkaydIIdibCQuXxM,7344
72
72
  chellow/gas/dn_rate_parser.py,sha256=Mq8rAcUEUxIQOks59bsCKl8GrefvoHbrTCHqon9N0z0,11340
73
73
  chellow/gas/engine.py,sha256=d2rR1y8b3u2QhmfqyFwwLu_loeZxY_3WwwtDyGJfam0,25282
74
74
  chellow/gas/transportation.py,sha256=Bkg8TWOs-v0ES-4qqwbleiOhqbE_t2KauUx9JYMZELM,5300
75
- chellow/gas/views.py,sha256=7ObrqTR-z6cRGpBcilUroSKL9C0hGfOo4GcCnGhBuYU,59737
75
+ chellow/gas/views.py,sha256=GeCvi6BGTUN7bu7sVkypNckwG3Crl6AbUcRob9qMi0E,59733
76
76
  chellow/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  chellow/reports/report_109.py,sha256=Exb-FQ5f70-ier_h15CgHGysQ7vJ7k3gFZ1001zM3iM,11171
78
78
  chellow/reports/report_111.py,sha256=o_wWyrtmxpTnmT9DVlsG5h5wdm3JqojirRlx_7W5_kQ,28787
@@ -90,7 +90,7 @@ chellow/reports/report_291.py,sha256=BnWtxe0eWN2QKKWpwjs5-RI5LbReBKL119QbkrkNhV8
90
90
  chellow/reports/report_33.py,sha256=lt1EN_LNx6u-AgdaS3YRkPMZA33JgMcELolHF4oJUMw,16689
91
91
  chellow/reports/report_387.py,sha256=bBqtnGGDWIXzXe2zVm9yeDRZKNgkUaOS3XTX6k09f18,5631
92
92
  chellow/reports/report_41.py,sha256=8xUIN9D5dtT0Dn1RmbnWsLRmM-QbGA5xGhWCK3ljAzA,7406
93
- chellow/reports/report_429.py,sha256=bpnz_SAiWFoHN7UF3rS0-LupbDyE_AnGI-XOphGXyPc,12627
93
+ chellow/reports/report_429.py,sha256=SbFlBsHXWMWRlMbY_e0zco2R8tMG9XVulr56E3mRalU,14287
94
94
  chellow/reports/report_59.py,sha256=PkdRA6ctvDGWTQd5vb9cQH6MG920TxcD6aHh268zrj8,42221
95
95
  chellow/reports/report_81.py,sha256=bBpV6MtvKGtoLiqzZoK2h21KDs4vfDwy-etpfL9oiEI,5570
96
96
  chellow/reports/report_87.py,sha256=udzbCuXcckWD-OHmfJCT6bwg_paYhm4vfDWlL8WM-jA,6933
@@ -108,7 +108,7 @@ chellow/reports/report_g_virtual_bills.py,sha256=20vHa5LGQwOAlJlaGJaGszZrrbT0PMO
108
108
  chellow/reports/report_g_virtual_bills_hh.py,sha256=gaiLEmKTpq6JsfZ1p0SdCDuPvzvigXp6z88gHRCA63w,3416
109
109
  chellow/reports/report_sscs.py,sha256=fQWyVG-gdg37DyNHgpNARpSxIwTl7mCn20fDLwx9oHg,3214
110
110
  chellow/reports/report_supply_contacts.py,sha256=pvwlInaPYV_pa9MMK6vh854plHFwv3m5zo5xulR1g5I,3599
111
- chellow/static/css/chellow.css,sha256=mcLjqKMo0qtdQWY7AnXEL8Bvx2B-Pu8kcGO58bUXOpY,5372
111
+ chellow/static/css/chellow.css,sha256=dnkuj9Z1BCOV_L2Y26lDd2QlTmFFhATa1YvwPVch1Oc,5375
112
112
  chellow/static/images/favicon.svg,sha256=ySFHoVJYmr-xU93QrE-jLYn-ZNythh2vsemnR8dkvg0,2339
113
113
  chellow/static/images/favicon_test.svg,sha256=HnLS_BjNt8M0Ikko5Z-f_E2aed7y6RRU6j3K6XADciE,2346
114
114
  chellow/static/images/logo.png,sha256=XMW2XwukTicKVJ46E2SnbHJYh77uFLVYlwR4xN43YKg,1569
@@ -142,9 +142,11 @@ chellow/templates/report_run.html,sha256=O_wjIu43S-mKVyZyku3dJJdvyck3rAgEdhw59Ts
142
142
  chellow/templates/report_run_asset_comparison.html,sha256=VYCCUmIC7Mfe7uuaAHb6ihiK6zsqeTlQbzgtzLqR3zg,2305
143
143
  chellow/templates/report_run_bill_check.html,sha256=H2ayoBL7EgKMq2Nwq5VjE_TNvcIKcqeCm0alQWLIw78,5084
144
144
  chellow/templates/report_run_ecoes_comparison.html,sha256=VmkT5ypWLP8qZS6NbDTC4yDaG7mnUlxZz7EV8xkHGZw,4086
145
+ chellow/templates/report_run_g_bill_check.html,sha256=tOXl_mjR__foYKiOYflJbK-459actAtjzv8rfuL3TwM,4851
145
146
  chellow/templates/report_run_monthly_duration_org.html,sha256=gGNGJ4Q50q4BtIMi98rhO-7NqRHcsFUmbj2qzeOLejw,1713
146
147
  chellow/templates/report_run_row.html,sha256=bmtcdqJaS1CXpL0i8PuqvmeF98jKNYX5-mnQu-OuDKQ,3791
147
148
  chellow/templates/report_run_row_bill_check.html,sha256=aC2LMu_6NvmTN3ZdxHJPPPczyxPN6hg0F-PPcqIWUws,4683
149
+ chellow/templates/report_run_row_g_bill_check.html,sha256=aC2LMu_6NvmTN3ZdxHJPPPczyxPN6hg0F-PPcqIWUws,4683
148
150
  chellow/templates/report_run_supply_contacts.html,sha256=JNzwz9M6qbLRDMkCzFCxxANapUer5klxo7t5a48nAzg,2117
149
151
  chellow/templates/report_runs.html,sha256=ecoIkl2WtfYtifiTxnslmpMGYYGVQW-CVSBpqhXyiE4,1131
150
152
  chellow/templates/scenario.html,sha256=tCoq1wBq4l9PRS-zFtPcCWXlxD_SSFvFFkERf4FWVNU,2055
@@ -376,6 +378,6 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
376
378
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
377
379
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
378
380
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
379
- chellow-1738691248.0.0.dist-info/METADATA,sha256=4CALlxViKDlHgdl8qlcsc3GFhP8T1xV2WVZfUGV0shI,12204
380
- chellow-1738691248.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
381
- chellow-1738691248.0.0.dist-info/RECORD,,
381
+ chellow-1740066311.0.0.dist-info/METADATA,sha256=Ptkn8LQ0lzGXaW-pi11oteZKiDLxBJuK0WvC7HpscYY,12204
382
+ chellow-1740066311.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
383
+ chellow-1740066311.0.0.dist-info/RECORD,,