chellow 1744710468.0.0__py3-none-any.whl → 1744720514.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/__init__.py CHANGED
@@ -22,6 +22,7 @@ import chellow.e.hh_importer
22
22
  import chellow.e.rcrc
23
23
  import chellow.e.system_price
24
24
  import chellow.e.views
25
+ import chellow.fake_batch_updater
25
26
  import chellow.gas.cv
26
27
  import chellow.gas.views
27
28
  import chellow.national_grid
@@ -62,6 +63,7 @@ def get_importer_modules():
62
63
  chellow.national_grid,
63
64
  chellow.rate_server,
64
65
  chellow.rrun,
66
+ chellow.fake_batch_updater,
65
67
  )
66
68
 
67
69
 
@@ -0,0 +1,305 @@
1
+ import atexit
2
+ import collections
3
+ import threading
4
+ import traceback
5
+ from datetime import timedelta
6
+
7
+ from sqlalchemy import false, null, select
8
+
9
+ from werkzeug.exceptions import BadRequest
10
+
11
+ from chellow.e.computer import contract_func
12
+ from chellow.gas.engine import g_contract_func
13
+ from chellow.models import (
14
+ Batch,
15
+ BillType,
16
+ Contract,
17
+ GBatch,
18
+ GBill,
19
+ GContract,
20
+ GRateScript,
21
+ GReadType,
22
+ GSupply,
23
+ GUnit,
24
+ RateScript,
25
+ ReadType,
26
+ Session,
27
+ Supply,
28
+ Tpr,
29
+ )
30
+ from chellow.utils import (
31
+ c_months_u,
32
+ ct_datetime_now,
33
+ hh_format,
34
+ keydefaultdict,
35
+ utc_datetime_now,
36
+ )
37
+
38
+
39
+ importer = None
40
+
41
+
42
+ def run_import(sess, log, set_progress):
43
+ log("Starting to update the fake batches")
44
+ caches = {}
45
+
46
+ now_ct = ct_datetime_now()
47
+ (last_month_start, last_month_finish), (
48
+ current_month_start,
49
+ current_month_finish,
50
+ ) = list(c_months_u(finish_year=now_ct.year, finish_month=now_ct.month, months=2))
51
+
52
+ for last_rate_script in sess.scalars(
53
+ select(RateScript)
54
+ .join(Contract, RateScript.contract_id == Contract.id)
55
+ .where(RateScript.finish_date == null())
56
+ ):
57
+ contract = last_rate_script.contract
58
+ fb_func = contract_func(caches, contract, "make_fake_bills")
59
+ if fb_func is None:
60
+ continue
61
+
62
+ fake_batch_name = f"fake_e_batch_{contract.id}"
63
+
64
+ fake_batch = sess.scalar_one_or_none(
65
+ select(Batch).where(
66
+ Batch.contract == contract,
67
+ Batch.reference == fake_batch_name,
68
+ )
69
+ )
70
+
71
+ if fake_batch is not None and fake_batch.start_date < current_month_start:
72
+ fake_batch.delete(sess)
73
+ sess.flush()
74
+ fake_batch = None
75
+
76
+ if fake_batch is None:
77
+ fake_batch = contract.insert_batch(sess, fake_batch_name, "Fake Batch")
78
+ bill_types = keydefaultdict(lambda k: BillType.get_by_code(sess, k))
79
+
80
+ tprs = keydefaultdict(
81
+ lambda k: None if k is None else Tpr.get_by_code(sess, k)
82
+ )
83
+
84
+ read_types = keydefaultdict(lambda k: ReadType.get_by_code(sess, k))
85
+ for raw_bill in fb_func(
86
+ sess,
87
+ log,
88
+ last_month_start,
89
+ last_month_finish,
90
+ current_month_start,
91
+ current_month_finish,
92
+ ):
93
+ mpan_core = raw_bill["mpan_core"]
94
+ supply = Supply.get_by_mpan_core(sess, mpan_core)
95
+ bill = fake_batch.insert_bill(
96
+ sess,
97
+ raw_bill["account"],
98
+ raw_bill["reference"],
99
+ raw_bill["issue_date"],
100
+ raw_bill["start_date"],
101
+ raw_bill["finish_date"],
102
+ raw_bill["kwh"],
103
+ raw_bill["net"],
104
+ raw_bill["vat"],
105
+ raw_bill["gross"],
106
+ bill_types[raw_bill["bill_type_code"]],
107
+ raw_bill["breakdown"],
108
+ supply,
109
+ )
110
+ for raw_read in raw_bill["reads"]:
111
+ bill.insert_read(
112
+ sess,
113
+ tprs[raw_read["tpr_code"]],
114
+ raw_read["coefficient"],
115
+ raw_read["units"],
116
+ raw_read["msn"],
117
+ raw_read["mpan"],
118
+ raw_read["prev_date"],
119
+ raw_read["prev_value"],
120
+ read_types[raw_read["prev_type_code"]],
121
+ raw_read["pres_date"],
122
+ raw_read["pres_value"],
123
+ read_types[raw_read["pres_type_code"]],
124
+ )
125
+ for last_rate_script in sess.scalars(
126
+ select(GRateScript)
127
+ .join(GContract, GRateScript.g_contract_id == GContract.id)
128
+ .where(GContract.is_industry == false(), GRateScript.finish_date == null())
129
+ ):
130
+ g_contract = last_rate_script.g_contract
131
+ log(f"Looking at gas contract {g_contract.name}")
132
+ fb_func = g_contract_func(caches, g_contract, "make_fake_bills")
133
+ if fb_func is None:
134
+ log("Doesn't have a make_fake_bills function so skipping")
135
+ continue
136
+
137
+ fake_batch_name = f"fake_g_batch_{g_contract.id}"
138
+
139
+ fake_batch = sess.scalars(
140
+ select(GBatch).where(
141
+ GBatch.g_contract == g_contract,
142
+ GBatch.reference == fake_batch_name,
143
+ )
144
+ ).one_or_none()
145
+
146
+ if fake_batch is not None:
147
+ first_fake_bill = sess.scalars(
148
+ select(GBill)
149
+ .where(GBill.g_batch == fake_batch)
150
+ .order_by(GBill.start_date)
151
+ ).first()
152
+ if (
153
+ first_fake_bill is None
154
+ or first_fake_bill.start_date < current_month_start
155
+ ):
156
+ fake_batch.delete(sess)
157
+ sess.flush()
158
+ fake_batch = None
159
+
160
+ if fake_batch is None:
161
+ fake_batch = g_contract.insert_g_batch(sess, fake_batch_name, "Fake Batch")
162
+ raw_bills = fb_func(
163
+ sess,
164
+ log,
165
+ last_month_start,
166
+ last_month_finish,
167
+ current_month_start,
168
+ current_month_finish,
169
+ )
170
+ if raw_bills is not None and len(raw_bills) > 0:
171
+ for raw_bill in raw_bills:
172
+ bill_type = BillType.get_by_code(sess, raw_bill["bill_type_code"])
173
+ g_supply = GSupply.get_by_mprn(sess, raw_bill["mprn"])
174
+ g_bill = fake_batch.insert_g_bill(
175
+ sess,
176
+ g_supply,
177
+ bill_type,
178
+ raw_bill["reference"],
179
+ raw_bill["account"],
180
+ raw_bill["issue_date"],
181
+ raw_bill["start_date"],
182
+ raw_bill["finish_date"],
183
+ raw_bill["kwh"],
184
+ raw_bill["net_gbp"],
185
+ raw_bill["vat_gbp"],
186
+ raw_bill["gross_gbp"],
187
+ raw_bill["raw_lines"],
188
+ raw_bill["breakdown"],
189
+ )
190
+ sess.flush()
191
+ for raw_read in raw_bill["reads"]:
192
+ prev_type = GReadType.get_by_code(
193
+ sess, raw_read["prev_type_code"]
194
+ )
195
+ pres_type = GReadType.get_by_code(
196
+ sess, raw_read["pres_type_code"]
197
+ )
198
+ g_unit = GUnit.get_by_code(sess, raw_read["unit"])
199
+ g_bill.insert_g_read(
200
+ sess,
201
+ raw_read["msn"],
202
+ g_unit,
203
+ raw_read["correction_factor"],
204
+ raw_read["calorific_value"],
205
+ raw_read["prev_value"],
206
+ raw_read["prev_date"],
207
+ prev_type,
208
+ raw_read["pres_value"],
209
+ raw_read["pres_date"],
210
+ pres_type,
211
+ )
212
+ sess.commit()
213
+
214
+
215
+ LAST_RUN_KEY = "fake_batch_updater_last_run"
216
+
217
+
218
+ class FakeBatchUpdater(threading.Thread):
219
+ def __init__(self):
220
+ super().__init__(name="Fake Batch Updater")
221
+ self.messages = collections.deque(maxlen=500)
222
+ self.progress = ""
223
+ self.stopped = threading.Event()
224
+ self.going = threading.Event()
225
+ self.global_alert = None
226
+
227
+ def stop(self):
228
+ self.stopped.set()
229
+ self.going.set()
230
+ self.join()
231
+
232
+ def go(self):
233
+ self.going.set()
234
+
235
+ def log(self, message):
236
+ self.messages.appendleft(
237
+ f"{ct_datetime_now().strftime('%Y-%m-%d %H:%M:%S')} - {message}"
238
+ )
239
+
240
+ def set_progress(self, progress):
241
+ self.progress = progress
242
+
243
+ def run(self):
244
+ while not self.stopped.is_set():
245
+ with Session() as sess:
246
+ try:
247
+ config = Contract.get_non_core_by_name(sess, "configuration")
248
+ state = config.make_state()
249
+ except BaseException as e:
250
+ msg = f"{e.description} " if isinstance(e, BadRequest) else ""
251
+ self.log(f"{msg}{traceback.format_exc()}")
252
+ self.global_alert = (
253
+ "There's a problem with a <a href='/fake_batch_updater'>"
254
+ "Fake Batch Updater</a>."
255
+ )
256
+ sess.rollback()
257
+
258
+ last_run = state.get(LAST_RUN_KEY)
259
+ if last_run is None or utc_datetime_now() - last_run > timedelta(days=1):
260
+ self.going.set()
261
+
262
+ if self.going.is_set():
263
+ self.global_alert = None
264
+ with Session() as sess:
265
+ try:
266
+ config = Contract.get_non_core_by_name(sess, "configuration")
267
+ state = config.make_state()
268
+ state[LAST_RUN_KEY] = utc_datetime_now()
269
+ config.update_state(state)
270
+ sess.commit()
271
+ run_import(sess, self.log, self.set_progress)
272
+ except BaseException as e:
273
+ msg = f"{e.description} " if isinstance(e, BadRequest) else ""
274
+ self.log(f"{msg}{traceback.format_exc()}")
275
+ self.global_alert = (
276
+ "There's a problem with a "
277
+ "<a href='/fake_batch_updater'>Fake Batch Updater</a>."
278
+ )
279
+ sess.rollback()
280
+ finally:
281
+ self.going.clear()
282
+ self.log("Finished updating fake batches.")
283
+
284
+ else:
285
+ self.log(
286
+ f"The updater was last run at {hh_format(last_run)}. There will "
287
+ f"be another update when a hour has elapsed since the last run."
288
+ )
289
+ self.going.wait(60 * 60)
290
+
291
+
292
+ def get_importer():
293
+ return importer
294
+
295
+
296
+ def startup():
297
+ global importer
298
+ importer = FakeBatchUpdater()
299
+ importer.start()
300
+
301
+
302
+ @atexit.register
303
+ def shutdown():
304
+ if importer is not None:
305
+ importer.stop()
@@ -0,0 +1,99 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Fake Batch Updater
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ Fake Batch Updater
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ <p>
13
+ {% if importer %}
14
+ <ul>
15
+ <li><a href="#controls">Controls</a></li>
16
+ <li><a href="#log">Log</a></li>
17
+ <li><a href="#batches">Fake Batches</a></li>
18
+ </ul>
19
+
20
+ <h4 id="controls">Controls</h4>
21
+
22
+ <table>
23
+ <tr>
24
+ <th>Is Going?</th>
25
+ <td>{{importer.going.is_set()}}</td>
26
+ </tr>
27
+ <tr>
28
+ <th>Import Now</th>
29
+ <td>
30
+ <form method="post">
31
+ <fieldset {% if importer.going.is_set() %}disabled{% endif %}>
32
+ <input type="submit" value="Import" name="import">
33
+ </fieldset>
34
+ </form>
35
+ </td>
36
+ </tr>
37
+ {% if importer.going.is_set() %}
38
+ <tr>
39
+ <th>Progress</th>
40
+ <td>{{importer.progress}}</td>
41
+ </tr>
42
+ {% endif %}
43
+ </table>
44
+
45
+
46
+ <h4 id="log">Log</h4>
47
+
48
+ <ul>
49
+ {% for message in importer.messages %}
50
+ <li>{{message}}</li>
51
+ {% endfor %}
52
+ </ul>
53
+
54
+ <h4 id="batches">Fake Batches</h4>
55
+
56
+ <table>
57
+ <caption>Electricity</caption>
58
+ <thead>
59
+ <tr>
60
+ <th>View</th>
61
+ <th>Reference</th>
62
+ <th>Contract Name</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+ {% for batch in e_fake_batches %}
67
+ <tr>
68
+ <th><a href="/e/supplier_batches/{{batch.id}}">View</a></th>
69
+ <td>{{ batch.reference }}</td>
70
+ <td>{{ batch.contract.name }}</td>
71
+ </tr>
72
+ {% endfor %}
73
+ </tbody>
74
+ </table>
75
+
76
+ <table>
77
+ <caption>Gas</caption>
78
+ <thead>
79
+ <tr>
80
+ <th>View</th>
81
+ <th>Reference</th>
82
+ <th>Contract Name</th>
83
+ </tr>
84
+ </thead>
85
+ <tbody>
86
+ {% for batch in g_fake_batches %}
87
+ <tr>
88
+ <th><a href="/g/batches/{{batch.id}}">View</a></th>
89
+ <td>{{ batch.reference }}</td>
90
+ <td>{{ batch.g_contract.name }}</td>
91
+ </tr>
92
+ {% endfor %}
93
+ </tbody>
94
+ </table>
95
+
96
+ {% else %}
97
+ <p>Importer not present.</p>
98
+ {% endif %}
99
+ {% endblock %}
@@ -214,6 +214,7 @@
214
214
  <li><a href="/national_grid">Automatic Importer: National Grid</a></li>
215
215
  <li><a href="/e/lcc">Automatic Importer: Low Carbon Contracts</a></li>
216
216
  <li><a href="/e/elexon">Automatic Importer: Elexon</a></li>
217
+ <li><a href="/fake_batch_updater">Automatic Fake Batch Updater</a></li>
217
218
  </ul>
218
219
 
219
220
  <ul>
chellow/views.py CHANGED
@@ -72,6 +72,7 @@ import chellow.national_grid
72
72
  import chellow.rate_server
73
73
  from chellow.edi_lib import SEGMENTS, parse_edi
74
74
  from chellow.models import (
75
+ Batch,
75
76
  BillType,
76
77
  Channel,
77
78
  Comm,
@@ -79,6 +80,7 @@ from chellow.models import (
79
80
  Cop,
80
81
  EnergisationStatus,
81
82
  Era,
83
+ GBatch,
82
84
  GContract,
83
85
  GEra,
84
86
  GExitZone,
@@ -156,6 +158,41 @@ def configuration():
156
158
  return redirect(f"/non_core_contracts/{config.id}")
157
159
 
158
160
 
161
+ @home.route("/fake_batch_updater")
162
+ def fake_batch_updater_get():
163
+ importer = chellow.fake_batch_updater.importer
164
+ config = Contract.get_non_core_by_name(g.sess, "configuration")
165
+ props = config.make_properties()
166
+
167
+ e_fake_batches = g.sess.scalars(
168
+ select(Batch).where(
169
+ Batch.reference.startswith("e_fake_batch_"),
170
+ )
171
+ )
172
+
173
+ g_fake_batches = g.sess.scalars(
174
+ select(GBatch).where(
175
+ GBatch.reference.startswith("g_fake_batch_"),
176
+ )
177
+ )
178
+
179
+ return render_template(
180
+ "fake_batch_updater.html",
181
+ importer=importer,
182
+ config_state=config.make_state(),
183
+ config_properties=props.get("fake_batch_updater", {}),
184
+ e_fake_batches=e_fake_batches,
185
+ g_fake_batches=g_fake_batches,
186
+ )
187
+
188
+
189
+ @home.route("/fake_batch_updater", methods=["POST"])
190
+ def fake_batch_updater_post():
191
+ importer = chellow.fake_batch_updater.importer
192
+ importer.go()
193
+ return redirect("/fake_batch_updater", 303)
194
+
195
+
159
196
  @home.route("/health")
160
197
  def health():
161
198
  return Response("healthy\n", mimetype="text/plain")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1744710468.0.0
3
+ Version: 1744720514.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)
@@ -1,9 +1,10 @@
1
- chellow/__init__.py,sha256=S1IjQChkEMA_MfzE5F1pPP-54bz_CRH4KE9X6SgX8bI,9722
1
+ chellow/__init__.py,sha256=yBjAoOWh4NlA847_j4hiI7O6YyiEysf2X1y873JS-aA,9792
2
2
  chellow/api.py,sha256=mk17TfweR76DPFC8lX2SArTjai6y6YshASxqO1w-_-s,11036
3
3
  chellow/bank_holidays.py,sha256=T_utYMwe_g1dz5X-aOTdIPryg49SvB7QsWM1yphlqG8,4423
4
4
  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
+ chellow/fake_batch_updater.py,sha256=UdI1ygrrU5a9UZVxc9j9Lq-tIejHFGalG_4g-rPsriI,10596
7
8
  chellow/general_import.py,sha256=y8X-FzQJzVrfvVMyErNHns2MGI511KwDC19AjIX3nTk,65325
8
9
  chellow/models.py,sha256=XD5wl3Pa8vZFGA0aB1Pu-xJs3iBoBoeX44E8Myho_68,244648
9
10
  chellow/national_grid.py,sha256=czwIZqzJndSGhEMQ5YzI6hRBhvjkM6VRVYXybf4_KXg,4377
@@ -12,7 +13,7 @@ chellow/rate_server.py,sha256=fg-Pf_9Hk3bXmC9riPQNGQxBvLvBa_WtNYdwDCjnCSg,5678
12
13
  chellow/rrun.py,sha256=1Kt2q_K9UoDG_nsZz-Q6XJiMNKroWqlqFdxn2M6Q8CA,2088
13
14
  chellow/testing.py,sha256=Dj2c1NX8lVlygueOrh2eyYawLW6qKEHxNhXVVUaNRO0,3637
14
15
  chellow/utils.py,sha256=Ej7dsbQ6Ee8X2aZ7B2Vs-hUFCsMABioAdOV1DJjwY-0,19293
15
- chellow/views.py,sha256=2WCU-_8lDuSDROvNlCGsAQVLSMh5qrD2wmeSr2G-umA,83872
16
+ chellow/views.py,sha256=Ax8TW5dOoWN1G7zmmO4_X0af_9gftKU0HAHoPpkYNIc,84883
16
17
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
18
19
  chellow/e/bill_importer.py,sha256=7UcnqNlKbJc2GhW9gy8sDp9GuqambJVpZLvbafOZztA,7411
@@ -121,9 +122,10 @@ chellow/templates/chain.html,sha256=iOnSoDQOO_-1tfcxUHQKDAUnV4QbCYu5PPNmS4RkOg4,
121
122
  chellow/templates/csv_sites_monthly_duration.html,sha256=59gErG6KwA57z-qh2EJsVGvTUazqm85CSXefSfqPWcQ,486
122
123
  chellow/templates/downloads.html,sha256=R9QPcFz-PLJOX7rDlmquIk-Hp9Iq-thzWCTfOexSpXg,937
123
124
  chellow/templates/edi_viewer.html,sha256=szUthgHOdph6Of-7f_1LeC_zYlNJaMeS5ctn6xTAeiM,1437
125
+ chellow/templates/fake_batch_updater.html,sha256=aRQbxtNUlIzxwgSUy2pr-Km5NbhZkse4WSBtlqFIJMg,1885
124
126
  chellow/templates/general_import.html,sha256=9ezzieDjaPBZ0nUJkMkzoDxWVzYtr4D-Dr2UCA5xV8U,1370
125
127
  chellow/templates/general_imports.html,sha256=9sYN7FdzfxFypAUbUJ4VbhU3WhJrjArtqneohgX1hGE,13171
126
- chellow/templates/home.html,sha256=illtSabepcScYwmKss0o2gwvjXGBtr6LkfwmeL6ezSA,5655
128
+ chellow/templates/home.html,sha256=EZbvIvNw0RiuIlaUTOojYSZMTd3VMbTkulABgTB_lAc,5732
127
129
  chellow/templates/input_date.html,sha256=rpgB5n0LfN8Y5djN_ZiuSxqdskxzCoKrEqI7hyJkVQo,1248
128
130
  chellow/templates/local_report.html,sha256=pV7_0QwyQ-D3OS9LXrly5pq3qprZnwTCoq6vCnMTkS4,1332
129
131
  chellow/templates/local_reports.html,sha256=4wbfVkY4wUfSSfWjxqIsvCpIsa9k7H_dGAjznrG5jNM,701
@@ -378,6 +380,6 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
378
380
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
379
381
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
380
382
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
381
- chellow-1744710468.0.0.dist-info/METADATA,sha256=iMSbXSKlBSPZZa8DhxsOskSD_yN4Q40MZJBuA_CXHf4,12238
382
- chellow-1744710468.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
383
- chellow-1744710468.0.0.dist-info/RECORD,,
383
+ chellow-1744720514.0.0.dist-info/METADATA,sha256=h25ZnkwR-pmuFVXtxncX-IM8v7L7Z_yXQ4DZ_H-HlYw,12238
384
+ chellow-1744720514.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
385
+ chellow-1744720514.0.0.dist-info/RECORD,,