uk_bin_collection 0.146.1__py3-none-any.whl → 0.147.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.
@@ -0,0 +1,58 @@
1
+ import json
2
+ import geopandas as gpd
3
+
4
+ def extract_lad_codes(input_json_path):
5
+ with open(input_json_path, "r") as f:
6
+ data = json.load(f)
7
+
8
+ lad_codes = set()
9
+ lad_code_to_council_input = {}
10
+
11
+ for council_key, council_info in data.items():
12
+ if isinstance(council_info, dict):
13
+ if "LAD24CD" in council_info:
14
+ code = council_info["LAD24CD"]
15
+ lad_codes.add(code)
16
+ lad_code_to_council_input[code] = council_key
17
+ if "supported_councils_LAD24CD" in council_info:
18
+ for code in council_info["supported_councils_LAD24CD"]:
19
+ lad_codes.add(code)
20
+ lad_code_to_council_input[code] = f"{council_key} (shared)"
21
+
22
+ return lad_codes, lad_code_to_council_input
23
+
24
+ def compare_with_geojson(input_lad_codes, geojson_path):
25
+ gdf = gpd.read_file(geojson_path)
26
+ geojson_lad_codes = set(gdf["LAD24CD"].dropna().unique())
27
+
28
+ geojson_lad_map = {
29
+ row["LAD24CD"]: row["LAD24NM"]
30
+ for _, row in gdf.iterrows()
31
+ if "LAD24CD" in row and "LAD24NM" in row
32
+ }
33
+
34
+ missing_in_input = geojson_lad_codes - input_lad_codes
35
+ extra_in_input = input_lad_codes - geojson_lad_codes
36
+ matching = input_lad_codes & geojson_lad_codes
37
+
38
+ return matching, missing_in_input, extra_in_input, geojson_lad_map
39
+
40
+ # --- Run the comparison ---
41
+ input_json_path = "uk_bin_collection/tests/input.json"
42
+ geojson_path = "uk_bin_collection/Local_Authority_Boundaries.geojson"
43
+
44
+ input_lad_codes, input_name_map = extract_lad_codes(input_json_path)
45
+ matching, missing, extra, geojson_name_map = compare_with_geojson(input_lad_codes, geojson_path)
46
+
47
+ # --- Print results ---
48
+ print(f"✅ Matching LAD24CDs ({len(matching)}):")
49
+ for code in sorted(matching):
50
+ print(f" {code} → input.json: {input_name_map.get(code)} | geojson: {geojson_name_map.get(code)}")
51
+
52
+ print(f"\n🟡 LADs in GeoJSON but missing in input.json ({len(missing)}):")
53
+ for code in sorted(missing):
54
+ print(f" {code} → geojson: {geojson_name_map.get(code)}")
55
+
56
+ print(f"\n🔴 LADs in input.json but not in GeoJSON ({len(extra)}):")
57
+ for code in sorted(extra):
58
+ print(f" {code} → input.json: {input_name_map.get(code)}")
@@ -24,94 +24,90 @@
24
24
  attribution: '© OpenStreetMap contributors'
25
25
  }).addTo(map);
26
26
 
27
- // Helper to slugify wiki anchor
28
27
  function slugify(str) {
29
28
  return str.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
30
29
  }
31
30
 
32
- fetch('tests/input.json')
33
- .then(res => res.json())
34
- .then(integrationData => {
35
- console.log("🔍 Loaded integration data:", integrationData);
36
-
37
- const coveredLADs = new Set();
38
- const integrationByLAD24CD = {};
39
-
40
- for (const [moduleName, entry] of Object.entries(integrationData)) {
41
- const wikiName = entry.wiki_name || moduleName;
42
- const wikiUrl = `https://github.com/robbrad/UKBinCollectionData/wiki/Councils#${slugify(wikiName)}`;
43
-
44
- // Case 1: Direct LAD24CD
45
- if (entry.LAD24CD) {
46
- const code = entry.LAD24CD?.replace(/[^\x20-\x7E]/g, '').trim();
47
- if (code) {
48
- coveredLADs.add(code);
49
- integrationByLAD24CD[code] = { wiki_name: wikiName, url: wikiUrl };
50
- } else {
51
- console.warn("⚠️ Entry with bad LAD24CD:", moduleName, entry);
52
- }
31
+ Promise.all([
32
+ fetch('tests/input.json').then(res => res.json()),
33
+ fetch('../build/integration-test-results/test_results.json').then(res => res.json()).catch(() => ({}))
34
+ ])
35
+ .then(([integrationData, testResults]) => {
36
+ console.log("🔍 Loaded integration data:", integrationData);
37
+ console.log("🧪 Loaded test results:", testResults);
38
+
39
+ const coveredLADs = new Set();
40
+ const integrationByLAD24CD = {};
41
+ const testStatusByLAD24CD = {};
42
+
43
+ for (const [moduleName, entry] of Object.entries(integrationData)) {
44
+ const wikiName = entry.wiki_name || moduleName;
45
+ const wikiUrl = `https://github.com/robbrad/UKBinCollectionData/wiki/Councils#${slugify(wikiName)}`;
46
+ const normalizeKey = str => str.toLowerCase().replace(/[^a-z0-9]/g, '');
47
+
48
+ const testResult = testResults[normalizeKey(moduleName)] || null;
49
+
50
+ const addCode = (code) => {
51
+ const cleanCode = code?.replace(/[^\x20-\x7E]/g, '').trim();
52
+ if (!cleanCode) return;
53
+
54
+ coveredLADs.add(cleanCode);
55
+ if (!integrationByLAD24CD[cleanCode]) {
56
+ integrationByLAD24CD[cleanCode] = { wiki_name: wikiName, url: wikiUrl };
57
+ testStatusByLAD24CD[cleanCode] = testResult;
53
58
  }
59
+ };
54
60
 
55
- // Case 2: Shared modules with multiple LADs
56
- if (Array.isArray(entry.supported_councils_LAD24CD)) {
57
- for (const codeRaw of entry.supported_councils_LAD24CD) {
58
- const code = codeRaw?.replace(/[^\x20-\x7E]/g, '').trim();
59
- if (code) {
60
- coveredLADs.add(code);
61
- // Only overwrite if not already present (prefer specific match)
62
- if (!integrationByLAD24CD[code]) {
63
- integrationByLAD24CD[code] = { wiki_name: wikiName, url: wikiUrl };
64
- }
65
- }
66
- }
67
- }
61
+ if (entry.LAD24CD) addCode(entry.LAD24CD);
62
+ if (Array.isArray(entry.supported_councils_LAD24CD)) {
63
+ for (const code of entry.supported_councils_LAD24CD) addCode(code);
68
64
  }
65
+ }
66
+
67
+ return fetch('Local_Authority_Boundaries.geojson')
68
+ .then(res => res.json())
69
+ .then(geojson => {
70
+ L.geoJSON(geojson, {
71
+ style: feature => {
72
+ const code = feature.properties.LAD24CD?.trim();
73
+ const isCovered = coveredLADs.has(code);
74
+ const testResult = testStatusByLAD24CD[code];
75
+
76
+ let fillColor = 'red'; // default: not covered
77
+ if (isCovered) {
78
+ fillColor = (testResult === 'pass') ? 'green' :
79
+ (testResult === 'fail') ? 'orange' : 'green';
80
+ }
69
81
 
70
- console.log("📦 Final covered LADs:", [...coveredLADs]);
71
-
72
- return fetch('Local_Authority_Boundaries.geojson')
73
- .then(res => res.json())
74
- .then(geojson => {
75
- L.geoJSON(geojson, {
76
- style: feature => {
77
- const code = feature.properties.LAD24CD?.trim();
78
- const isCovered = coveredLADs.has(code);
79
- return {
80
- color: '#333',
81
- weight: 1,
82
- fillColor: isCovered ? 'green' : 'red',
83
- fillOpacity: 0.6
84
- };
85
- },
86
- onEachFeature: (feature, layer) => {
87
- const code = feature.properties.LAD24CD?.trim();
88
- const name = feature.properties.LAD24NM;
89
- const covered = coveredLADs.has(code);
90
-
91
- if (!code) console.warn("⚠️ Missing LAD24CD in GeoJSON feature:", feature);
92
- if (!covered && code) console.warn("❌ Not covered LAD:", code, name);
93
-
94
- if (covered && integrationByLAD24CD[code]) {
95
- const wiki = integrationByLAD24CD[code];
96
- layer.bindPopup(
97
- `<strong>${name}</strong><br>Status: ✅ Covered<br>` +
98
- `<a href="${wiki.url}" target="_blank">📘 ${wiki.wiki_name}</a>`
99
- );
100
- } else {
101
- layer.bindPopup(`<strong>${name}</strong><br>Status: ❌ Not Covered`);
102
- }
82
+ return {
83
+ color: '#333',
84
+ weight: 1,
85
+ fillColor,
86
+ fillOpacity: 0.6
87
+ };
88
+ },
89
+ onEachFeature: (feature, layer) => {
90
+ const code = feature.properties.LAD24CD?.trim();
91
+ const name = feature.properties.LAD24NM;
92
+ const isCovered = coveredLADs.has(code);
93
+ const testResult = testStatusByLAD24CD[code];
94
+ const wiki = integrationByLAD24CD[code];
95
+
96
+ if (!code) return;
97
+
98
+ if (isCovered && wiki) {
99
+ const status = (testResult === 'pass') ? '✅ Covered (Test Passed)' :
100
+ (testResult === 'fail') ? '🟠 Covered (Test Failed)' :
101
+ '✅ Covered (No test result)';
102
+ layer.bindPopup(`<strong>${name}</strong><br>Status: ${status}<br><a href="${wiki.url}" target="_blank">📘 ${wiki.wiki_name}</a>`);
103
+ } else {
104
+ layer.bindPopup(`<strong>${name}</strong><br>Status: ❌ Not Covered`);
103
105
  }
104
- }).addTo(map);
105
-
106
- // Debug missing LADs
107
- const missing = geojson.features
108
- .map(f => [f.properties.LAD24CD?.trim(), f.properties.LAD24NM])
109
- .filter(([code]) => !coveredLADs.has(code))
110
- .map(([code, name]) => `${code} - ${name}`);
111
- console.warn("🛠️ Missing LADs in input.json:", missing);
112
- });
113
- })
114
- .catch(err => console.error('❌ Error loading data:', err));
106
+ }
107
+ }).addTo(map);
108
+ });
109
+ })
110
+ .catch(err => console.error('❌ Error loading data:', err));
115
111
  </script>
116
112
  </body>
117
113
 
@@ -0,0 +1,46 @@
1
+ import sys
2
+ import json
3
+ import xml.etree.ElementTree as ET
4
+ from collections import defaultdict
5
+ import re
6
+
7
+ def extract_council_name(testname):
8
+ """
9
+ Extracts the council name from the test name.
10
+ E.g. "test_scenario_outline[BarnetCouncil]" => "barnetcouncil"
11
+ """
12
+ match = re.search(r'\[(.*?)\]', testname)
13
+ if match:
14
+ return match.group(1).strip().lower()
15
+ return None
16
+
17
+ def parse_junit_xml(path):
18
+ tree = ET.parse(path)
19
+ root = tree.getroot()
20
+
21
+ results = defaultdict(lambda: "pass")
22
+
23
+ for testcase in root.iter("testcase"):
24
+ testname = testcase.attrib.get("name", "")
25
+ council = extract_council_name(testname)
26
+ if not council:
27
+ continue
28
+
29
+ if testcase.find("failure") is not None or testcase.find("error") is not None:
30
+ results[council] = "fail"
31
+
32
+ return results
33
+
34
+ def main():
35
+ if len(sys.argv) != 2:
36
+ print("Usage: python generate_test_results.py <junit.xml path>")
37
+ sys.exit(1)
38
+
39
+ junit_path = sys.argv[1]
40
+ results = parse_junit_xml(junit_path)
41
+
42
+ print(json.dumps(results, indent=2))
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -1013,7 +1013,8 @@
1013
1013
  "N09000010",
1014
1014
  "S12000005",
1015
1015
  "S12000045",
1016
- "W06000020"
1016
+ "W06000020",
1017
+ "E07000122"
1017
1018
  ]
1018
1019
  },
1019
1020
  "GraveshamBoroughCouncil": {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.146.1
3
+ Version: 0.147.0
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com
@@ -9,6 +9,7 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.12
10
10
  Classifier: Programming Language :: Python :: 3.13
11
11
  Requires-Dist: bs4
12
+ Requires-Dist: geopandas (>=1.0.1,<2.0.0)
12
13
  Requires-Dist: holidays
13
14
  Requires-Dist: icalevents (>=0.2.1,<0.3.0)
14
15
  Requires-Dist: lxml
@@ -361,6 +362,42 @@ All integration tests results are in [CodeCov](https://app.codecov.io/gh/robbrad
361
362
  ### Nightly Full Integration Test Reports:
362
363
  - [Nightly Council Test](https://app.codecov.io/gh/robbrad/UKBinCollectionData/tests/master)
363
364
 
365
+
366
+ 🗺️ View Test Coverage Map (in VS Code)
367
+ ---------------------------------------
368
+
369
+ You can generate integration test results and view the interactive UK council coverage map with traffic-light-style statuses for each council.
370
+
371
+ ### 🧪 Step 1: Run Integration Tests
372
+
373
+ Run: `make integration-tests`
374
+
375
+ This runs the full BDD test suite and outputs a `junit.xml` report to:
376
+
377
+ `build/test/integration-test-results/junit.xml`
378
+
379
+ ### 📊 Step 2: Generate Map Test Results JSON
380
+
381
+ Convert the JUnit XML output to a flat test result JSON: `make generate-test-map-test-results`
382
+
383
+ This creates: `build/integration-test-results/test_results.json`
384
+
385
+ This file is used by the map to color each council:
386
+
387
+ * ✅ Green: Test passed
388
+ * 🟠 Amber: Test failed
389
+ * ❌ Red: Not integrated
390
+
391
+ ### 🗺️ Step 3: Open the Map
392
+
393
+ Open the map viewer in VS Code:
394
+
395
+ 1. Right-click the `map.html` file in VSCode and choose **Show Preview**
396
+
397
+ 2. The map will open in your browser, showing real-time integration coverage and test results.
398
+
399
+ ![Test Results Map](test_results_map.png)
400
+
364
401
  ---
365
402
  ## Docker API Server
366
403
  We have created an API for this located under [uk_bin_collection_api_server](https://github.com/robbrad/UKBinCollectionData/uk_bin_collection_api_server)
@@ -1,11 +1,13 @@
1
1
  uk_bin_collection/Local_Authority_Boundaries.geojson,sha256=_j-hUiL0--t2ewd_s29-j7_AKRlhagRMmOhXyco-B6I,1175922
2
2
  uk_bin_collection/README.rst,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- uk_bin_collection/map.html,sha256=qKc4Lscv1eSIc0M3vDofzZf5w9_eslEYpz1-kK4tup0,4346
3
+ uk_bin_collection/compare_lad_codes.py,sha256=BjXPzxYbbqyl2Pv9yPip0BVpyD3GOofwgWa-BQUecqM,2214
4
+ uk_bin_collection/map.html,sha256=1xqlWRc2g4poZwT9FVdsSAkXPmkZ3dmQeA_-ikbU9dg,4135
4
5
  uk_bin_collection/tests/check_selenium_url_in_input.json.py,sha256=lf-JT7vvaSfvgbrfOhzrhfSzJqL82WajlRqo1GqfcMM,7875
5
6
  uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
6
7
  uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
7
8
  uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
8
- uk_bin_collection/tests/input.json,sha256=hJ_W7-yiUwGRA2V9WVQVXCV3UfaBFfbdaIbxm3c7hIM,133302
9
+ uk_bin_collection/tests/generate_map_test_results.py,sha256=8KjfNWNd44eGhiQMDJejoVzTh9dckGHikZQfwJgNO3w,1141
10
+ uk_bin_collection/tests/input.json,sha256=-EVTLbgBwk879YMbAHkIYC7svXffzlL8xOa8Tab8M7g,133327
9
11
  uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
10
12
  uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
11
13
  uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=VZ0a81sioJULD7syAYHjvK_-nT_Rd36tUyzPetSA0gk,3475
@@ -332,8 +334,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
332
334
  uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=QD4v4xpsEE0QheR_fGaNOIRMc2FatcUfKkkhAhseyVU,1159
333
335
  uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
334
336
  uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
335
- uk_bin_collection-0.146.1.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
336
- uk_bin_collection-0.146.1.dist-info/METADATA,sha256=Q2CxbBh-CbGhd-HLj-lH0U3mDLxZWv3JgBXKMvaWNh0,19858
337
- uk_bin_collection-0.146.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
338
- uk_bin_collection-0.146.1.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
339
- uk_bin_collection-0.146.1.dist-info/RECORD,,
337
+ uk_bin_collection-0.147.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
338
+ uk_bin_collection-0.147.0.dist-info/METADATA,sha256=0ub11DJ2h8YO2rfIqeLVzLAb9rAGJkU33VoMPKdgX-s,20956
339
+ uk_bin_collection-0.147.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
340
+ uk_bin_collection-0.147.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
341
+ uk_bin_collection-0.147.0.dist-info/RECORD,,