scdata 1.5.0__tar.gz → 1.5.2__tar.gz

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.
Files changed (99) hide show
  1. {scdata-1.5.0 → scdata-1.5.2}/MANIFEST.in +3 -2
  2. {scdata-1.5.0/scdata.egg-info → scdata-1.5.2}/PKG-INFO +3 -6
  3. scdata-1.5.2/requirements.txt +12 -0
  4. {scdata-1.5.0 → scdata-1.5.2}/scdata/__init__.py +1 -1
  5. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/device.py +25 -6
  6. scdata-1.5.2/scdata/plot/templates/map_legend.html +53 -0
  7. scdata-1.5.2/scdata/test/export/templates/delivery_template.html +360 -0
  8. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/export/to_file.py +15 -19
  9. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/custom_logger.py +0 -1
  10. {scdata-1.5.0 → scdata-1.5.2/scdata.egg-info}/PKG-INFO +3 -6
  11. {scdata-1.5.0 → scdata-1.5.2}/scdata.egg-info/SOURCES.txt +2 -0
  12. {scdata-1.5.0 → scdata-1.5.2}/scdata.egg-info/requires.txt +2 -5
  13. {scdata-1.5.0 → scdata-1.5.2}/setup.py +1 -1
  14. scdata-1.5.0/requirements.txt +0 -18
  15. {scdata-1.5.0 → scdata-1.5.2}/LICENSE +0 -0
  16. {scdata-1.5.0 → scdata-1.5.2}/README.md +0 -0
  17. {scdata-1.5.0 → scdata-1.5.2}/scdata/_config/__init__.py +0 -0
  18. {scdata-1.5.0 → scdata-1.5.2}/scdata/_config/config.py +0 -0
  19. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/__init__.py +0 -0
  20. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/check/__init__.py +0 -0
  21. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/check/flats.py +0 -0
  22. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/check/gaps.py +0 -0
  23. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/check/implausible.py +0 -0
  24. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/check/outliers.py +0 -0
  25. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/plot/__init__.py +0 -0
  26. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/__init__.py +0 -0
  27. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/alphasense.py +0 -0
  28. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/error_codes.py +0 -0
  29. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/formulae.py +0 -0
  30. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/geoseries.py +0 -0
  31. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/params.py +0 -0
  32. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/regression.py +0 -0
  33. {scdata-1.5.0 → scdata-1.5.2}/scdata/device/process/timeseries.py +0 -0
  34. {scdata-1.5.0 → scdata-1.5.2}/scdata/io/__init__.py +0 -0
  35. {scdata-1.5.0 → scdata-1.5.2}/scdata/io/device_api.py +0 -0
  36. {scdata-1.5.0 → scdata-1.5.2}/scdata/io/device_file.py +0 -0
  37. {scdata-1.5.0 → scdata-1.5.2}/scdata/io/model.py +0 -0
  38. {scdata-1.5.0 → scdata-1.5.2}/scdata/models/__init__.py +0 -0
  39. {scdata-1.5.0 → scdata-1.5.2}/scdata/models/models.py +0 -0
  40. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/__init__.py +0 -0
  41. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/box_plot.py +0 -0
  42. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/heatmap_iplot.py +0 -0
  43. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/heatmap_plot.py +0 -0
  44. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/heatmap_uplot.py +0 -0
  45. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/maps.py +0 -0
  46. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/scatter_dispersion_grid.py +0 -0
  47. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/scatter_iplot.py +0 -0
  48. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/scatter_plot.py +0 -0
  49. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/target_diagram.py +0 -0
  50. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/tools.py +0 -0
  51. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_dendrogram.py +0 -0
  52. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_dispersion_grid.py +0 -0
  53. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_dispersion_plot.py +0 -0
  54. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_dispersion_uplot.py +0 -0
  55. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_iplot.py +0 -0
  56. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_panel.py +0 -0
  57. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_plot.py +0 -0
  58. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_scatter.py +0 -0
  59. {scdata-1.5.0 → scdata-1.5.2}/scdata/plot/ts_uplot.py +0 -0
  60. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/__init__.py +0 -0
  61. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/checks/__init__.py +0 -0
  62. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/checks/checks.py +0 -0
  63. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/dispersion/__init__.py +0 -0
  64. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/dispersion/dispersion.py +0 -0
  65. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/export/__init__.py +0 -0
  66. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/export/templates/sc_template.html +0 -0
  67. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/test.py +0 -0
  68. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/tools/__init__.py +0 -0
  69. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/tools/combine.py +0 -0
  70. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/tools/history.py +0 -0
  71. {scdata-1.5.0 → scdata-1.5.2}/scdata/test/tools/prepare.py +0 -0
  72. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/__init__.py +0 -0
  73. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/cleaning.py +0 -0
  74. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/date.py +0 -0
  75. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/dictmerge.py +0 -0
  76. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/find.py +0 -0
  77. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/gets.py +0 -0
  78. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/interim/example.csv +0 -0
  79. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/interim/geodata.csv +0 -0
  80. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/lazy.py +0 -0
  81. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/location.py +0 -0
  82. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/merging.py +0 -0
  83. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/report.py +0 -0
  84. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/series.py +0 -0
  85. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/stats.py +0 -0
  86. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/tree.py +0 -0
  87. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/units.py +0 -0
  88. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/uploads/example_upload_1.json +0 -0
  89. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/uploads/example_zenodo_upload.yaml +0 -0
  90. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/uploads/report.pdf +0 -0
  91. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/url_check.py +0 -0
  92. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/zenodo.py +0 -0
  93. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/zenodo_templates/README.md +0 -0
  94. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/zenodo_templates/template_zenodo_dataset.json +0 -0
  95. {scdata-1.5.0 → scdata-1.5.2}/scdata/tools/zenodo_templates/template_zenodo_publication.json +0 -0
  96. {scdata-1.5.0 → scdata-1.5.2}/scdata.egg-info/dependency_links.txt +0 -0
  97. {scdata-1.5.0 → scdata-1.5.2}/scdata.egg-info/not-zip-safe +0 -0
  98. {scdata-1.5.0 → scdata-1.5.2}/scdata.egg-info/top_level.txt +0 -0
  99. {scdata-1.5.0 → scdata-1.5.2}/setup.cfg +0 -0
@@ -2,8 +2,9 @@ include README.md
2
2
  include LICENSE
3
3
  include setup.py
4
4
  include setup.cfg
5
- include scdata/test/export/templates/sc_template.html
5
+ include scdata/test/export/templates/*.html
6
+ include scdata/plot/templates/*.html
6
7
  include scdata/tools/interim/*.csv
7
8
  include scdata/tools/zenodo_templates/*.*
8
9
  include scdata/tools/uploads/*.*
9
- include requirements.txt
10
+ include requirements.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scdata
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: Analysis of sensors and time series data
5
5
  Home-page: https://github.com/fablabbcn/smartcitizen-data
6
6
  Author: oscgonfer
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3
18
18
  Requires-Python: >=3.9
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
- Requires-Dist: geopy~=1.21.0
21
+ Requires-Dist: geopy<3,>=2.4
22
22
  Requires-Dist: Jinja2~=3.1.2
23
23
  Requires-Dist: matplotlib
24
24
  Requires-Dist: pandas~=2.2.2
@@ -28,11 +28,8 @@ Requires-Dist: requests
28
28
  Requires-Dist: scipy
29
29
  Requires-Dist: scikit-learn
30
30
  Requires-Dist: seaborn
31
- Requires-Dist: smartcitizen-connector>=1.5.0
32
- Requires-Dist: termcolor==1.1.0
31
+ Requires-Dist: smartcitizen-connector>=1.5.1
33
32
  Requires-Dist: timezonefinder~=6.1.9
34
- Requires-Dist: urllib3
35
- Requires-Dist: Flask~=2.2.2
36
33
  Provides-Extra: plotting
37
34
  Requires-Dist: bokeh; extra == "plotting"
38
35
  Requires-Dist: panel; extra == "plotting"
@@ -0,0 +1,12 @@
1
+ geopy>=2.4,<3
2
+ Jinja2~=3.1.2
3
+ matplotlib
4
+ pandas~=2.2.2
5
+ pydantic~=2.8
6
+ PyYAML~=6.0.1
7
+ requests
8
+ scipy
9
+ scikit-learn
10
+ seaborn
11
+ smartcitizen-connector>=1.5.1
12
+ timezonefinder~=6.1.9
@@ -3,4 +3,4 @@ from .device import Device
3
3
  from .test import Test
4
4
  from .models import Source, TestOptions, DeviceOptions, APIParams, FileParams, CSVParams
5
5
 
6
- __version__ = '1.5.0'
6
+ __version__ = '1.5.2'
@@ -304,22 +304,33 @@ class Device(BaseModel):
304
304
  def __unavailable_inputs__(self, channel, skipped):
305
305
  '''
306
306
  Returns the inputs of a channel that the device cannot provide: known sensors
307
- that the device does not have, or channels that were skipped
307
+ without data (not in the device, or without readings), or channels that were skipped
308
308
  '''
309
309
  channel_names = [channel.name for channel in self.channels]
310
- sensor_map = self.__sensor_map__()
311
- known_sensors = [name.name for name in config.names.get(self.source.handler, [])]
310
+ known_sensors = {name.name for names in config.names.values() for name in names}
312
311
 
313
312
  required, _ = self.__channel_inputs__(channel)
314
313
  unavailable = [item for item in channel.depends_on if item in skipped]
315
314
  for item in required:
316
315
  if item in channel_names:
317
316
  if item in skipped: unavailable.append(item)
318
- elif item in known_sensors and item not in sensor_map:
317
+ elif item in known_sensors and item not in self.data.columns:
319
318
  unavailable.append(item)
320
319
 
321
320
  return sorted(set(unavailable))
322
321
 
322
+ def __failed_inputs__(self, channel):
323
+ '''
324
+ Returns the inputs of a channel whose data request failed in the handler
325
+ '''
326
+ failed = getattr(self.handler, 'failed_sensors', None) or []
327
+ rename = getattr(self, '_rename', {})
328
+ failed = set(failed) | {rename.get(name, name) for name in failed}
329
+
330
+ required, _ = self.__channel_inputs__(channel)
331
+
332
+ return sorted(required & failed)
333
+
323
334
  def add_channel(self, calculated_channel = dict()):
324
335
  '''
325
336
  Add a CalculatedChannel to the device to be processed by a callable function
@@ -608,10 +619,18 @@ class Device(BaseModel):
608
619
  if channel.name not in _channels_list: continue
609
620
  logger.info(f'Processing {channel.name}')
610
621
 
611
- # Skip channels whose inputs the device does not have (and their dependants)
622
+ # Data that failed to load cannot be skipped: processing fails
623
+ failed = self.__failed_inputs__(channel)
624
+ if failed:
625
+ logger.error(f'Cannot process {channel.name}. Data request failed for: {failed}')
626
+ process_ok = False
627
+ skipped.add(channel.name)
628
+ continue
629
+
630
+ # Skip channels whose inputs have no data (and their dependants)
612
631
  unavailable = self.__unavailable_inputs__(channel, skipped)
613
632
  if unavailable:
614
- logger.warning(f'Skipping {channel.name}. Device does not provide: {unavailable}')
633
+ logger.warning(f'Skipping {channel.name}. No data for: {unavailable}')
615
634
  skipped.add(channel.name)
616
635
  continue
617
636
 
@@ -0,0 +1,53 @@
1
+ <div id='maplegend' class='maplegend'
2
+ style='position: absolute; z-index:9999; background-color:rgba(255, 255, 255, 0.9); border-radius:5px; padding: 10px; font-size:14px; right: 10px; top: 10px;'>
3
+ <div class='legend-title'>Legend</div>
4
+ <div class='legend-scale'>
5
+ <ul class='legend-labels'>
6
+ {% for label, color in legend_labels.items() %}
7
+ <li><span style="background:{{color}};opacity:0.8;"></span>{{label}}</li>
8
+ {% endfor %}
9
+ </ul>
10
+ </div>
11
+ </div>
12
+
13
+ <style type='text/css'>
14
+ .maplegend .legend-title {
15
+ text-align: center;
16
+ margin-bottom: 5px;
17
+ font-weight: bold;
18
+ font-size: 90%;
19
+ }
20
+ .maplegend .legend-scale ul {
21
+ margin: 0;
22
+ margin-bottom: 5px;
23
+ padding: 0;
24
+ float: left;
25
+ list-style: none;
26
+ }
27
+ .maplegend .legend-scale ul li {
28
+ font-size: 80%;
29
+ list-style: none;
30
+ margin-left: 0;
31
+ line-height: 18px;
32
+ margin-bottom: 2px;
33
+ }
34
+ .maplegend ul.legend-labels li span {
35
+ display: block;
36
+ float: left;
37
+ height: 16px;
38
+ width: 16px;
39
+ margin-right: 5px;
40
+ margin-left: 0;
41
+ border: 1px solid #999;
42
+ border-radius:15px;
43
+ }
44
+ .maplegend .legend-source {
45
+ font-size: 80%;
46
+ color: #777;
47
+ clear: both;
48
+ }
49
+ .maplegend a {
50
+ color: #777;
51
+ }
52
+ </style>
53
+
@@ -0,0 +1,360 @@
1
+ {% if full %}
2
+ <!doctype html>
3
+
4
+ <html>
5
+
6
+ <head>
7
+ <meta charset="utf-8"/>
8
+
9
+ <title>{{ descriptor['id'] }}</title>
10
+
11
+ <!-- Web Fonts -->
12
+ <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i%7CMontserrat:400,700"
13
+ rel="stylesheet">
14
+
15
+ <link rel="shortcut icon" type="image/jpg" href="https://smartcitizen.me/assets/images/smartcitizen_logo.svg"/>
16
+ </head>
17
+
18
+ <body>
19
+
20
+ {% endif %}
21
+
22
+ <div {% if header %} id="report-header" {% else %} id="normal-header" {% endif %}>
23
+ <h1 {% if header %} id="top" {% endif %}>{{ title }}</h1>
24
+
25
+ {% if header %}
26
+ <div style="text-align: right">
27
+ <img src="https://smartcitizen.me/assets/images/smartcitizen_logo.svg" alt="Smart Citizen Logo" width=50px height=50px>
28
+ </div>
29
+ {% endif %}
30
+ </div>
31
+
32
+ <div id="container">
33
+
34
+ {% if details %}
35
+
36
+ <div class="header">
37
+ <h2>Delivery</h2>
38
+ </div>
39
+
40
+ <ul>
41
+ <li><strong>Delivery:</strong> {{ descriptor.delivery.destination }}</li>
42
+ <li><strong>Batch:</strong> {{ descriptor.delivery.batch }}</li>
43
+ <li><strong>Author:</strong> {{ descriptor.author }}</li>
44
+ </ul>
45
+
46
+ <h3>Devices</h3>
47
+
48
+ {{ descriptor.delivery.devices }}
49
+
50
+ {% endif %}
51
+
52
+ <div class="header">
53
+ <h2>Test description</h2>
54
+ </div>
55
+
56
+ <p>The devices are co-located for a period of at least 72h in an <b>indoor</b> environment. Devices that show an abnormal behaviour are analysed and replaced if necessary.
57
+
58
+ <h3> Test conditions </h3>
59
+
60
+ <ul>
61
+ <li>Indoor environment</li>
62
+ <li>No controlled temperature/humidity setup</li>
63
+ </ul>
64
+
65
+ <h3> Additional comments </h3>
66
+
67
+ <ul>
68
+ <li>The test batch average is considered reference for batch dispersion limits</li>
69
+ <li>The confidence intervals used are those of the normal distribution (sample numbers >30) and of the t-student distribution (sample numbers <30).</li>
70
+ <li>The individual sensors components integrated in the Smart Citizen hardware have their own accuracies and dispersions, for which Smart Citizen cannot assume any liability other than trying to work with the most appropiate selection. The tests we perform are aimed to determine and assume any failures in the sensors and their integration within the Smart Citizen hardware. For more information, please check the <a href="https://docs.smartcitizen.me">official documentation</a> and the datasheets of each of the sensors in the <a href="https://docs.smartcitizen.me/Components/boards/Urban%20Board/">sensors part</a>.</li>
71
+ </ul>
72
+
73
+ {% if devices_summary %}
74
+
75
+ <div class="header">
76
+ <h2>Devices summary</h2>
77
+ </div>
78
+
79
+ <table>
80
+ <tr class="first">
81
+ <th style="width: 15vh;"> Device </th>
82
+ <th class='table-center' style="width: 35vh;"> Blueprint </th>
83
+ <th class='table-center' style="width: 20vh;"> Documentation </th>
84
+ <th class='table-center' style="width: 20vh;"> Timezone </th>
85
+ <th class='table-center' style="width: 20vh;"> Source </th>
86
+ </tr>
87
+
88
+ {% for key, value in descriptor.devices.items() %}
89
+ <tr>
90
+ <td>{{ key }}</td>
91
+ <td class='table-center'>{{ value.blueprint.upper().replace("_", " ") }}</td>
92
+ <td class='table-center'><a href="{{ value.documentation }}" >Documentation</a></td>
93
+ <td class='table-center'><a>{{ value.timezone }}</a></td>
94
+ <td class='table-center'><em>{{ value.source.upper().replace("_", " ") }}</em></td>
95
+ </tr>
96
+ {% endfor %}
97
+ </table>
98
+
99
+ <div class="header">
100
+ <h2>Device information</h2>
101
+ </div>
102
+
103
+ {% for device, _ in descriptor.devices.items() %}
104
+ <details>
105
+ <summary>Device {{ device }}</summary>
106
+
107
+ {% if descriptor.devices[device]['sensors'].items().__len__() == 0 %}
108
+ <p style="margin-left: 2rem;">Device empty. No data to show</p>
109
+ {% else %}
110
+ <table>
111
+ <tr class="first">
112
+ <th class='table-center' style="width: 20vh;"> Channel </th>
113
+ <th class='table-center' style="width: 10vh;"> Units </th>
114
+ <th class='table-center' style="width: 20vh;"> Type (raw/processed) </th>
115
+ <th class='table-center' style="width: 20vh;"> Algorithm </th>
116
+ <th class='table-center' style="width: 60vh;"> Description </th>
117
+ </tr>
118
+
119
+ {% for metric, value in descriptor.devices[device]['sensors'].items() %}
120
+ <tr>
121
+ <td>{{ metric }}</td>
122
+ <td class='table-center'><em>{{ value.units }}</em></td>
123
+ <td class='table-center'>Raw</td>
124
+ <td class='table-center'>n/a</td>
125
+ {% if 'desc' in value %}
126
+ <td class='table-center'>{{ value.desc }}</td>
127
+ {% else %}
128
+ <td class='table-center'>No description available</td>
129
+ {% endif %}
130
+ </tr>
131
+ {% endfor %}
132
+
133
+ {% if 'metrics' in descriptor.devices[device] %}
134
+ {% for metric, value in descriptor.devices[device]['metrics'].items() %}
135
+ <tr>
136
+ <td>{{ metric }}</td>
137
+ <td class='table-center'>{{ value.units }}</td>
138
+ <td class='table-center'>Processed</td>
139
+ <td class='table-center'>{{ value.process }}</td>
140
+ {% if value %}
141
+ {% if 'desc' in value %}
142
+ <td class='table-center'>{{ value.desc }}</td>
143
+ {% endif %}
144
+ {% else %}
145
+ <td class='table-center'>No description available</td>
146
+ {% endif %}
147
+ </tr>
148
+ {% endfor %}
149
+ {% endif %}
150
+ </table>
151
+ <br>
152
+ </details>
153
+ {% endif %}
154
+ {% endfor %}
155
+
156
+ {% endif %}
157
+
158
+ {% if content %}
159
+
160
+ <h2> Results </h2>
161
+
162
+ {% for item, value in content.items() %}
163
+ <h3>{{ value.title }}</h3>
164
+
165
+ {% if 'text' in value %}
166
+ <p>
167
+ {{ value.text }}
168
+ </p>
169
+ {% endif %}
170
+
171
+ {% if 'image' in value %}
172
+ <div class="img-container">
173
+ <img src="{{ value.image }}" style="width:80%; max-width: 1000px">
174
+ </div>
175
+ {% endif %}
176
+
177
+ {% if 'iframe' in value %}
178
+ <div class="iframe-container">
179
+ {{ value.iframe }}
180
+ </div>
181
+ {% endif %}
182
+
183
+ {% endfor %}
184
+
185
+ {% endif %}
186
+
187
+ </div>
188
+
189
+ {% if full %}
190
+
191
+ </body>
192
+
193
+ </html>
194
+
195
+ {% endif %}
196
+
197
+ <style type="text/css">
198
+
199
+ body {
200
+ text-align: justify;
201
+ line-height: 1.6;
202
+ font-size: .9rem;
203
+ font-family: Roboto, sans-serif;
204
+ font-weight: lighter;
205
+ margin: 15px;
206
+ overflow: visible;
207
+ }
208
+
209
+ div#container{
210
+ padding-left: 40px;
211
+ padding-right: 30px;
212
+ {% if header %}
213
+ padding-top: 40px;
214
+ {% else %}
215
+ padding-top: 20px;
216
+ {% endif %}
217
+ }
218
+
219
+ h1#top {
220
+ margin: 0px !important;
221
+ padding: 0px !important;
222
+ font-size: 1.5rem !important;
223
+ font-weight: bold;
224
+ }
225
+
226
+ h2{
227
+ font-size: 1.2rem;
228
+ font-weight: normal;
229
+ }
230
+
231
+ h3{
232
+ font-size: 1.1rem;
233
+ font-weight: normal;
234
+ }
235
+
236
+ ul {
237
+ }
238
+
239
+ table {
240
+ font-size: 0.8rem;
241
+ margin: auto;
242
+ padding: 20px;
243
+ text-align: center;
244
+ }
245
+
246
+ summary{
247
+ background: grey;
248
+ color: white;
249
+ text-emphasis: 2em;
250
+ padding: 3px 20px;
251
+ margin-bottom: 5px;
252
+ border-radius: 20px;
253
+ }
254
+
255
+ div.img-container {
256
+ text-align: center;
257
+ padding: 20px;
258
+ }
259
+
260
+ div.iframe-container {
261
+ text-align: center;
262
+ }
263
+
264
+ div#normal-header {
265
+ padding-left: 40px;
266
+ }
267
+
268
+ div#report-header {
269
+ display: flex;
270
+ flex-direction: row;
271
+ -ms-flex-align: center;
272
+ -webkit-align-items: center;
273
+ align-items: center;
274
+ -ms-flex-pack: justify;
275
+ -webkit-justify-content: space-between;
276
+ justify-content: space-between;
277
+ color: #FFFFFF;
278
+ height: 120px;
279
+ padding-left: 40px;
280
+ overflow: hidden;
281
+ border-top: none;
282
+ background: #000000;
283
+ box-shadow: 0px 0px 12px 1px rgba(87, 87, 87, 0.2);
284
+ border-radius: 5px 5px 0px 0px;
285
+ padding-right: 60px;
286
+ }
287
+
288
+ td.table-center{
289
+ text-align: center;
290
+ }
291
+
292
+ th.table-center{
293
+ text-align: center;
294
+ }
295
+
296
+ tr.first{
297
+ background: black;
298
+ color: white;
299
+ border-color: black;
300
+ }
301
+
302
+ @page
303
+ {
304
+ size: auto; /* auto is the initial value */
305
+
306
+ /* this affects the margin in the printer settings */
307
+ margin: 0mm;
308
+ }
309
+
310
+ @media print {
311
+ body{
312
+ padding: 0mm !important;
313
+ margin: 0mm !important;
314
+ }
315
+
316
+ div#report-header{
317
+ background: #000000 !important;
318
+ font-size: 90% !important;
319
+ page-break-inside: avoid;
320
+ }
321
+
322
+ div.device {
323
+ display: block;
324
+ page-break-inside: avoid;
325
+ }
326
+
327
+ table {
328
+ page-break-inside: avoid;
329
+ }
330
+ }
331
+
332
+ table.dataframe {
333
+ border: 0px none;
334
+ border-collapse: collapse;
335
+ border-spacing: 0;
336
+ text-align: right;
337
+ }
338
+ table.dataframe tr {
339
+ border: 0px none;
340
+ }
341
+
342
+ table.dataframe td, table.dataframe th {
343
+ border: 0px none;
344
+ padding-left: 0.5em;
345
+ padding-right: 0.5em;
346
+ font-weight: lighter;
347
+ white-space: normal;
348
+ max-width: none;
349
+ }
350
+
351
+ table.dataframe th:not(:empty) {
352
+ text-align: left;
353
+ font-weight: bold;
354
+ }
355
+
356
+ table.dataframe tr:nth-child(even) td{
357
+ background-color: #f5f5f5;
358
+ }
359
+
360
+ </style>
@@ -3,8 +3,7 @@
3
3
  from os.path import join, dirname, exists
4
4
  from os import makedirs
5
5
  from scdata.tools.custom_logger import logger
6
- import flask
7
- from re import sub
6
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
8
7
 
9
8
  def to_csv(self, path = None, forced_overwrite = False):
10
9
  """
@@ -68,10 +67,11 @@ def to_html(self, title = 'Your title here', template = 'sc_template.html', path
68
67
  Returns
69
68
  ----------
70
69
  rendered:
71
- flask rendered template
70
+ rendered template
72
71
  '''
73
- # TODO - Update or remove
74
- raise NotImplementedError
72
+ # TODO - Update or remove: the templates use the old test descriptor format
73
+ raise NotImplementedError('to_html is disabled: its templates use the old test descriptor, '
74
+ 'which Test no longer has (descriptor, content)')
75
75
 
76
76
  # Find the path to the html templates directory
77
77
  template_folder = join(dirname(__file__), 'templates')
@@ -84,20 +84,16 @@ def to_html(self, title = 'Your title here', template = 'sc_template.html', path
84
84
 
85
85
  filename = join(path, f'{self.name}.html')
86
86
 
87
- docname = sub('.','_', self.name)
88
- app = flask.Flask(docname, template_folder = template_folder)
89
-
90
- with app.app_context():
91
- rendered = flask.render_template(
92
- template,
93
- title = title,
94
- descriptor = self.descriptor,
95
- content = self.content,
96
- details = details,
97
- devices_summary = devices_summary,
98
- full = full,
99
- header = header
100
- )
87
+ environment = Environment(loader=FileSystemLoader(template_folder), autoescape=select_autoescape())
88
+ rendered = environment.get_template(template).render(
89
+ title = title,
90
+ descriptor = self.descriptor,
91
+ content = self.content,
92
+ details = details,
93
+ devices_summary = devices_summary,
94
+ full = full,
95
+ header = header
96
+ )
101
97
 
102
98
  with open(filename, 'w') as handle:
103
99
  handle.write(rendered)
@@ -1,4 +1,3 @@
1
- from termcolor import colored
2
1
  from scdata._config import config
3
2
  from datetime import datetime
4
3
  import sys
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scdata
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: Analysis of sensors and time series data
5
5
  Home-page: https://github.com/fablabbcn/smartcitizen-data
6
6
  Author: oscgonfer
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3
18
18
  Requires-Python: >=3.9
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
- Requires-Dist: geopy~=1.21.0
21
+ Requires-Dist: geopy<3,>=2.4
22
22
  Requires-Dist: Jinja2~=3.1.2
23
23
  Requires-Dist: matplotlib
24
24
  Requires-Dist: pandas~=2.2.2
@@ -28,11 +28,8 @@ Requires-Dist: requests
28
28
  Requires-Dist: scipy
29
29
  Requires-Dist: scikit-learn
30
30
  Requires-Dist: seaborn
31
- Requires-Dist: smartcitizen-connector>=1.5.0
32
- Requires-Dist: termcolor==1.1.0
31
+ Requires-Dist: smartcitizen-connector>=1.5.1
33
32
  Requires-Dist: timezonefinder~=6.1.9
34
- Requires-Dist: urllib3
35
- Requires-Dist: Flask~=2.2.2
36
33
  Provides-Extra: plotting
37
34
  Requires-Dist: bokeh; extra == "plotting"
38
35
  Requires-Dist: panel; extra == "plotting"
@@ -55,6 +55,7 @@ scdata/plot/ts_panel.py
55
55
  scdata/plot/ts_plot.py
56
56
  scdata/plot/ts_scatter.py
57
57
  scdata/plot/ts_uplot.py
58
+ scdata/plot/templates/map_legend.html
58
59
  scdata/test/__init__.py
59
60
  scdata/test/test.py
60
61
  scdata/test/checks/__init__.py
@@ -63,6 +64,7 @@ scdata/test/dispersion/__init__.py
63
64
  scdata/test/dispersion/dispersion.py
64
65
  scdata/test/export/__init__.py
65
66
  scdata/test/export/to_file.py
67
+ scdata/test/export/templates/delivery_template.html
66
68
  scdata/test/export/templates/sc_template.html
67
69
  scdata/test/tools/__init__.py
68
70
  scdata/test/tools/combine.py
@@ -1,4 +1,4 @@
1
- geopy~=1.21.0
1
+ geopy<3,>=2.4
2
2
  Jinja2~=3.1.2
3
3
  matplotlib
4
4
  pandas~=2.2.2
@@ -8,11 +8,8 @@ requests
8
8
  scipy
9
9
  scikit-learn
10
10
  seaborn
11
- smartcitizen-connector>=1.5.0
12
- termcolor==1.1.0
11
+ smartcitizen-connector>=1.5.1
13
12
  timezonefinder~=6.1.9
14
- urllib3
15
- Flask~=2.2.2
16
13
 
17
14
  [dev]
18
15
  pytest
@@ -19,7 +19,7 @@ REQUIREMENTS = [i.strip() for i in open("requirements.txt").readlines()]
19
19
 
20
20
  setup(
21
21
  name='scdata',
22
- version='1.5.0',
22
+ version='1.5.2',
23
23
  description='Analysis of sensors and time series data',
24
24
  author='oscgonfer',
25
25
  license='GNU-GPL3.0',
@@ -1,18 +0,0 @@
1
- # TODO To be updated?
2
- geopy~=1.21.0
3
- # TODO To be updated?
4
- Jinja2~=3.1.2
5
- matplotlib
6
- pandas~=2.2.2
7
- pydantic~=2.8
8
- # TODO To be updated?
9
- PyYAML~=6.0.1
10
- requests
11
- scipy
12
- scikit-learn
13
- seaborn
14
- smartcitizen-connector>=1.5.0
15
- termcolor==1.1.0
16
- timezonefinder~=6.1.9
17
- urllib3
18
- Flask~=2.2.2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes