cjber commited on
Commit
eeee3d5
·
1 Parent(s): 6f58df5

feat: extract document text into .txt files

Browse files

Former-commit-id: 11596fc1b9ab675c9ca7c5efcdd3bf0b5c074fea [formerly 657c0a1ac324d253ea5cc9ae6b9a9cbdfaecbb0e]
Former-commit-id: 4ec539b4a9493588a816986acf3b7f37e63e24ba

planning_ai/documents/document.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import re
3
+ from collections import Counter
4
+
5
+
6
+ import geopandas as gpd
7
+ import matplotlib as mpl
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+ import pandas as pd
11
+ import polars as pl
12
+ from matplotlib.patches import Patch
13
+ from polars.dependencies import subprocess
14
+
15
+ from planning_ai.common.utils import Paths
16
+
17
+ mpl.rcParams["text.usetex"] = True
18
+ mpl.rcParams["text.latex.preamble"] = r"\usepackage{libertine}"
19
+
20
+ WARDS = [
21
+ "E05013050",
22
+ "E05013051",
23
+ "E05013052",
24
+ "E05013053",
25
+ "E05013054",
26
+ "E05013055",
27
+ "E05013056",
28
+ "E05013057",
29
+ "E05013058",
30
+ "E05013059",
31
+ "E05013060",
32
+ "E05013061",
33
+ "E05013062",
34
+ "E05013063",
35
+ ]
36
+
37
+
38
+ def _process_postcodes(final):
39
+ documents = final["documents"]
40
+ postcodes = [doc["document"].metadata["respondentpostcode"] for doc in documents]
41
+ postcodes = (
42
+ pl.DataFrame({"postcode": postcodes})["postcode"]
43
+ .value_counts()
44
+ .with_columns(pl.col("postcode").str.replace_all(" ", ""))
45
+ )
46
+ onspd = pl.read_parquet(
47
+ Paths.RAW / "onspd_cambridge.parquet",
48
+ columns=["PCD", "OSWARD", "LSOA11", "OA21"],
49
+ ).with_columns(pl.col("PCD").str.replace_all(" ", "").alias("postcode"))
50
+ postcodes = postcodes.join(onspd, on="postcode")
51
+ return postcodes
52
+
53
+
54
+ def _process_policies(final):
55
+ def process_policy_group(policy_group, theme, stance):
56
+ details = "".join(
57
+ f'\n### {row["policies"]}\n\n'
58
+ + "".join(
59
+ f"- {detail} {doc_id}\n"
60
+ for detail, doc_id in zip(row["detail"], row["doc_id"])
61
+ )
62
+ for row in policy_group.rows(named=True)
63
+ )
64
+ return f"## {theme} - {stance}\n\n{details}\n"
65
+
66
+ policies_df = final["policies"]
67
+
68
+ support_policies = ""
69
+ object_policies = ""
70
+ other_policies = ""
71
+
72
+ for (theme, stance), policy in policies_df.group_by(
73
+ ["themes", "stance"], maintain_order=True
74
+ ):
75
+ if stance == "Support":
76
+ support_policies += process_policy_group(policy, theme, stance)
77
+ elif stance == "Object":
78
+ object_policies += process_policy_group(policy, theme, stance)
79
+ else:
80
+ other_policies += process_policy_group(policy, theme, stance)
81
+
82
+ return support_policies, object_policies, other_policies
83
+
84
+
85
+ def _process_stances(final):
86
+ documents = final["documents"]
87
+ stances = [
88
+ doc["document"].metadata["representations_support/object"] for doc in documents
89
+ ]
90
+ value_counts = Counter(stances)
91
+ total_values = sum(value_counts.values())
92
+ percentages = {
93
+ key: {"count": count, "percentage": (count / total_values)}
94
+ for key, count in value_counts.items()
95
+ }
96
+ stances_top = sorted(
97
+ percentages.items(), key=lambda x: x[1]["percentage"], reverse=True
98
+ )
99
+ return " | ".join(
100
+ [
101
+ f"**{item}**: {stance['percentage']:.1%} _({stance['count']})_"
102
+ for item, stance in stances_top
103
+ ]
104
+ )
105
+
106
+
107
+ def _process_themes(final):
108
+ documents = final["documents"]
109
+ themes = Counter(
110
+ [theme["theme"].value for doc in documents for theme in doc["themes"]]
111
+ )
112
+ themes = pl.DataFrame(themes).transpose(include_header=True)
113
+ themes_breakdown = themes.with_columns(
114
+ ((pl.col("column_0") / pl.sum("column_0")) * 100).round(2).alias("percentage")
115
+ ).sort("percentage", descending=True)
116
+ themes_breakdown = themes_breakdown.rename(
117
+ {"column": "Theme", "column_0": "Count", "percentage": "Percentage"}
118
+ )
119
+ pd.set_option("display.precision", 1)
120
+ return themes_breakdown.to_pandas().to_markdown(index=False)
121
+
122
+
123
+ def fig_oa(postcodes):
124
+ oa_lookup = pl.read_csv(
125
+ Paths.RAW
126
+ / "Output_Area_to_Local_Authority_District_(April_2023)_Lookup_in_England_and_Wales.csv"
127
+ )
128
+ camb_oa = oa_lookup.filter(
129
+ pl.col("LAD23NM").is_in(["Cambridge", "South Cambridgeshire"])
130
+ )
131
+ oa_pop = pl.read_csv(Paths.RAW / "oa_populations.csv")
132
+ oa_pop = (
133
+ oa_pop.join(camb_oa, left_on="Output Areas Code", right_on="OA21CD")
134
+ .group_by(pl.col("Output Areas Code"))
135
+ .sum()
136
+ .rename({"Output Areas Code": "OA2021", "Observation": "population"})
137
+ .select(["OA2021", "population"])
138
+ )
139
+
140
+ oac = pl.read_csv(Paths.RAW / "oac21ew.csv")
141
+ oac_names = pl.read_csv(Paths.RAW / "classification_codes_and_names.csv")
142
+ oac = (
143
+ oac.with_columns(pl.col("supergroup").cast(str))
144
+ .join(oac_names, left_on="supergroup", right_on="Classification Code")
145
+ .select(["oa21cd", "Classification Name", "supergroup"])
146
+ .rename(
147
+ {
148
+ "Classification Name": "supergroup_name",
149
+ }
150
+ )
151
+ )
152
+ oac = oac.join(oa_pop, left_on="oa21cd", right_on="OA2021")
153
+ oac = (
154
+ postcodes.join(oac, left_on="OA21", right_on="oa21cd", how="right")
155
+ .group_by(["supergroup", "supergroup_name"])
156
+ .sum()
157
+ .select(["supergroup", "supergroup_name", "population", "count"])
158
+ .sort("supergroup")
159
+ .with_columns(
160
+ ((pl.col("count") / pl.col("count").sum()) * 100).alias("perc_count"),
161
+ ((pl.col("population") / pl.col("population").sum()) * 100).alias(
162
+ "perc_pop"
163
+ ),
164
+ )
165
+ .with_columns((pl.col("perc_count") - pl.col("perc_pop")).alias("perc_diff"))
166
+ )
167
+ oa_pd = oac.to_pandas()
168
+
169
+ _, ax1 = plt.subplots(figsize=(8, 8))
170
+
171
+ # Define a list of colors for each supergroup
172
+ colors = [
173
+ "#7f7f7f", # retired
174
+ "#2ca02c", # suburbanites
175
+ "#d62728", # multicultural
176
+ "#e377c2", # low skilled
177
+ "#ff7f0e", # ethnically diverse
178
+ "#bcbd22", # baseline uk
179
+ "#1f77b4", # semi unskilled
180
+ "#9467bd", # legacy
181
+ ]
182
+
183
+ # Plot bars for percentage of representations
184
+ bars1 = ax1.bar(
185
+ oa_pd["supergroup"],
186
+ oa_pd["perc_diff"],
187
+ label="Percentage of Representations (\%)",
188
+ color=colors[: len(oa_pd)],
189
+ edgecolor="black",
190
+ )
191
+
192
+ # Add centerline at y=0
193
+ ax1.axhline(0, color="black", linewidth=1.5)
194
+
195
+ # Annotate bars with percentage values
196
+ for bar in bars1:
197
+ height = bar.get_height()
198
+ if height > 0:
199
+ ax1.annotate(
200
+ f"{height:.0f}\%",
201
+ xy=(bar.get_x() + bar.get_width() / 2, height),
202
+ xytext=(0, 3), # 3 points vertical offset
203
+ textcoords="offset points",
204
+ ha="center",
205
+ va="bottom",
206
+ )
207
+ else:
208
+ ax1.annotate(
209
+ f"{height:.0f}\%",
210
+ xy=(bar.get_x() + bar.get_width() / 2, height),
211
+ xytext=(0, -6), # 10 points vertical offset
212
+ textcoords="offset points",
213
+ ha="center",
214
+ va="top",
215
+ )
216
+
217
+ ax1.set_xlabel("Output Area Classification (OAC) Supergroup")
218
+ ax1.set_ylabel("Difference from national average (\%)")
219
+
220
+ supergroup_names = [
221
+ f"{i}: {name}"
222
+ for i, name in enumerate(oa_pd["supergroup_name"].unique(), start=1)
223
+ ]
224
+ legend_patches = [
225
+ Patch(color=colors[i], label=supergroup_names[i])
226
+ for i in range(len(supergroup_names))
227
+ ]
228
+ ax1.legend(handles=legend_patches, title="Supergroup", frameon=False)
229
+
230
+ plt.tight_layout()
231
+
232
+ plt.savefig(Paths.SUMMARY / "figs" / "oas.pdf")
233
+
234
+
235
+ def fig_wards(postcodes):
236
+ ward_boundaries = gpd.read_file(
237
+ Paths.RAW / "Wards_December_2021_GB_BFE_2022_7523259277605796091.zip"
238
+ )
239
+
240
+ camb_ward_boundaries = ward_boundaries[ward_boundaries["WD21CD"].isin(WARDS)]
241
+ ward_boundaries_prop = ward_boundaries.merge(
242
+ postcodes.to_pandas(), left_on="WD21CD", right_on="OSWARD"
243
+ )
244
+
245
+ _, ax = plt.subplots(figsize=(8, 8))
246
+ ward_boundaries_prop.plot(
247
+ ax=ax,
248
+ column="count",
249
+ legend=True,
250
+ vmax=20,
251
+ legend_kwds={"label": "Number of Representations"},
252
+ )
253
+ ward_boundaries.plot(ax=ax, color="none", edgecolor="gray")
254
+ camb_ward_boundaries.plot(ax=ax, color="none", edgecolor="black")
255
+
256
+ bounds = np.array([541419.8982, 253158.2036, 549420.4025, 262079.7998])
257
+ buffer = 20_000
258
+ ax.set_xlim([bounds[0] - buffer, bounds[2] + buffer])
259
+ ax.set_ylim([bounds[1] - buffer, bounds[3] + buffer])
260
+
261
+ plt.axis("off")
262
+ plt.tight_layout()
263
+
264
+ plt.savefig(Paths.SUMMARY / "figs" / "wards.pdf")
265
+
266
+
267
+ def fig_imd(postcodes):
268
+ imd = pl.read_csv(
269
+ Paths.RAW / "uk_imd2019.csv", columns=["LSOA", "SOA_decile"]
270
+ ).with_columns(((pl.col("SOA_decile") - 1) // 2) + 1)
271
+
272
+ oa_lookup = pl.read_csv(Paths.RAW / "lsoa_lad_lookup.csv")[
273
+ ["LSOA11CD", "LAD11NM"]
274
+ ].unique()
275
+ lsoa_camb = oa_lookup.filter(
276
+ pl.col("LAD11NM").is_in(["Cambridge", "South Cambridgeshire"])
277
+ )
278
+
279
+ imd = imd.join(lsoa_camb, left_on="LSOA", right_on="LSOA11CD")
280
+ pops = pl.read_excel(
281
+ Paths.RAW / "sapelsoabroadage20112022.xlsx",
282
+ sheet_name="Mid-2022 LSOA 2021",
283
+ read_options={"header_row": 3},
284
+ columns=["LSOA 2021 Code", "Total"],
285
+ )
286
+ imd = (
287
+ postcodes.join(imd, left_on="LSOA11", right_on="LSOA", how="right")
288
+ .join(pops, left_on="LSOA", right_on="LSOA 2021 Code")
289
+ .group_by("SOA_decile")
290
+ .agg(pl.col("count").sum(), pl.col("LSOA").count(), pl.col("Total").sum())
291
+ .sort("SOA_decile")
292
+ .with_columns(
293
+ ((pl.col("count") / pl.col("count").sum()) * 100).alias("perc_count"),
294
+ ((pl.col("Total") / pl.col("Total").sum()) * 100).alias("perc_pop"),
295
+ )
296
+ .with_columns((pl.col("perc_count") - pl.col("perc_pop")).alias("perc_diff"))
297
+ )
298
+
299
+ postcodes_pd = imd.to_pandas()
300
+ colors = [
301
+ "#d62728",
302
+ "#9f4e64",
303
+ "#6f76a0",
304
+ "#478dbf",
305
+ "#1f77b4",
306
+ ]
307
+
308
+ _, ax1 = plt.subplots(figsize=(8, 8))
309
+
310
+ x = np.arange(len(postcodes_pd))
311
+ ax1.bar(
312
+ x, # Shift to the left
313
+ postcodes_pd["perc_diff"],
314
+ edgecolor="black",
315
+ color=colors,
316
+ )
317
+
318
+ # Set labels and ticks
319
+ ax1.set_xlabel("Deprivation Quintile")
320
+ ax1.set_ylabel("Difference from national average (\%)")
321
+ ax1.set_xticks(x)
322
+ ax1.axhline(0, color="black", linewidth=1.5)
323
+
324
+ # ax1.legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=5, frameon=False)
325
+ plt.tight_layout()
326
+ ax1.set_xticklabels(["1 - Most Deprived", "2", "3", "4", "5 - Least Deprived"])
327
+
328
+ plt.savefig(Paths.SUMMARY / "figs" / "imd_decile.pdf")
329
+
330
+
331
+ def load_txt(file_path):
332
+ with open(file_path, "r", encoding="utf-8") as file:
333
+ return file.read()
334
+
335
+
336
+ def build_final_report(out, rep):
337
+ introduction_paragraph = load_txt("planning_ai/documents/introduction.txt")
338
+ figures_paragraph = load_txt("planning_ai/documents/figures.txt")
339
+ themes_paragraph = load_txt("planning_ai/documents/themes.txt")
340
+ final = out["generate_final_report"]
341
+ unused_documents = out["generate_final_report"]["unused_documents"]
342
+ support_policies, object_policies, other_policies = _process_policies(final)
343
+ postcodes = _process_postcodes(final)
344
+ stances = _process_stances(final)
345
+ themes = _process_themes(final)
346
+
347
+ fig_wards(postcodes)
348
+ fig_oa(postcodes)
349
+ fig_imd(postcodes)
350
+
351
+ quarto_doc = (
352
+ "---\n"
353
+ f"title: 'Summary of Submitted Representations: {rep}'\n"
354
+ "geometry: a4paper\n"
355
+ "margin: 2cm\n"
356
+ "fontfamily: libertinus\n"
357
+ "monofont: 'JetBrains Mono'\n"
358
+ "monofontoptions:\n"
359
+ " - Scale=0.55\n"
360
+ "---\n\n"
361
+ "# Executive Summary\n\n"
362
+ f"{final['executive']}\n\n"
363
+ f"There were a total of {len(final['documents']):,} responses. Of these, submissions indicated "
364
+ "the following support and objection of the plan:\n\n"
365
+ f"{stances}\n\n"
366
+ "# Introduction\n\n"
367
+ f"{introduction_paragraph}\n\n"
368
+ "\n# Profile of Submissions\n\n"
369
+ f"{figures_paragraph}\n\n"
370
+ f"![Total number of representations submitted by Ward\\label{{fig-wards}}](./data/out/summary/figs/wards.pdf)\n\n"
371
+ f"![Total number of representations submitted by Output Area (OA 2021)\\label{{fig-oas}}](./data/out/summary/figs/oas.pdf)\n\n"
372
+ f"![Percentage of representations submitted by quintile of index of multiple deprivation (2019)\\label{{fig-imd}}](./data/out/summary/figs/imd_decile.pdf)\n\n"
373
+ r"\newpage"
374
+ "\n\n# Themes and Policies\n\n"
375
+ f"{themes_paragraph}\n\n"
376
+ f"{themes}{{#tbl-themes}}\n\n"
377
+ "## Supporting Representations\n\n"
378
+ "The following section presents a list of all points raised in representations that support the plan"
379
+ ", grouped by theme and policy.\n\n"
380
+ f"{support_policies or '_No supporting representations._'}\n\n"
381
+ "## Objecting Representations\n\n"
382
+ "The following section presents a list of all points raised in representations that object to "
383
+ "the plan, grouped by theme and policy.\n\n"
384
+ f"{object_policies or '_No objecting representations._'}\n\n"
385
+ "## Comment\n\n"
386
+ "The following section presents a list of all points raised in representations that do not support "
387
+ "or object to the plan, grouped by theme and policy.\n\n"
388
+ f"{other_policies or '_No other representations._'}\n\n"
389
+ "## Unused Documents\n\n"
390
+ "Please note that the following documents were not used to produce this report:\n\n"
391
+ f"{str(unused_documents)}"
392
+ )
393
+
394
+ out_path = Paths.SUMMARY / f"Summary_of_Submitted_Responses-{rep}.md"
395
+ out_file = Paths.SUMMARY / f"Summary_of_Submitted_Responses-{rep}.pdf"
396
+ with open(out_path, "w") as f:
397
+ f.write(quarto_doc)
398
+ command = ["pandoc", f"{out_path}", "-o", f"{out_file}"]
399
+ try:
400
+ subprocess.run(command, check=True, capture_output=True)
401
+ except subprocess.CalledProcessError as e:
402
+ logging.error(f"Error during Summary_of_Submitted_Responses.md render: {e}")
403
+
404
+
405
+ def build_summaries_document(out, rep):
406
+ sub = r"Document ID: \[\d+\]\n\n"
407
+ full_text = "".join(
408
+ f"**Document ID**: {document['doc_id']}\n\n"
409
+ # f"**Original Document**\n\n{document['document'].page_content}\n\n"
410
+ f"**Summarised Document**\n\n{re.sub(sub, '', document['summary'].summary)}\n\n"
411
+ # f"**Identified Entities**\n\n{document['entities']}\n\n"
412
+ for document in out["generate_final_report"]["documents"]
413
+ )
414
+ header = (
415
+ "---\n"
416
+ f"title: 'Summary Documents: {rep}'\n"
417
+ "fontfamily: libertinus\n"
418
+ "geometry: a4paper\n"
419
+ "margin: 2cm\n"
420
+ "monofont: 'JetBrains Mono'\n"
421
+ "monofontoptions:\n"
422
+ " - Scale=0.55\n"
423
+ "---\n\n"
424
+ )
425
+ out_path = Paths.SUMMARY / f"Summary_Documents-{rep}.md"
426
+ out_file = Paths.SUMMARY / f"Summary_Documents-{rep}.pdf"
427
+ with open(out_path, "w") as f:
428
+ f.write(f"{header}{full_text}")
429
+
430
+ command = ["pandoc", f"{out_path}", "-o", f"{out_file}"]
431
+ try:
432
+ subprocess.run(command, check=True, capture_output=True)
433
+ except subprocess.CalledProcessError as e:
434
+ logging.error(f"Error during render: {e}")
planning_ai/documents/figures.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ This section describes the characteristics of where submissions were received from. This can help to identify how representative submissions were and whether there were any communities whose views were not being considered. Figure \ref{fig-wards} shows the number (frequency) of submitted representations by Ward based on the address attached to the submission. To interpret the figure, areas which are coloured white had no submissions from residents, and then areas are coloured in based on the total number of submissions with yellows and greens representing the largest numbers. This figure helps to identify which Wards are more active in terms of participation and representation in this report.
2
+
3
+ Figure \ref{fig-oas} displays the percentage of representations submitted by the Output Area Classification (2021). The Output Area Classification is the Office for National Statistics preferred classification of neighbourhoods. This measure groups neighbourhoods (here defined as Output Areas, typically containing 100 people) into categories that capture similar types of people based on population, demographic and socioeconomic characteristics. It therefore provides an insightful view of the types of communities who submitted representations. To interpret the figure, where bars extend higher/upwards, this represents a larger population share within a specific area type. The blue bars represent the characteristics of who submitted representations, and the orange bars represent the underlying population – allowing one to compare whether the profile of submissions matched the characteristics of the local population. This figure uses OAC 'Supergroups', which are the highest level of the hierarchy, and provide information relative to the average values for the UK population at large.
4
+
5
+ Figure \ref{fig-imd} shows the percentage of responses by level of neighbourhood socioeconomic deprivation. The information is presented using the 2019 Index of Multiple Deprivation, divided into quintiles (i.e., dividing the English population into equal fifths). This measure is the UK Government’s preferred measure of socioeconomic deprivation and is based on information about income, employment, education, health, crime, housing and the local environment for small areas (Lower Super Output Areas, typically containing 1600 people). To interpret the graph, bars represent the share of population from each quintile. Quintile 1 represents the most deprived 20% of areas, and quintile 5 the least deprived 20% of areas. The orange bars represent the distribution of people who submitted representations (i.e., larger bars mean that more people from these areas submitted representations). The blue bars show the distribution of the local population, allowing one to evaluate whether the evidence submitted was from the same communities in the area.
planning_ai/documents/introduction.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ This report was produced using a generative pre-trained transformer (GPT) large-language model (LLM) to produce an abstractive summary of all responses to the related planning application. This model automatically reviews every response in detail, and extracts key information to inform decision making. This document first consolidates this information into a single-page executive summary, highlighting areas of particular interest to consider, and the broad consensus of responses. Figures generated from responses then give both a geographic and statistical overview, highlighting any demographic imbalances in responses. The document then extracts detailed information from responses, grouped by theme and policy. In this section we incorporate citations which relate with the 'Summary Responses' document, to increase transparency.
planning_ai/documents/themes.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ The following section provides a detailed breakdown of notable details from responses, grouped by themes and policies. Both themes and associated policies are automatically determined through an analysis of the summary content by an LLM agent. Each theme is grouped by whether a responses is supporting, opposed, or a general comment. This section aims to give a comprehensive view of the key issues raised by the respondents with respect to the themes and policies outlined. We have incorporated citations into eac hpoint (see numbers in square brackets) which relate to the specific document they were made in, to promote the transparency of where information was sourced from. @tbl-themes gives a breakdown of the number of submissions that relate with each theme, submissions may relate to more than one theme.