chellow 1729081025.0.0__py3-none-any.whl → 1729755916.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.

@@ -1,4 +1,3 @@
1
- import sys
2
1
  import threading
3
2
  import traceback
4
3
 
@@ -14,34 +13,36 @@ from werkzeug.exceptions import BadRequest
14
13
 
15
14
  import chellow.e.computer
16
15
  from chellow.dloads import open_file
17
- from chellow.e.computer import contract_func
16
+ from chellow.e.computer import contract_func, forecast_date
18
17
  from chellow.gas.engine import GDataSource
19
18
  from chellow.models import (
20
19
  GBill,
21
20
  GContract,
22
21
  GEra,
23
22
  GSupply,
24
- Session,
23
+ RSession,
24
+ ReportRun,
25
+ Scenario,
25
26
  Site,
26
27
  SiteGEra,
28
+ User,
27
29
  )
28
30
  from chellow.utils import (
31
+ c_months_c,
29
32
  c_months_u,
30
- ct_datetime_now,
31
33
  hh_format,
32
34
  hh_max,
33
35
  hh_min,
34
36
  make_val,
35
37
  req_bool,
36
38
  req_int,
39
+ req_str,
40
+ to_utc,
41
+ utc_datetime_now,
37
42
  )
38
43
  from chellow.views import chellow_redirect
39
44
 
40
45
 
41
- CATEGORY_ORDER = {None: 0, "unmetered": 1, "nhh": 2, "amr": 3, "hh": 4}
42
- meter_order = {"hh": 0, "amr": 1, "nhh": 2, "unmetered": 3}
43
-
44
-
45
46
  def write_spreadsheet(fl, compressed, site_rows, era_rows):
46
47
  fl.seek(0)
47
48
  fl.truncate()
@@ -61,6 +62,7 @@ def _process_era(
61
62
  g_era_rows,
62
63
  vb_titles,
63
64
  now,
65
+ summary_titles,
64
66
  ):
65
67
  g_supply = g_era.g_supply
66
68
 
@@ -91,28 +93,30 @@ def _process_era(
91
93
  f"Problem with virtual bill for g_supplier_contrat {contract.id}."
92
94
  ) from e
93
95
 
94
- try:
95
- gbp = bill["net_gbp"]
96
- except KeyError:
97
- gbp = 0
98
- bill["problem"] += (
99
- f"For the supply {ss.mprn} the virtual bill {bill} "
100
- f"from the contract {contract.name} does not contain "
101
- f"the net_gbp key."
102
- )
103
- try:
104
- kwh = bill["kwh"]
105
- except KeyError:
106
- kwh = 0
107
- bill["problem"] += (
108
- f"For the supply {ss.mprn} the virtual bill "
109
- f"{bill} from the contract {contract.name} does not "
110
- f"contain the 'kwh' key."
111
- )
112
-
113
- billed_kwh = billed_net_gbp = billed_vat_gbp = billed_gross_gbp = 0
96
+ summary_data = {
97
+ "kwh": 0,
98
+ "net_gbp": 0,
99
+ "vat_gbp": 0,
100
+ "gross_gbp": 0,
101
+ "billed_kwh": 0,
102
+ "billed_net_gbp": 0,
103
+ "billed_vat_gbp": 0,
104
+ "billed_gross_gbp": 0,
105
+ }
106
+
107
+ for key in ("kwh", "net_gbp", "vat_gbp", "gross_gbp"):
108
+ try:
109
+ summary_data[key] += bill[key]
110
+ except KeyError:
111
+ bill["problem"] += (
112
+ f"For the supply {ss.mprn} the virtual bill {bill} "
113
+ f"from the contract {contract.name} does not contain "
114
+ f"the {key} key."
115
+ )
114
116
 
115
- g_era_associates = {s.site.code for s in g_era.site_g_eras if not s.is_physical}
117
+ associated_site_codes = {
118
+ s.site.code for s in g_era.site_g_eras if not s.is_physical
119
+ }
116
120
 
117
121
  for g_bill in sess.query(GBill).filter(
118
122
  GBill.g_supply == g_supply,
@@ -126,13 +130,12 @@ def _process_era(
126
130
  min(bill_finish, ss_finish) - max(bill_start, ss_start)
127
131
  ).total_seconds() + (30 * 60)
128
132
  overlap_proportion = overlap_duration / bill_duration
129
- billed_kwh += overlap_proportion * float(g_bill.kwh)
130
- billed_net_gbp += overlap_proportion * float(g_bill.net)
131
- billed_vat_gbp += overlap_proportion * float(g_bill.vat)
132
- billed_gross_gbp += overlap_proportion * float(g_bill.gross)
133
+ summary_data["billed_kwh"] += overlap_proportion * float(g_bill.kwh)
134
+ summary_data["billed_net_gbp"] += overlap_proportion * float(g_bill.net)
135
+ summary_data["billed_vat_gbp"] += overlap_proportion * float(g_bill.vat)
136
+ summary_data["billed_gross_gbp"] += overlap_proportion * float(g_bill.gross)
133
137
 
134
- associated_site_ids = ",".join(sorted(g_era_associates))
135
- g_era_rows.append(
138
+ g_era_row = (
136
139
  [
137
140
  make_val(v)
138
141
  for v in [
@@ -145,71 +148,117 @@ def _process_era(
145
148
  contract.name,
146
149
  site.code,
147
150
  site.name,
148
- associated_site_ids,
151
+ associated_site_codes,
149
152
  g_era.start_date,
150
153
  month_finish,
151
- kwh,
152
- gbp,
153
- billed_kwh,
154
- billed_net_gbp,
155
- billed_vat_gbp,
156
- billed_gross_gbp,
157
154
  ]
158
155
  ]
156
+ + [make_val(summary_data[t]) for t in summary_titles]
159
157
  + [make_val(bill.get(t)) for t in vb_titles]
160
158
  )
161
- return kwh, gbp, billed_kwh, billed_net_gbp, billed_vat_gbp, billed_gross_gbp
159
+ g_era_rows.append(g_era_row)
160
+ return summary_data
162
161
 
163
162
 
164
- def content(
165
- site_id, g_supply_id, user, compression, finish_year, finish_month, months, now=None
166
- ):
167
- if now is None:
168
- now = ct_datetime_now()
163
+ def content(scenario_props, user_id, compression, now, base_name):
169
164
  report_context = {}
170
- month_list = list(
171
- c_months_u(finish_year=finish_year, finish_month=finish_month, months=months)
172
- )
173
- start_date, finish_date = month_list[0][0], month_list[-1][-1]
174
-
175
165
  try:
176
- with Session() as sess:
177
- base_name = [
178
- "g_monthly_duration",
166
+ with RSession() as sess:
167
+ start_year = scenario_props["scenario_start_year"]
168
+ start_month = scenario_props["scenario_start_month"]
169
+ months = scenario_props["scenario_duration"]
170
+
171
+ month_pairs = list(
172
+ c_months_u(
173
+ start_year=start_year, start_month=start_month, months=months
174
+ )
175
+ )
176
+ start_date = month_pairs[0][0]
177
+ finish_date = month_pairs[-1][-1]
178
+
179
+ base_name.append(
179
180
  hh_format(start_date)
180
181
  .replace(" ", "_")
181
182
  .replace(":", "")
182
- .replace("-", ""),
183
- "for",
184
- str(months),
185
- "months",
186
- ]
183
+ .replace("-", "")
184
+ )
187
185
 
188
- forecast_from = chellow.e.computer.forecast_date()
186
+ base_name.append("for")
187
+ base_name.append(str(months))
188
+ base_name.append("months")
189
+
190
+ if "forecast_from" in scenario_props:
191
+ forecast_from = scenario_props["forecast_from"]
192
+ else:
193
+ forecast_from = None
194
+
195
+ if forecast_from is None:
196
+ forecast_from = forecast_date()
197
+ else:
198
+ forecast_from = to_utc(forecast_from)
199
+
200
+ sites = sess.query(Site).distinct().order_by(Site.code)
201
+
202
+ mprns = scenario_props.get("mprns")
203
+ g_supply_ids = None
204
+ if mprns is not None:
205
+ g_supply_ids = []
206
+ for mprn in mprns:
207
+ g_supply = GSupply.get_by_mprn(sess, mprn)
208
+ g_supply_ids.append(g_supply.id)
209
+
210
+ if len(g_supply_ids) == 1:
211
+ base_name.append("g_supply")
212
+ base_name.append(str(g_supply.id))
213
+ else:
214
+ base_name.append("mprns")
215
+
216
+ sites = (
217
+ sites.join(SiteGEra)
218
+ .join(GEra)
219
+ .join(GSupply)
220
+ .where(GSupply.id.in_(g_supply_ids))
221
+ )
189
222
 
190
- sites = (
191
- sess.query(Site)
192
- .join(SiteGEra)
193
- .join(GEra)
194
- .filter(SiteGEra.is_physical == true())
195
- .distinct()
196
- .order_by(Site.code)
197
- )
198
- if site_id is not None:
199
- site = Site.get_by_id(sess, site_id)
200
- sites = sites.filter(Site.id == site.id)
201
- base_name.append("site")
202
- base_name.append(site.code)
203
- if g_supply_id is not None:
204
- g_supply = GSupply.get_by_id(sess, g_supply_id)
205
- base_name.append("g_supply")
206
- base_name.append(str(g_supply.id))
207
- sites = sites.filter(GEra.g_supply == g_supply)
208
-
209
- rf = open_file("_".join(base_name) + ".ods", user, mode="wb")
223
+ site_codes = scenario_props.get("site_codes")
224
+ if site_codes is not None:
225
+ if len(site_codes) == 1:
226
+ base_name.append("site")
227
+ base_name.append(site_codes[0])
228
+ else:
229
+ base_name.append("sitecodes")
230
+ sites = sites.where(Site.code.in_(site_codes))
231
+
232
+ user = User.get_by_id(sess, user_id)
233
+ fname = "_".join(base_name) + ".ods"
234
+ rf = open_file(fname, user, mode="wb")
235
+ org_rows = []
210
236
  site_rows = []
211
237
  g_era_rows = []
212
238
 
239
+ org_header_titles = [
240
+ "creation_date",
241
+ "month",
242
+ ]
243
+ summary_titles = [
244
+ "kwh",
245
+ "net_gbp",
246
+ "vat_gbp",
247
+ "gross_gbp",
248
+ "billed_kwh",
249
+ "billed_net_gbp",
250
+ "billed_vat_gbp",
251
+ "billed_gross_gbp",
252
+ ]
253
+ org_titles = org_header_titles + summary_titles
254
+ site_header_titles = [
255
+ "creation_date",
256
+ "site_code",
257
+ "site_name",
258
+ "associated_site_codes",
259
+ "month",
260
+ ]
261
+ site_titles = site_header_titles + summary_titles
213
262
  era_header_titles = [
214
263
  "creation_date",
215
264
  "mprn",
@@ -218,27 +267,12 @@ def content(
218
267
  "msn",
219
268
  "unit",
220
269
  "contract",
221
- "site_id",
270
+ "site_code",
222
271
  "site_name",
223
- "associated_site_ids",
272
+ "associated_site_codes",
224
273
  "era-start",
225
274
  "month",
226
275
  ]
227
- site_header_titles = [
228
- "creation_date",
229
- "site_id",
230
- "site_name",
231
- "associated_site_ids",
232
- "month",
233
- ]
234
- summary_titles = [
235
- "kwh",
236
- "gbp",
237
- "billed_kwh",
238
- "billed_net_gbp",
239
- "billed_vat_gbp",
240
- "billed_gross_gbp",
241
- ]
242
276
 
243
277
  vb_titles = []
244
278
  conts = (
@@ -252,8 +286,8 @@ def content(
252
286
  .distinct()
253
287
  .order_by(GContract.id)
254
288
  )
255
- if g_supply_id is not None:
256
- conts = conts.filter(GEra.g_supply_id == g_supply_id)
289
+ if g_supply_ids is not None:
290
+ conts = conts.where(GEra.g_supply_id.in_(g_supply_ids))
257
291
  for cont in conts:
258
292
  title_func = chellow.e.computer.contract_func(
259
293
  report_context, cont, "virtual_bill_titles"
@@ -267,16 +301,47 @@ def content(
267
301
  if title not in vb_titles:
268
302
  vb_titles.append(title)
269
303
 
270
- g_era_rows.append(era_header_titles + summary_titles + vb_titles)
304
+ org_rows.append(org_header_titles + summary_titles)
271
305
  site_rows.append(site_header_titles + summary_titles)
306
+ g_era_rows.append(era_header_titles + summary_titles + vb_titles)
272
307
 
273
- for month_start, month_finish in month_list:
308
+ for month_start, month_finish in month_pairs:
309
+ org_row = {
310
+ "creation_date": now,
311
+ "month": month_start,
312
+ "kwh": 0,
313
+ "net_gbp": 0,
314
+ "vat_gbp": 0,
315
+ "gross_gbp": 0,
316
+ "billed_kwh": 0,
317
+ "billed_net_gbp": 0,
318
+ "billed_vat_gbp": 0,
319
+ "billed_gross_gbp": 0,
320
+ }
274
321
  for site in sites.filter(
275
322
  GEra.start_date <= month_finish,
276
323
  or_(GEra.finish_date == null(), GEra.finish_date >= month_start),
277
324
  ):
278
- site_kwh = site_gbp = site_billed_kwh = site_billed_net_gbp = 0
279
- site_billed_vat_gbp = site_billed_gross_gbp = 0
325
+ linked_sites = {
326
+ s.code
327
+ for s in site.find_linked_sites(sess, month_start, month_finish)
328
+ }
329
+
330
+ site_row = {
331
+ "creation_date": now,
332
+ "site_code": site.code,
333
+ "site_name": site.name,
334
+ "associated_site_codes": linked_sites,
335
+ "month": month_finish,
336
+ "kwh": 0,
337
+ "net_gbp": 0,
338
+ "vat_gbp": 0,
339
+ "gross_gbp": 0,
340
+ "billed_kwh": 0,
341
+ "billed_net_gbp": 0,
342
+ "billed_vat_gbp": 0,
343
+ "billed_gross_gbp": 0,
344
+ }
280
345
 
281
346
  g_eras_q = (
282
347
  select(GEra)
@@ -297,19 +362,12 @@ def content(
297
362
  )
298
363
  .order_by(GEra.id)
299
364
  )
300
- if g_supply_id is not None:
301
- g_eras_q = g_eras_q.where(GEra.g_supply_id == g_supply_id)
365
+ if g_supply_ids is not None:
366
+ g_eras_q = g_eras_q.where(GEra.g_supply_id.in_(g_supply_ids))
302
367
 
303
368
  for g_era in sess.scalars(g_eras_q):
304
369
  try:
305
- (
306
- kwh,
307
- gbp,
308
- billed_kwh,
309
- billed_net_gbp,
310
- billed_vat_gbp,
311
- billed_gross_gbp,
312
- ) = _process_era(
370
+ summary_data = _process_era(
313
371
  report_context,
314
372
  sess,
315
373
  site,
@@ -320,47 +378,39 @@ def content(
320
378
  g_era_rows,
321
379
  vb_titles,
322
380
  now,
381
+ summary_titles,
323
382
  )
324
- site_kwh += kwh
325
- site_gbp += gbp
326
- site_billed_kwh += billed_kwh
327
- site_billed_net_gbp += billed_net_gbp
328
- site_billed_vat_gbp += billed_vat_gbp
329
- site_billed_gross_gbp += billed_gross_gbp
330
383
  except BadRequest as e:
331
384
  raise BadRequest(
332
385
  f"Problem with g_era {g_era.id}: {e.description}"
333
386
  )
387
+ for k, v in summary_data.items():
388
+ org_row[k] += v
389
+ site_row[k] += v
334
390
 
335
- linked_sites = ", ".join(
336
- s.code
337
- for s in site.find_linked_sites(sess, month_start, month_finish)
338
- )
339
-
340
- site_rows.append(
341
- [
342
- make_val(v)
343
- for v in [
344
- now,
345
- site.code,
346
- site.name,
347
- linked_sites,
348
- month_finish,
349
- site_kwh,
350
- site_gbp,
351
- site_billed_kwh,
352
- site_billed_net_gbp,
353
- site_billed_vat_gbp,
354
- site_billed_gross_gbp,
355
- ]
356
- ]
357
- )
391
+ site_rows.append([make_val(site_row[t]) for t in site_titles])
358
392
  sess.rollback()
393
+ org_rows.append([make_val(org_row[t]) for t in org_titles])
359
394
  write_spreadsheet(rf, compression, site_rows, g_era_rows)
360
395
 
396
+ if scenario_props.get("save_report_run", False):
397
+ report_run_id = ReportRun.w_insert(
398
+ "g_monthly_duration", user_id, fname, {"scenario": scenario_props}
399
+ )
400
+ for tab, rows in (
401
+ ("org", org_rows),
402
+ ("site", site_rows),
403
+ ("era", g_era_rows),
404
+ ):
405
+ titles = rows[0]
406
+ for row in rows[1:]:
407
+ values = dict(zip(titles, row))
408
+ ReportRun.w_insert_row(report_run_id, tab, titles, values, {})
409
+ ReportRun.w_update(report_run_id, "finished")
410
+
361
411
  except BaseException:
362
412
  msg = traceback.format_exc()
363
- sys.stderr.write(msg + "\n")
413
+ print(msg)
364
414
  site_rows.append(["Problem " + msg])
365
415
  write_spreadsheet(rf, compression, site_rows, g_era_rows)
366
416
  finally:
@@ -374,24 +424,55 @@ def content(
374
424
 
375
425
 
376
426
  def do_get(sess):
377
- finish_year = req_int("finish_year")
378
- finish_month = req_int("finish_month")
379
- months = req_int("months")
427
+ now = utc_datetime_now()
428
+ base_name = []
429
+ if "scenario_id" in request.values:
430
+ scenario_id = req_int("scenario_id")
431
+ scenario = Scenario.get_by_id(sess, scenario_id)
432
+ scenario_props = scenario.props
433
+ base_name.append(scenario.name)
434
+ else:
435
+ scenario_props = {}
436
+ base_name.append("g_monthly_duration")
437
+
438
+ if "finish_year" in request.values:
439
+ year = req_int("finish_year")
440
+ month = req_int("finish_month")
441
+ months = req_int("months")
442
+ start_date, _ = next(
443
+ c_months_c(finish_year=year, finish_month=month, months=months)
444
+ )
445
+ scenario_props["scenario_start_year"] = start_date.year
446
+ scenario_props["scenario_start_month"] = start_date.month
447
+ scenario_props["scenario_duration"] = months
380
448
 
381
- site_id = req_int("site_id") if "site_id" in request.values else None
449
+ if "site_id" in request.values:
450
+ site_id = req_int("site_id")
451
+ scenario_props["site_codes"] = [Site.get_by_id(sess, site_id).code]
452
+
453
+ if "site_codes" in request.values:
454
+ site_codes_raw_str = req_str("site_codes")
455
+ site_codes_str = site_codes_raw_str.strip()
456
+ if len(site_codes_str) > 0:
457
+ site_codes = []
458
+
459
+ for site_code in site_codes_str.splitlines():
460
+ Site.get_by_code(sess, site_code) # Check valid
461
+ site_codes.append(site_code)
462
+
463
+ scenario_props["site_codes"] = site_codes
382
464
 
383
465
  if "g_supply_id" in request.values:
384
466
  g_supply_id = req_int("g_supply_id")
385
- else:
386
- g_supply_id = None
467
+ g_supply = GSupply.get_by_id(sess, g_supply_id)
468
+ scenario_props["mprns"] = [g_supply.mprn]
387
469
 
388
470
  if "compression" in request.values:
389
471
  compression = req_bool("compression")
390
472
  else:
391
473
  compression = True
392
474
 
393
- user = g.user
394
- args = (site_id, g_supply_id, user, compression, finish_year, finish_month, months)
475
+ args = scenario_props, g.user.id, compression, now, base_name
395
476
 
396
477
  threading.Thread(target=content, args=args).start()
397
478
  return chellow_redirect("/downloads", 303)
@@ -84,7 +84,7 @@
84
84
  {% endfor %}
85
85
  </ul>
86
86
  </td>
87
- <td>{{snag_group.era.dc_contract}}</td>
87
+ <td>{{snag_group.era.dc_contract.name}}</td>
88
88
  <td>
89
89
  {% if snag_group.era.imp_mpan_core %}
90
90
  {{snag_group.era.imp_mpan_core}}
@@ -221,7 +221,7 @@
221
221
  <li><a href="/user_roles">Users Roles</a></li>
222
222
  <li><a href="/system">System</a></li>
223
223
  <li><a href="/edi_viewer">EDI Viewer</a></li>
224
- <li><a href="/e/scenarios">Scenarios</a></li>
224
+ <li><a href="/scenarios">Scenarios</a></li>
225
225
  <li><a href="/reports/batches">Batches</a></li>
226
226
  <li><a href="/tester">Tester</a></li>
227
227
  <li><a href="/e/site_snags">Site Snags</a></li>
@@ -0,0 +1,61 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Scenarios &raquo; {{scenario.name}}
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/scenarios">Scenarios</a> &raquo; {{scenario.name}}
9
+ [<a href="/scenarios/{{scenario.id}}/edit">edit</a>]
10
+ {% endblock %}
11
+
12
+ {% block content %}
13
+ {% if scenario_props.get('utility') == 'gas' %}
14
+ <form action="/reports/g_monthly_duration">
15
+ <legend>Run With Monthly Duration</legend>
16
+ <fieldset>
17
+ <input type="hidden" name="scenario_id" value="{{scenario.id}}">
18
+ <label>Months</label> {{input_text('months', '1', 2, 2)}}
19
+ <label>Finish Month</label>
20
+ {{input_date('finish', month_finish, resolution='month')}}
21
+ <input type="submit" value="Run">
22
+ </fieldset>
23
+ </form>
24
+ {% else %}
25
+ <form action="/reports/247" method="post">
26
+ <fieldset>
27
+ <legend>Run With Monthly Duration</legend>
28
+ <input type="hidden" name="scenario_id" value="{{scenario.id}}">
29
+ <label>Months</label> {{input_text('months', initial=scenario_duration, size=2, maxlength=2)}}
30
+ <label>Finish month</label>
31
+ {{input_date('finish', scenario_finish_date, resolution='month')}}
32
+ <label>Site Codes</label>
33
+ {{input_textarea('site_codes', '', 5, 40,
34
+ placeholder='One on each line, includes all if left blank')}}
35
+ <input type="submit" value="Run">
36
+ </fieldset>
37
+ </form>
38
+
39
+ <form action="/reports/59" method="post">
40
+ <fieldset>
41
+ <legend>Run With Duration</legend>
42
+ <input type="hidden" name="scenario_id" value="{{scenario.id}}">
43
+ <label>Start Date</label>
44
+ {{ input_date('start', scenario_start_date) }}
45
+ <label>Finish Date</label>
46
+ {{ input_date('finish', scenario_finish_date) }}
47
+ <label>Site Codes</label>
48
+ {{input_textarea('site_codes', site_codes, 5, 40,
49
+ placeholder='One on each line, includes all if left blank')}}
50
+ <input type="submit" value="Run">
51
+ </fieldset>
52
+ </form>
53
+ {% endif %}
54
+
55
+ <h2>Properties</h2>
56
+
57
+ <pre>{{scenario.properties}}</pre>
58
+
59
+ {% include "/scenario_docs.html" %}
60
+
61
+ {% endblock %}
@@ -0,0 +1,21 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Scenarios &raquo; Add
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/e/scenarios">Scenarios</a> &raquo; Add
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ <form action="/e/scenarios/add" method="post">
13
+ <fieldset>
14
+ <legend>Add a scenario</legend>
15
+ <label>Name</label> {{input_text('name')}}
16
+ <label>Properties</label>
17
+ {{input_textarea('properties', initial_props, 20, 80, show_pos=True)}}
18
+ <input type="submit" value="Add">
19
+ </fieldset>
20
+ </form>
21
+ {% endblock %}