chellow 1760028799.0.0__py3-none-any.whl → 1761056531.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/dloads.py CHANGED
@@ -91,7 +91,7 @@ class DloadFile:
91
91
  def open_file(base, user, mode="r", newline=None, is_zip=False):
92
92
  global download_id
93
93
 
94
- base = base.replace("/", "").replace(" ", "")
94
+ base = "".join(x if (x.isalnum() or x in "_.") else "_" for x in base)
95
95
  try:
96
96
  lock.acquire()
97
97
  if len(list(download_path.iterdir())) == 0:
@@ -108,7 +108,7 @@ def open_file(base, user, mode="r", newline=None, is_zip=False):
108
108
  un = user.proxy_username
109
109
  else:
110
110
  un = user.email_address
111
- uname = un.replace("@", "").replace(".", "").replace("\\", "")
111
+ uname = "".join(x if (x.isalnum() or x in "_") else "_" for x in un)
112
112
 
113
113
  names = tuple("_".join((serial, v, uname, base)) for v in ("RUNNING", "FINISHED"))
114
114
  running_name, finished_name = tuple(download_path / name for name in names)
chellow/e/views.py CHANGED
@@ -381,7 +381,7 @@ def channel_snags_get():
381
381
  total_snags_q = total_snags_q.where(Era.dc_contract == contract)
382
382
  snags_q = snags_q.where(Era.dc_contract == contract)
383
383
 
384
- total_snags = g.sess.execute(total_snags_q)
384
+ total_snags = g.sess.scalars(total_snags_q).one()
385
385
 
386
386
  snag_groups = []
387
387
  prev_snag = None
@@ -1116,10 +1116,7 @@ def do_post(sess):
1116
1116
  exp_mpan_core if imp_mpan_core is None else imp_mpan_core
1117
1117
  ]
1118
1118
 
1119
- if "compression" in request.values:
1120
- compression = req_bool("compression")
1121
- else:
1122
- compression = True
1119
+ compression = req_bool("compression")
1123
1120
 
1124
1121
  user = g.user
1125
1122
 
@@ -0,0 +1,288 @@
1
+ import csv
2
+ import sys
3
+ import threading
4
+ import traceback
5
+
6
+ from dateutil.relativedelta import relativedelta
7
+
8
+ from flask import g, redirect, render_template
9
+
10
+ from sqlalchemy.orm import joinedload
11
+ from sqlalchemy.sql.expression import false, select, true
12
+
13
+ from werkzeug.exceptions import BadRequest
14
+
15
+ from chellow.dloads import open_file
16
+ from chellow.models import (
17
+ Channel,
18
+ Contract,
19
+ Era,
20
+ Party,
21
+ RSession,
22
+ Site,
23
+ SiteEra,
24
+ Snag,
25
+ Supply,
26
+ User,
27
+ )
28
+ from chellow.utils import (
29
+ csv_make_val,
30
+ hh_before,
31
+ req_bool,
32
+ req_int,
33
+ req_int_none,
34
+ req_str,
35
+ utc_datetime_now,
36
+ )
37
+
38
+
39
+ def _make_rows(
40
+ sess,
41
+ now,
42
+ contract,
43
+ days_hidden,
44
+ is_ignored,
45
+ show_settlement,
46
+ only_ongoing,
47
+ days_long_hidden,
48
+ limit=None,
49
+ ):
50
+ cutoff_date = now - relativedelta(days=days_hidden)
51
+ q = (
52
+ select(Snag, Channel, Era, Supply, SiteEra, Site, Contract)
53
+ .join(Channel, Snag.channel_id == Channel.id)
54
+ .join(Era, Channel.era_id == Era.id)
55
+ .join(Supply, Era.supply_id == Supply.id)
56
+ .join(SiteEra, Era.site_eras)
57
+ .join(Site, SiteEra.site_id == Site.id)
58
+ .join(Contract, Era.dc_contract_id == Contract.id)
59
+ .where(
60
+ SiteEra.is_physical == true(),
61
+ Snag.start_date < cutoff_date,
62
+ )
63
+ .order_by(
64
+ Site.code,
65
+ Supply.id,
66
+ Channel.imp_related,
67
+ Channel.channel_type,
68
+ Snag.description,
69
+ Snag.start_date,
70
+ Snag.id,
71
+ )
72
+ .options(joinedload(Era.dc_contract))
73
+ )
74
+ if contract is not None:
75
+ q = q.where(Era.dc_contract == contract)
76
+
77
+ if not is_ignored:
78
+ q = q.where(Snag.is_ignored == false())
79
+
80
+ if show_settlement == "yes":
81
+ q = q.join(Party, Supply.dno_id == Party.id).where(Party.dno_code != "99")
82
+ elif show_settlement == "no":
83
+ q = q.join(Party, Supply.dno_id == Party.id).where(Party.dno_code == "99")
84
+ elif show_settlement == "both":
85
+ pass
86
+ else:
87
+ raise BadRequest("show_settlement must be 'yes', 'no' or 'both'.")
88
+
89
+ snag_groups = []
90
+ prev_snag = None
91
+ for snag, channel, era, supply, site_era, site, contract in sess.execute(q):
92
+ snag_start = snag.start_date
93
+ snag_finish = snag.finish_date
94
+
95
+ if snag_finish is None:
96
+ duration = now - snag_start
97
+ age_of_snag = None
98
+ else:
99
+ duration = snag_finish - snag_start
100
+ if hh_before(cutoff_date, snag_finish):
101
+ age_of_snag = None
102
+ else:
103
+ delta = now - snag_finish
104
+ age_of_snag = delta.days
105
+
106
+ if only_ongoing and age_of_snag is not None:
107
+ continue
108
+
109
+ if days_long_hidden is not None and duration < days_long_hidden:
110
+ continue
111
+
112
+ if (
113
+ prev_snag is None
114
+ or channel.era != prev_snag.channel.era
115
+ or snag.start_date != prev_snag.start_date
116
+ or snag.finish_date != prev_snag.finish_date
117
+ or snag.description != prev_snag.description
118
+ ):
119
+ if limit is not None and len(snag_groups) > limit:
120
+ break
121
+
122
+ snag_group = {
123
+ "snags": [],
124
+ "site": site,
125
+ "era": era,
126
+ "supply": supply,
127
+ "description": snag.description,
128
+ "start_date": snag.start_date,
129
+ "finish_date": snag.finish_date,
130
+ "contract": contract,
131
+ "contract_name": contract.name,
132
+ "hidden_days": days_hidden,
133
+ "chellow_id": snag.id,
134
+ "imp_mpan_core": era.imp_mpan_core,
135
+ "exp_mpan_core": era.exp_mpan_core,
136
+ "site_code": site.code,
137
+ "site_name": site.name,
138
+ "snag_description": snag.description,
139
+ "channel_type": channel.channel_type,
140
+ "is_ignored": snag.is_ignored,
141
+ "days_since_finished": age_of_snag,
142
+ "duration": duration.days,
143
+ }
144
+ snag_groups.append(snag_group)
145
+ snag_group["snags"].append(snag)
146
+ prev_snag = snag
147
+
148
+ def make_key(item):
149
+ return item["duration"]
150
+
151
+ snag_groups.sort(key=make_key, reverse=True)
152
+ return snag_groups
153
+
154
+
155
+ def content(
156
+ contract_id,
157
+ days_hidden,
158
+ is_ignored,
159
+ user_id,
160
+ only_ongoing,
161
+ show_settlement,
162
+ days_long_hidden,
163
+ ):
164
+ f = writer = None
165
+ try:
166
+ with RSession() as sess:
167
+ user = User.get_by_id(sess, user_id)
168
+ if contract_id is None:
169
+ contract = None
170
+ namef = "all"
171
+ else:
172
+ contract = Contract.get_dc_by_id(sess, contract_id)
173
+ namef = contract.name
174
+
175
+ f = open_file(f"channel_snags_{namef}.csv", user, mode="w", newline="")
176
+ writer = csv.writer(f, lineterminator="\n")
177
+ titles = (
178
+ "contract",
179
+ "hidden_days",
180
+ "chellow_ids",
181
+ "imp_mpan_core",
182
+ "exp_mpan_core",
183
+ "site_code",
184
+ "site_name",
185
+ "snag_description",
186
+ "channel_types",
187
+ "start_date",
188
+ "finish_date",
189
+ "is_ignored",
190
+ "days_since_finished",
191
+ "duration",
192
+ )
193
+ writer.writerow(titles)
194
+
195
+ now = utc_datetime_now()
196
+ for snag_group in _make_rows(
197
+ sess,
198
+ now,
199
+ contract,
200
+ days_hidden,
201
+ is_ignored,
202
+ show_settlement,
203
+ only_ongoing,
204
+ days_long_hidden,
205
+ ):
206
+
207
+ vals = {
208
+ "contract": snag_group["contract"].name,
209
+ "hidden_days": days_hidden,
210
+ "chellow_ids": [snag.id for snag in snag_group["snags"]],
211
+ "imp_mpan_core": snag_group["imp_mpan_core"],
212
+ "exp_mpan_core": snag_group["exp_mpan_core"],
213
+ "site": snag_group["site"],
214
+ "site_code": snag_group["site"].code,
215
+ "site_name": snag_group["site"].name,
216
+ "snag_description": snag_group["description"],
217
+ "channel_types": [
218
+ f"{s.channel.imp_related}_{s.channel.channel_type}"
219
+ for s in snag_group["snags"]
220
+ ],
221
+ "start_date": snag_group["start_date"],
222
+ "finish_date": snag_group["finish_date"],
223
+ "is_ignored": snag_group["is_ignored"],
224
+ "days_since_finished": snag_group["days_since_finished"],
225
+ "duration": snag_group["duration"],
226
+ }
227
+
228
+ writer.writerow(csv_make_val(vals[t]) for t in titles)
229
+ except BaseException:
230
+ msg = traceback.format_exc()
231
+ sys.stderr.write(msg)
232
+ writer.writerow([msg])
233
+ finally:
234
+ if f is not None:
235
+ f.close()
236
+
237
+
238
+ LIMIT = 200
239
+
240
+
241
+ def do_get(sess):
242
+ contract_id = req_int_none("dc_contract_id")
243
+ days_hidden = req_int("days_hidden")
244
+ is_ignored = req_bool("is_ignored")
245
+ only_ongoing = req_bool("only_ongoing")
246
+ show_settlement = req_str("show_settlement")
247
+ as_csv = req_bool("as_csv")
248
+ days_long_hidden = req_int_none("days_long_hidden")
249
+
250
+ if as_csv:
251
+ args = (
252
+ contract_id,
253
+ days_hidden,
254
+ is_ignored,
255
+ g.user.id,
256
+ only_ongoing,
257
+ show_settlement,
258
+ )
259
+ threading.Thread(target=content, args=args).start()
260
+ return redirect("/downloads", 303)
261
+ else:
262
+ if contract_id is None:
263
+ contract = None
264
+ else:
265
+ contract = Contract.get_dc_by_id(sess, contract_id)
266
+ now = utc_datetime_now()
267
+ snag_groups = _make_rows(
268
+ sess,
269
+ now,
270
+ contract,
271
+ days_hidden,
272
+ is_ignored,
273
+ show_settlement,
274
+ only_ongoing,
275
+ days_long_hidden,
276
+ limit=LIMIT,
277
+ )
278
+ return render_template(
279
+ "reports/channel_snags.html",
280
+ contract=contract,
281
+ limit=LIMIT,
282
+ snag_groups=snag_groups,
283
+ days_hidden=days_hidden,
284
+ is_ignored=is_ignored,
285
+ only_ongoing=only_ongoing,
286
+ show_settlement=show_settlement,
287
+ days_long_hidden=days_long_hidden,
288
+ )
@@ -8,6 +8,15 @@
8
8
  Channel Snags
9
9
  {% endblock %}
10
10
 
11
+ {% block inside_head %}
12
+ <style>
13
+ #snags.htmx-swapping {
14
+ opacity: 0;
15
+ transition: opacity 1s ease-out;
16
+ }
17
+ </style>
18
+ {% endblock %}
19
+
11
20
  {% block nav %}
12
21
  <a href="/e/dc_contracts">DC Contracts</a> &raquo;
13
22
  {% if contract %}
@@ -17,113 +26,36 @@
17
26
  {% endblock %}
18
27
 
19
28
  {% block content %}
20
-
21
- <form action="/reports/233">
29
+ <form hx-get="/reports/channel_snags" hx-params="*" hx-include="this" hx-trigger="load, change"
30
+ hx-target="#snags" hx-swap="outerHTML swap:1s">
22
31
  <fieldset>
23
- <legend>Download CSV</legend>
32
+ <legend>Filters</legend>
24
33
  <input type="hidden" name="dc_contract_id" value="{{contract.id}}">
25
34
  <label>Hide snags &lt; days old</label>
26
- {{input_text('days_hidden', '0', 3, 3)}}
35
+ {{input_text('days_hidden', initial='5', size=3, maxlength=3)}}
27
36
  <label>Include ignored snags</label> {{input_checkbox('is_ignored', False)}}
28
- <input type="submit" value="Download">
29
- </fieldset>
30
- </form>
31
37
 
32
- <p>
33
- The 'Days Since Snag Finished' column is blank if the snag is ongoing (ie. if the
34
- finish date is 'ongoing' or if it finished less than 'Days Hidden' ago).
35
- </p>
38
+ <label>Only show ongoing snags</label>
39
+ {{input_checkbox('only_ongoing', initial=True)}}
36
40
 
37
- <form action="/e/channel_snags">
38
- <fieldset>
39
- <legend>Show Channel Snags</legend>
40
- <input type="hidden" name="dc_contract_id" value="
41
- {%- if contract -%}
42
- {{contract.id}}
43
- {%- endif -%}
44
- ">
45
- <label>Hide snags &lt; days old</label>
46
- {{input_text('days_hidden', '0', 3, 3)}}
47
- <label>Include ignored snags</label> {{input_checkbox('is_ignored', False)}}
48
- <input type="submit" value="Show">
49
- </fieldset>
50
- </form>
41
+ <fieldset>
42
+
43
+ <label>Show settlement only</label>
44
+ {{input_radio('show_settlement', 'yes', initial=False)}}
45
+
46
+ <label>Show non-settlement only</label>
47
+ {{input_radio('show_settlement', 'no', initial=False)}}
51
48
 
52
- <p>
53
- There are {{total_snags}} snag(s) older than {{request.values.days_hidden}} days
54
- {%- if not is_ignored %}
55
- that aren't ignored
56
- {%- endif -%}.
57
- </p>
49
+ <label>Show both</label>
50
+ {{input_radio('show_settlement', 'both', initial=True)}}
58
51
 
59
- <table>
60
- <caption>Snags (truncated after 200)</caption>
61
- <thead>
62
- <tr>
63
- <th>View</th>
64
- <th>Contract</th>
65
- <th>Import MPAN Core</th>
66
- <th>Export MPAN Core</th>
67
- <th>Sites</th>
68
- <th>Snag Description</th>
69
- <th>Channels</th>
70
- <th>Duration</th>
71
- </tr>
72
- </thead>
73
- <tbody>
74
- {% for snag_group in snag_groups %}
75
- <tr>
76
- <td>
77
- <ul>
78
- {% for snag in snag_group.snags %}
79
- <li>
80
- <a href="/e/channel_snags/{{snag.id}}">view</a>
81
- [<a href="/e/channel_snags/{{snag.id}}/edit">edit</a>]
82
- {% if snag.is_ignored %} ignored{% endif %}
83
- </li>
84
- {% endfor %}
85
- </ul>
86
- </td>
87
- <td>{{snag_group.era.dc_contract.name}}</td>
88
- <td>
89
- {% if snag_group.era.imp_mpan_core %}
90
- {{snag_group.era.imp_mpan_core}}
91
- {% endif %}
92
- </td>
93
- <td>
94
- {% if snag_group.era.exp_mpan_core %}
95
- {{snag_group.era.exp_mpan_core}}
96
- {% endif %}
97
- </td>
98
- <td>
99
- <ul>
100
- {% for site in snag_group.sites %}
101
- <li>{{site.code}} {{site.name}}</li>
102
- {% endfor %}
103
- </ul>
104
- </td>
105
- <td>{{snag_group.description}}</td>
106
- <td>
107
- <ul>
108
- {% for snag in snag_group.snags %}
109
- <li>
110
- {% if snag.channel.imp_related %}
111
- Import
112
- {% else %}
113
- Export
114
- {% endif %}
115
- {{snag.channel.channel_type}}
116
- </li>
117
- {% endfor %}
118
- </ul>
119
- </td>
120
- <td>
121
- {{snag_group.start_date|hh_format}} to
122
- {{snag_group.finish_date|hh_format}}
123
- </td>
124
- </tr>
125
- {% endfor %}
126
- </tbody>
127
- </table>
52
+ </fieldset>
53
+
54
+ <label>Hide snags &lt; days long</label>
55
+ {{input_text('days_long_hidden', initial='', size=3, maxlength=3)}}
56
+
57
+ </fieldset>
58
+ </form>
128
59
 
60
+ <div id="snags"></div>
129
61
  {% endblock %}
@@ -124,13 +124,23 @@
124
124
  </div>
125
125
  {%- endmacro -%}
126
126
 
127
- {%- macro input_checkbox(name, initial) %}
128
- <input type="checkbox" name="{{ name }}" value="true"
127
+ {%- macro input_checkbox(name, initial=False, value='true') %}
128
+ <input type="checkbox" name="{{ name }}" value="{{value}}"
129
129
  {%- if request.values[name] -%}
130
- {%- if request.values[name] == 'true' %} checked
130
+ {%- if request.values[name] == value %} checked
131
131
  {%- endif -%}
132
132
  {%- else -%}
133
- {%- if initial == True %} checked{% endif -%}
133
+ {%- if initial %} checked{% endif -%}
134
+ {%- endif -%}>
135
+ {%- endmacro -%}
136
+
137
+ {%- macro input_radio(name, value, initial=False) %}
138
+ <input type="radio" name="{{ name }}" value="{{value}}"
139
+ {%- if request.values[name] -%}
140
+ {%- if request.values[name] == value %} checked
141
+ {%- endif -%}
142
+ {%- else -%}
143
+ {%- if initial %} checked{% endif -%}
134
144
  {%- endif -%}>
135
145
  {%- endmacro -%}
136
146
 
@@ -0,0 +1,91 @@
1
+ {% extends "macros.html" %}
2
+
3
+ {% block html %}
4
+ <table class="sticky" id="snags">
5
+ <caption>
6
+ Snags (truncated after 200)
7
+ <a href="/reports/channel_snags?dc_contract_id={{contract.id}}&amp;days_hidden={{days_hidden}}&amp;is_ignored={{is_ignored}}&amp;only_ongoing={{only_ongoing}}&amp;show_settlement={{show_settlement}}&amp;as_csv=true">Download as CSV</a>
8
+ </caption>
9
+ <thead>
10
+ <tr>
11
+ <th>View</th>
12
+ <th>Contract</th>
13
+ <th>Import MPAN Core</th>
14
+ <th>Export MPAN Core</th>
15
+ <th>Site</th>
16
+ <th>Description</th>
17
+ <th>Channels</th>
18
+ <th>Start Date</th>
19
+ <th>Finish Date</th>
20
+ <th>
21
+ <span title="Blank if the snag is ongoing (ie. if the finish date is 'ongoing' or if it finished less than 'Days Hidden' ago).">Days Since Finished</span>
22
+ </th>
23
+ <th>Duration</th>
24
+ <th>Comm</th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ {% for snag_group in snag_groups %}
29
+ <tr>
30
+ <td>
31
+ <ul>
32
+ {% for snag in snag_group.snags %}
33
+ <li>
34
+ <a href="/e/channel_snags/{{snag.id}}">view</a>
35
+ [<a href="/e/channel_snags/{{snag.id}}/edit">edit</a>]
36
+ {% if snag.is_ignored %} ignored{% endif %}
37
+ </li>
38
+ {% endfor %}
39
+ </ul>
40
+ </td>
41
+ <td>
42
+ <a href="/e/dc_contracts/{{snag_group.contract.id}}">{{snag_group.contract.name}}</a>
43
+ </td>
44
+ <td>
45
+ {% if snag_group.era.imp_mpan_core %}
46
+ <a href="/e/supplies/{{snag_group.supply.id}}">{{snag_group.era.imp_mpan_core}}</a>
47
+ {% endif %}
48
+ </td>
49
+ <td>
50
+ {% if snag_group.era.exp_mpan_core %}
51
+ <a href="/e/supplies/{{snag_group.supply.id}}">{{snag_group.era.exp_mpan_core}}</a>
52
+ {% endif %}
53
+ </td>
54
+ <td>
55
+ <a href="/sites/{{snag_group.site.id}}">{{snag_group.site.code}} {{snag_group.site.name}}</a>
56
+ </td>
57
+ <td>{{snag_group.description}}</td>
58
+ <td>
59
+ <ul>
60
+ {% for snag in snag_group.snags %}
61
+ <li>
62
+ {% if snag.channel.imp_related %}
63
+ Import
64
+ {% else %}
65
+ Export
66
+ {% endif %}
67
+ {{snag.channel.channel_type}}
68
+ </li>
69
+ {% endfor %}
70
+ </ul>
71
+ </td>
72
+ <td>
73
+ {{snag_group.start_date|hh_format}}
74
+ </td>
75
+ <td>
76
+ {{snag_group.finish_date|hh_format}}
77
+ </td>
78
+ <td>
79
+ {% if snag_group.days_since_finished %}
80
+ {{snag_group.days_since_finished}}
81
+ {% endif %}
82
+ </td>
83
+ <td>{{snag_group.duration}}</td>
84
+ <td>
85
+ <a href="/e/comms/{{snag_group.era.comm.id}}" title="{{snag_group.era.comm.description}}">{{snag_group.era.comm.code}}</a>
86
+ </td>
87
+ </tr>
88
+ {% endfor %}
89
+ </tbody>
90
+ </table>
91
+ {% endblock %}
chellow/utils.py CHANGED
@@ -29,11 +29,15 @@ def req_str(name):
29
29
  raise BadRequest(f"The field {name} is required.")
30
30
 
31
31
 
32
- def req_bool(name):
32
+ def req_strs(name):
33
33
  try:
34
- return request.values[name] == "true"
34
+ return request.values.getlist(name)
35
35
  except KeyError:
36
- return False
36
+ raise BadRequest(f"The field {name} is required.")
37
+
38
+
39
+ def req_bool(name):
40
+ return name in request.values
37
41
 
38
42
 
39
43
  def req_int(name):
@@ -43,6 +47,13 @@ def req_int(name):
43
47
  raise BadRequest(f"Problem parsing the field {name} as an integer: {e}")
44
48
 
45
49
 
50
+ def req_ints(name):
51
+ try:
52
+ return [int(v) for v in req_strs(name)]
53
+ except ValueError as e:
54
+ raise BadRequest(f"Problem parsing the field {name} as an integer: {e}")
55
+
56
+
46
57
  def req_int_none(name):
47
58
  val = req_str(name)
48
59
  if val == "":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1760028799.0.0
3
+ Version: 1761056531.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)
@@ -2,7 +2,7 @@ chellow/__init__.py,sha256=9aoSbGmHCIHwzI_c_TLSg5CeKwqAq0I6kKaMvTrTMqg,10936
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
- chellow/dloads.py,sha256=dixp-O0MF2_mlwrnKx3D9DH09Qu05BjTo0rZfigTjR4,5534
5
+ chellow/dloads.py,sha256=sNAPMe4LeHqfisEubGXvraDsUS0F-ujI3WG0Md8DywM,5565
6
6
  chellow/edi_lib.py,sha256=Lq70TUJuogoP5KGrphzUEUfyfgftEclg_iA3mpNAaDI,51324
7
7
  chellow/fake_batch_updater.py,sha256=khAmvSUn9qN04w8C92kRg1UeyQvfLztE7QXv9tUz6nE,11611
8
8
  chellow/general_import.py,sha256=ghybbden66VT4q5J0vYwiNg-6G2vg71EgCN_x3fvhW0,69200
@@ -12,7 +12,7 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
12
12
  chellow/rate_server.py,sha256=RwJo-AzBIdzxx7PAtboZEUH1nUjAeJckw0bk06-9oyM,5672
13
13
  chellow/rrun.py,sha256=sWm_tuJ_6xH3T28TY1w63k1Q44N_S_p_pYF5YCeztqU,2226
14
14
  chellow/testing.py,sha256=Dj2c1NX8lVlygueOrh2eyYawLW6qKEHxNhXVVUaNRO0,3637
15
- chellow/utils.py,sha256=i3GQK9MIcweosZk2gi-nX_IFq2DxURAJDyNoLBg6YwM,19421
15
+ chellow/utils.py,sha256=x6yReQkhFaFGnsuhs6PqrEkE1OfZSPrrC7IqstRXKKU,19701
16
16
  chellow/views.py,sha256=9IUUbYjqEmQQzbYQB4w-ygCo25gecIxYVbxTXRF-YfY,86078
17
17
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
@@ -45,7 +45,7 @@ chellow/e/system_price.py,sha256=6w5J7bzwFAZubE2zdOFRiS8IIrVP8hkoIOaG2yCt-Ic,623
45
45
  chellow/e/tlms.py,sha256=pyL32hPiYd09FbpXVMnBjHsWFEQHs-Az945V7EShGiY,9116
46
46
  chellow/e/tnuos.py,sha256=NBmc-f3oezrl4gviAKobljHfICTpBKxxxEGBGJi_lRk,4927
47
47
  chellow/e/triad.py,sha256=uQIngSrz8irBXQ0Rp_s8nAUzu-y2Ms7aj4B38_Ff8y8,13720
48
- chellow/e/views.py,sha256=UH9Wc6UOiCcs9OtXWU8MK2eBi9d_J2xJN6qmHgZVat0,234245
48
+ chellow/e/views.py,sha256=7WKLrqYtk018oXjmx9YBv72B5hpgOV0eTJYqsIoBYsE,234251
49
49
  chellow/e/bill_parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  chellow/e/bill_parsers/activity_mop_stark_xlsx.py,sha256=hcbjxqLOe7qkDjS7enCpmfyGwm3d-pq3u5VQIaUmVTw,3796
51
51
  chellow/e/bill_parsers/annual_mop_stark_xlsx.py,sha256=yoEGQS0qcrv3TWFfxELIIi8f1CyKcIzh0xVaPoz2w2s,4191
@@ -86,9 +86,8 @@ chellow/reports/report_183.py,sha256=fGONpKEIXTqIT_3dE5jZKBIesWPgLq-chHO6X22dIv0
86
86
  chellow/reports/report_187.py,sha256=RCvxDnkfRu7ocKUPEX0qtVphGOBEl9RULy790uteXcQ,9915
87
87
  chellow/reports/report_219.py,sha256=3Zs16ka6dWWY_lCDoA-ZFrlPb4vHk1LQoa2yrlTMntA,7117
88
88
  chellow/reports/report_231.py,sha256=WARLztV2HzBFSi5_xvY_DTXVyuBBbBYiU0RazfjKw-Y,5270
89
- chellow/reports/report_233.py,sha256=qJdPFuBgQAWD24BsljgGJ44L_RGdHOA1RqsxGjTWhFc,4975
90
89
  chellow/reports/report_241.py,sha256=lfivElRkV_CZAhck0rxAuhqzgponj3aWwmwGMNQvUxg,5343
91
- chellow/reports/report_247.py,sha256=KuNCWfYt1WByFv3EfiJF1-5blhJVWLbAdhlI5KD9HxQ,43346
90
+ chellow/reports/report_247.py,sha256=pbWfM-fUeuakZxH1Ovlx7-XfUg6SL9t51swK3yMc1lc,43253
92
91
  chellow/reports/report_29.py,sha256=ZXnq6Kg5on-IPquUsH4bn46mBVESY_WhwqDWZPXOJwo,2695
93
92
  chellow/reports/report_291.py,sha256=BnWtxe0eWN2QKKWpwjs5-RI5LbReBKL119QbkrkNhV8,7478
94
93
  chellow/reports/report_33.py,sha256=lt1EN_LNx6u-AgdaS3YRkPMZA33JgMcELolHF4oJUMw,16689
@@ -101,6 +100,7 @@ chellow/reports/report_87.py,sha256=udzbCuXcckWD-OHmfJCT6bwg_paYhm4vfDWlL8WM-jA,
101
100
  chellow/reports/report_asset_comparison.py,sha256=Cl2NgvbclqhOVvKuUu3sajTVO2JupMfzK3bV0_K8eNs,6077
102
101
  chellow/reports/report_batches.py,sha256=MoCv2dE-JgaJzaMjMA-kZrPkYR13uDoXer5UuF12OYc,4757
103
102
  chellow/reports/report_bills.py,sha256=LP7XDxzO0Fp10c8xDE67e4tHTEc7nN74ESQBy762Nx4,3401
103
+ chellow/reports/report_channel_snags.py,sha256=RV5L3zRwFmuZws7w9H9VzLCFlQ6U3-ZU6jn4j5VydNc,8626
104
104
  chellow/reports/report_csv_llfcs.py,sha256=mMB06b6Jems5kcQU4H4Le_fyKgVr8THP8BCx3pkvg5E,1903
105
105
  chellow/reports/report_csv_site_hh_data.py,sha256=ik0OkGVo1bfTXLdcT3gPUHnxSkSdorzZheP3qgnOR5c,4331
106
106
  chellow/reports/report_csv_site_snags.py,sha256=AuAy6vjL0g7vwPPAZhBqxOyITL9_jnyFj012MnUcxxk,2627
@@ -134,7 +134,7 @@ chellow/templates/home.html,sha256=7xiD6QLju3ugALzMlzTJosRyMUmQGWPK6jDF7oZbnAQ,5
134
134
  chellow/templates/input_date.html,sha256=rpgB5n0LfN8Y5djN_ZiuSxqdskxzCoKrEqI7hyJkVQo,1248
135
135
  chellow/templates/local_report.html,sha256=pV7_0QwyQ-D3OS9LXrly5pq3qprZnwTCoq6vCnMTkS4,1332
136
136
  chellow/templates/local_reports.html,sha256=4wbfVkY4wUfSSfWjxqIsvCpIsa9k7H_dGAjznrG5jNM,701
137
- chellow/templates/macros.html,sha256=zd5P3irbRMXiK6LnWdbv5M0SYX2roZKhEzNTt2_uPyM,5031
137
+ chellow/templates/macros.html,sha256=FlRTl9W2avRXErnS3vzeOxciGXJYoKptiR08iVmSpfw,5353
138
138
  chellow/templates/national_grid.html,sha256=8W92tsjlqPSB0J--nyFIi-wzFae9CyDr6fODXLZp8ic,991
139
139
  chellow/templates/non_core_auto_importer.html,sha256=s9SluRN1bHTHjd8GP6uFhk6LrZYG8dqBG3y94zUKe5Q,944
140
140
  chellow/templates/non_core_contract.html,sha256=BdGtxErTvw4DwXLb6B2vimuU6ode3fFA-90kBmHCwYc,1754
@@ -181,7 +181,7 @@ chellow/templates/e/channel_add.html,sha256=szwQJBHx2kAoSFomXDHD0N_7PSd4drqOoAWh
181
181
  chellow/templates/e/channel_edit.html,sha256=OUkdiS2NBQ_w4gmxRxXAcRToM5BT8DWWJrtQMFs-GUU,2198
182
182
  chellow/templates/e/channel_snag.html,sha256=wBJ5KBfeJdAeRmaB0qu0AD9Z4nM5fn6tJ_8bNqTWtoo,1477
183
183
  chellow/templates/e/channel_snag_edit.html,sha256=sUFI4Ml3F1B35x8_t_Pz3hWuQN9Xtxr3kt4u8hdxNwY,1911
184
- chellow/templates/e/channel_snags.html,sha256=PuYnDycsQcwmmW9TfCCa_WgJRHOFn9HAnNm51rH7l9k,3157
184
+ chellow/templates/e/channel_snags.html,sha256=TgRhS6DcOMyoAowwrCjPtq9iuX6lpvG3uchcd3g80kk,1593
185
185
  chellow/templates/e/comm.html,sha256=DSlAaDg1r4KYq9VUgDtt2lgW6apZjZvwhMujIJINmps,383
186
186
  chellow/templates/e/comms.html,sha256=bUXZePnMfpKk1E71qLGOSkx8r3GxdPPD_-MosIXXq4s,434
187
187
  chellow/templates/e/cop.html,sha256=ULv7ALFJHMUgPX96hQNm2irc3edtKYHS6fYAxvmzj8M,376
@@ -400,6 +400,7 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
400
400
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
401
401
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
402
402
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
403
- chellow-1760028799.0.0.dist-info/METADATA,sha256=zm1x1B_QH5ZI_HiJ4vFBX0RbyndLd_v2iw77jNS4QqU,12500
404
- chellow-1760028799.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
405
- chellow-1760028799.0.0.dist-info/RECORD,,
403
+ chellow/templates/reports/channel_snags.html,sha256=_aAgFgMlTkK2HuKFU8YisAIcUYfg6Hqhgyf5MZpdK8c,2629
404
+ chellow-1761056531.0.0.dist-info/METADATA,sha256=x465BtJg_0nCB84ep2IVjHyEpTTyZZixgcyJ_zKU7fk,12500
405
+ chellow-1761056531.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
406
+ chellow-1761056531.0.0.dist-info/RECORD,,
@@ -1,150 +0,0 @@
1
- import csv
2
- import sys
3
- import threading
4
- import traceback
5
-
6
- from dateutil.relativedelta import relativedelta
7
-
8
- from flask import g, redirect
9
-
10
- from sqlalchemy.orm import joinedload
11
- from sqlalchemy.sql.expression import false, select, true
12
-
13
- from chellow.dloads import open_file
14
- from chellow.models import (
15
- Channel,
16
- Contract,
17
- Era,
18
- Session,
19
- Site,
20
- SiteEra,
21
- Snag,
22
- Supply,
23
- User,
24
- )
25
- from chellow.utils import (
26
- csv_make_val,
27
- hh_before,
28
- req_bool,
29
- req_int,
30
- req_int_none,
31
- utc_datetime_now,
32
- )
33
-
34
-
35
- def content(contract_id, days_hidden, is_ignored, user_id):
36
- f = writer = None
37
- try:
38
- with Session() as sess:
39
- user = User.get_by_id(sess, user_id)
40
- if contract_id is None:
41
- contract = None
42
- namef = "all"
43
- else:
44
- contract = Contract.get_dc_by_id(sess, contract_id)
45
- namef = "".join(x if x.isalnum() else "_" for x in contract.name)
46
-
47
- f = open_file(f"channel_snags_{namef}.csv", user, mode="w", newline="")
48
- writer = csv.writer(f, lineterminator="\n")
49
- titles = (
50
- "contract",
51
- "Hidden Days",
52
- "Chellow Id",
53
- "Imp MPAN Core",
54
- "Exp MPAN Core",
55
- "Site Code",
56
- "Site Name",
57
- "Snag Description",
58
- "Import Related?",
59
- "Channel Type",
60
- "Start Date",
61
- "Finish Date",
62
- "Is Ignored?",
63
- "Days Since Snag Finished",
64
- "Duration Of Snag (Days)",
65
- )
66
- writer.writerow(titles)
67
-
68
- now = utc_datetime_now()
69
- cutoff_date = now - relativedelta(days=days_hidden)
70
- q = (
71
- select(Snag, Channel, Era, Supply, SiteEra, Site)
72
- .join(Channel, Snag.channel_id == Channel.id)
73
- .join(Era, Channel.era_id == Era.id)
74
- .join(Supply, Era.supply_id == Supply.id)
75
- .join(SiteEra, Era.site_eras)
76
- .join(Site, SiteEra.site_id == Site.id)
77
- .where(
78
- SiteEra.is_physical == true(),
79
- Snag.start_date < cutoff_date,
80
- )
81
- .order_by(
82
- Site.code,
83
- Supply.id,
84
- Channel.imp_related,
85
- Channel.channel_type,
86
- Snag.description,
87
- Snag.start_date,
88
- Snag.id,
89
- )
90
- .options(joinedload(Era.dc_contract))
91
- )
92
- if contract is not None:
93
- q = q.where(Era.dc_contract == contract)
94
-
95
- if not is_ignored:
96
- q = q.where(Snag.is_ignored == false())
97
-
98
- for snag, channel, era, supply, site_era, site in sess.execute(q):
99
- snag_start = snag.start_date
100
- snag_finish = snag.finish_date
101
- imp_mc = "" if era.imp_mpan_core is None else era.imp_mpan_core
102
- exp_mc = "" if era.exp_mpan_core is None else era.exp_mpan_core
103
-
104
- if snag_finish is None:
105
- duration = now - snag_start
106
- age_of_snag = None
107
- else:
108
- duration = snag_finish - snag_start
109
- if hh_before(cutoff_date, snag_finish):
110
- age_of_snag = None
111
- else:
112
- delta = now - snag_finish
113
- age_of_snag = delta.days
114
-
115
- vals = {
116
- "contract": era.dc_contract.name,
117
- "Hidden Days": days_hidden,
118
- "Chellow Id": snag.id,
119
- "Imp MPAN Core": imp_mc,
120
- "Exp MPAN Core": exp_mc,
121
- "Site Code": site.code,
122
- "Site Name": site.name,
123
- "Snag Description": snag.description,
124
- "Import Related?": channel.imp_related,
125
- "Channel Type": channel.channel_type,
126
- "Start Date": snag_start,
127
- "Finish Date": snag_finish,
128
- "Is Ignored?": snag.is_ignored,
129
- "Days Since Snag Finished": age_of_snag,
130
- "Duration Of Snag (Days)": duration.days,
131
- }
132
-
133
- writer.writerow(csv_make_val(vals[t]) for t in titles)
134
- except BaseException:
135
- msg = traceback.format_exc()
136
- sys.stderr.write(msg)
137
- writer.writerow([msg])
138
- finally:
139
- if f is not None:
140
- f.close()
141
-
142
-
143
- def do_get(sess):
144
- contract_id = req_int_none("dc_contract_id")
145
- days_hidden = req_int("days_hidden")
146
- is_ignored = req_bool("is_ignored")
147
-
148
- args = contract_id, days_hidden, is_ignored, g.user.id
149
- threading.Thread(target=content, args=args).start()
150
- return redirect("/downloads", 303)