scout-browser 4.93.1__py3-none-any.whl → 4.95.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.
Files changed (53) hide show
  1. scout/adapter/mongo/base.py +0 -0
  2. scout/adapter/mongo/hgnc.py +5 -1
  3. scout/adapter/mongo/managed_variant.py +4 -2
  4. scout/adapter/mongo/query.py +91 -54
  5. scout/adapter/mongo/variant.py +13 -8
  6. scout/build/panel.py +1 -1
  7. scout/commands/export/export_command.py +0 -0
  8. scout/commands/load/base.py +0 -0
  9. scout/commands/load/user.py +0 -0
  10. scout/commands/update/disease.py +0 -0
  11. scout/commands/update/genes.py +0 -0
  12. scout/commands/wipe_database.py +0 -0
  13. scout/constants/gene_tags.py +22 -12
  14. scout/demo/643594.research.mei.vcf.gz +0 -0
  15. scout/demo/643594.research.mei.vcf.gz.tbi +0 -0
  16. scout/load/panelapp.py +8 -12
  17. scout/parse/omim.py +5 -6
  18. scout/parse/panelapp.py +16 -42
  19. scout/parse/variant/compound.py +20 -21
  20. scout/parse/variant/gene.py +0 -0
  21. scout/parse/variant/genotype.py +0 -0
  22. scout/resources/custom_igv_tracks/mane.bb +0 -0
  23. scout/server/blueprints/cases/controllers.py +48 -0
  24. scout/server/blueprints/cases/templates/cases/case_report.html +17 -2
  25. scout/server/blueprints/cases/views.py +5 -5
  26. scout/server/blueprints/clinvar/controllers.py +4 -5
  27. scout/server/blueprints/institutes/controllers.py +129 -67
  28. scout/server/blueprints/institutes/forms.py +5 -2
  29. scout/server/blueprints/institutes/templates/overview/cases.html +6 -0
  30. scout/server/blueprints/institutes/templates/overview/utils.html +6 -5
  31. scout/server/blueprints/managed_variants/forms.py +17 -2
  32. scout/server/blueprints/managed_variants/templates/managed_variants/managed_variants.html +2 -2
  33. scout/server/blueprints/variant/templates/variant/components.html +27 -4
  34. scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -2
  35. scout/server/blueprints/variant/templates/variant/tx_overview.html +3 -3
  36. scout/server/blueprints/variant/views.py +1 -2
  37. scout/server/blueprints/variants/forms.py +33 -5
  38. scout/server/blueprints/variants/templates/variants/cancer-sv-variants.html +4 -18
  39. scout/server/blueprints/variants/templates/variants/cancer-variants.html +2 -12
  40. scout/server/blueprints/variants/templates/variants/components.html +15 -1
  41. scout/server/blueprints/variants/templates/variants/sv-variants.html +2 -2
  42. scout/server/links.py +1 -1
  43. scout/utils/acmg.py +0 -1
  44. scout/utils/ccv.py +1 -9
  45. scout/utils/link.py +4 -3
  46. scout/utils/md5.py +0 -0
  47. {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/METADATA +66 -45
  48. {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/RECORD +41 -42
  49. {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/WHEEL +1 -2
  50. scout/__version__.py +0 -1
  51. scout_browser-4.93.1.dist-info/top_level.txt +0 -1
  52. {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/entry_points.txt +0 -0
  53. {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info/licenses}/LICENSE +0 -0
@@ -16,7 +16,7 @@ from wtforms import (
16
16
  SubmitField,
17
17
  validators,
18
18
  )
19
- from wtforms.widgets import TextInput
19
+ from wtforms.widgets import NumberInput, TextInput
20
20
 
21
21
  from scout.constants import (
22
22
  CALLERS,
@@ -132,8 +132,22 @@ class VariantFiltersForm(FlaskForm):
132
132
  )
133
133
 
134
134
  chrom = NonValidatingSelectMultipleField("Chromosome", choices=[], default="")
135
- start = IntegerField("Start position", [validators.Optional()])
136
- end = IntegerField("End position", [validators.Optional()])
135
+ start = IntegerField(
136
+ "Start position",
137
+ [
138
+ validators.Optional(),
139
+ validators.NumberRange(min=0, message="Start position must be 1 or greater."),
140
+ ],
141
+ widget=NumberInput(min=1),
142
+ )
143
+ end = IntegerField(
144
+ "End position",
145
+ [
146
+ validators.Optional(),
147
+ validators.NumberRange(min=0, message="End position must be 1 or greater."),
148
+ ],
149
+ widget=NumberInput(min=1),
150
+ )
137
151
  cytoband_start = NonValidatingSelectField("Cytoband start", choices=[])
138
152
  cytoband_end = NonValidatingSelectField("Cytoband end", choices=[])
139
153
 
@@ -264,8 +278,22 @@ class OutlierFiltersForm(FlaskForm):
264
278
  )
265
279
 
266
280
  chrom = NonValidatingSelectMultipleField("Chromosome", choices=[], default="")
267
- start = IntegerField("Start position", [validators.Optional()])
268
- end = IntegerField("End position", [validators.Optional()])
281
+ start = IntegerField(
282
+ "Start position",
283
+ [
284
+ validators.Optional(),
285
+ validators.NumberRange(min=1, message="Start position must be 0 or greater."),
286
+ ],
287
+ widget=NumberInput(min=1),
288
+ )
289
+ end = IntegerField(
290
+ "End position",
291
+ [
292
+ validators.Optional(),
293
+ validators.NumberRange(min=1, message="End position must be 0 or greater."),
294
+ ],
295
+ widget=NumberInput(min=1),
296
+ )
269
297
  cytoband_start = NonValidatingSelectField("Cytoband start", choices=[])
270
298
  cytoband_end = NonValidatingSelectField("Cytoband end", choices=[])
271
299
 
@@ -1,6 +1,6 @@
1
1
  {% extends "layout.html" %}
2
2
  {% from "variants/utils.html" import cancer_sv_filters,cell_rank, pagination_footer, pagination_hidden_div, filter_form_footer, filter_script_main, update_stash_filter_button_status, dismiss_variants_block, callers_cell %}
3
- {% from "variants/components.html" import external_scripts, external_stylesheets, frequency_cell_general, observed_cell_general, variant_gene_symbols_cell, variant_funct_anno_cell %}
3
+ {% from "variants/components.html" import allele_cell, external_scripts, external_stylesheets, frequency_cell_general, observed_cell_general, variant_gene_symbols_cell, variant_funct_anno_cell %}
4
4
 
5
5
  {% block title %}
6
6
  {{ super() }} - {{ institute.display_name }} - {{ case.display_name }} - SV variants
@@ -111,8 +111,8 @@
111
111
  {{ variant.sub_category|upper }}
112
112
  </td>
113
113
  <td>{{ variant.chromosome if variant.chromosome == variant.end_chrom else variant.chromosome+'-'+variant.end_chrom }}</td>
114
- <td class="col-2"><span style="white-space: nowrap;">{{ variant.position|human_longint|safe }}</span></td>
115
- <td class="col-2"><span style="white-space: nowrap;">{{ 'inf' if variant.end == 100000000000 else variant.end|human_longint|safe }}</span></td>
114
+ <td class="col-2">{% if variant.chromosome != variant.end_chrom %}<span class="text-body"></span><b>{{ variant.chromosome }}</b>:</span>{% endif %}<span class="text-body" style="white-space: nowrap;">{{ variant.position|human_longint|safe }}</span></td>
115
+ <td class="col-2">{% if variant.chromosome != variant.end_chrom %}<span class="text-body"><b>{{ variant.end_chrom }}</b>:</span>{% endif %}<span style="white-space: nowrap;">{{ 'inf' if variant.end == 100000000000 else variant.end|human_longint|safe }}</span></td>
116
116
  <td class="col-2"><span style="white-space: nowrap;">{{ '-' if variant.length == 100000000000 else variant.length|human_longint|safe }}</span></td>
117
117
  <td class="text-end">{{ frequency_cell_general(variant) }}</td>
118
118
  <td>{{observed_cell_general(variant)}}</td>
@@ -122,25 +122,11 @@
122
122
  <td>
123
123
  {{ variant_funct_anno_cell(variant) }}
124
124
  </td>
125
- <td>{{ allele_cell(variant.tumor or {}) }}{% if variant.somatic_score %}<small class="text-muted">[{{ variant.somatic_score }}]</small>{% endif %}</td>
125
+ <td>{{ allele_cell(variant.tumor or {}) }}{% if variant.somatic_score %}<small class="text-body" data-bs-toggle="tooltip" data-bs-placement="top" title="SV caller (Manta) somatic score">[{{ variant.somatic_score }}]</small>{% endif %}</td>
126
126
  <td>{{ allele_cell(variant.normal or {}) }}</td>
127
127
  </tr>
128
128
  {% endmacro %}
129
129
 
130
- {% macro allele_cell(allele) %}
131
- {% if 'alt_freq' in allele %}
132
- {% if allele.alt_freq == -1 %}
133
- <span class="text-muted">.</span>
134
- {% else %}
135
- {{ allele.alt_freq|round(4) }}
136
- {% endif %}
137
- <br>
138
- <small class="text-muted">{% if allele.alt_depth >= 0 %}{{ allele.alt_depth }}{% else %}.{% endif %}|{% if allele.ref_depth >= 0 %}{{ allele.ref_depth }}{% else %}.{% endif %}</small>
139
- {% else %}
140
- <span class="text-muted">N/A</span>
141
- {% endif %}
142
- {% endmacro %}
143
-
144
130
 
145
131
  {% block scripts %}
146
132
  {{ super() }}
@@ -1,6 +1,6 @@
1
1
  {% extends "layout.html" %}
2
2
 
3
- {% from "variants/components.html" import external_scripts, external_stylesheets, gene_cell, frequency_cell_general, observed_cell_general, variant_funct_anno_cell %}
3
+ {% from "variants/components.html" import allele_cell, external_scripts, external_stylesheets, gene_cell, frequency_cell_general, observed_cell_general, variant_funct_anno_cell %}
4
4
  {% from "variants/utils.html" import cancer_filters, cell_rank, pagination_footer, pagination_hidden_div, dismiss_variants_block, filter_form_footer, filter_script_main, update_stash_filter_button_status, callers_cell %}
5
5
  {% from "variants/indicators.html" import pin_indicator, causative_badge, clinical_assessments_badge, comments_badge, dismissals_badge, evaluations_badge, ccv_evaluations_badge, group_assessments_badge, matching_manual_rank, other_tiered_variants, research_assessments_badge %}
6
6
 
@@ -175,17 +175,7 @@
175
175
  {% endmacro %}
176
176
 
177
177
  {% macro position_cell(variant) %}
178
- {{ variant.chromosome }}<span class="text-muted">:{{ variant.position }}</span>
179
- {% endmacro %}
180
-
181
- {% macro allele_cell(allele) %}
182
- {% if 'alt_freq' in allele %}
183
- {{ allele.alt_freq|round(4) }}
184
- <br>
185
- <small class="text-muted">{{ allele.alt_depth }} | {{ allele.ref_depth }}</small>
186
- {% else %}
187
- <span class="text-muted">N/A</span>
188
- {% endif %}
178
+ <span class="text-body"><b>{{ variant.chromosome }}</b>:{{ variant.position }}</span>
189
179
  {% endmacro %}
190
180
 
191
181
  {% block scripts %}
@@ -211,7 +211,7 @@
211
211
  title="Overlapping gene panels"
212
212
  data-bs-content="{% for panel in matching_panels %}
213
213
  {{ panel.panel_name|safe }}<br>
214
- {% endfor %}"
214
+ {% else %} No ovelapping gene panels {% endfor %}"
215
215
  >{{ panel_count }}</a>
216
216
  {% endmacro %}
217
217
 
@@ -403,3 +403,17 @@
403
403
  <script src="https://cdn.datatables.net/1.12.0/js/jquery.dataTables.min.js" integrity="sha512-fu0WiDG5xqtX2iWk7cp17Q9so54SC+5lk/z/glzwlKFdEOwGG6piUseP2Sik9hlvlmyOJ0lKXRSuv1ltdVk9Jg==" referrerpolicy="no-referrer" crossorigin="anonymous"></script>
404
404
  <script src="https://cdn.datatables.net/1.12.0/js/dataTables.bootstrap5.min.js" integrity="sha512-nfoMMJ2SPcUdaoGdaRVA1XZpBVyDGhKQ/DCedW2k93MTRphPVXgaDoYV1M/AJQLCiw/cl2Nbf9pbISGqIEQRmQ==" referrerpolicy="no-referrer" crossorigin="anonymous"></script>
405
405
  {% endmacro %}
406
+
407
+ {% macro allele_cell(allele) %}
408
+ {% if 'alt_freq' in allele %}
409
+ {% if allele.alt_freq == -1 %}
410
+ <span class="text-body">.</span>
411
+ {% else %}
412
+ <span class="text-body"><b>{{ allele.alt_freq|round(4) }}</b></span>
413
+ {% endif %}
414
+ <br>
415
+ <small class="text-body">{% if 'alt_depth' in allele and allele.alt_depth >= 0 %}{{ allele.alt_depth }}{% else %}.{% endif %}|{% if 'ref_depth' in allele and allele.ref_depth >= 0 %}{{ allele.ref_depth }}{% else %}.{% endif %}</small>
416
+ {% else %}
417
+ <span class="text-body">N/A</span>
418
+ {% endif %}
419
+ {% endmacro %}
@@ -108,8 +108,8 @@ onsubmit="return validateForm()">
108
108
  </td>
109
109
  <td>{{ variant.sub_category|upper }}</td>
110
110
  <td>{{ variant.chromosome if variant.chromosome == variant.end_chrom else variant.chromosome+'-'+variant.end_chrom }}</td>
111
- <td><span style="white-space: nowrap;">{{ variant.position|human_longint|safe }}</span></td>
112
- <td><span style="white-space: nowrap;">{{ 'inf' if variant.end == 100000000000 else variant.end|human_longint|safe }}</span></td>
111
+ <td>{% if variant.chromosome != variant.end_chrom %}<span class="text-body"></span><b>{{ variant.chromosome }}</b>:</span>{% endif %}<span style="white-space: nowrap;">{{ variant.position|human_longint|safe }}</span></td>
112
+ <td>{% if variant.chromosome != variant.end_chrom %}<span class="text-body"><b>{{ variant.end_chrom }}</b>:</span>{% endif %}<span style="white-space: nowrap;">{{ 'inf' if variant.end == 100000000000 else variant.end|human_longint|safe }}</span></td>
113
113
  <td class="text-end"><span style="white-space: nowrap;">{{ '-' if variant.length == 100000000000 else variant.length|human_longint|safe}}</span></td>
114
114
  <td>
115
115
  {{ frequency_cell_general(variant) }}
scout/server/links.py CHANGED
@@ -714,7 +714,7 @@ def ucsc_link(variant_obj, build=None):
714
714
  )
715
715
  if build == 38:
716
716
  url_template = (
717
- "http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg20&"
717
+ "http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg38&"
718
718
  "position=chr{this[chromosome]}:{this[position]}"
719
719
  "-{this[position]}&dgv=pack&knownGene=pack&omimGene=pack"
720
720
  )
scout/utils/acmg.py CHANGED
@@ -1,6 +1,5 @@
1
1
  # coding=UTF-8
2
2
 
3
-
4
3
  from typing import Optional
5
4
 
6
5
  from scout.constants import ACMG_COMPLETE_MAP
scout/utils/ccv.py CHANGED
@@ -146,15 +146,7 @@ def get_ccv_temperature(ccv_terms: set) -> Optional[dict]:
146
146
  }
147
147
 
148
148
  if not ccv_terms:
149
- points = 0
150
- point_classification = "uncertain_significance"
151
- return {
152
- "points": points,
153
- "temperature": TEMPERATURE_STRINGS[points].get("label"),
154
- "temperature_class": TEMPERATURE_STRINGS[points].get("color"),
155
- "temperature_icon": TEMPERATURE_STRINGS[points].get("icon"),
156
- "point_classification": CCV_COMPLETE_MAP[point_classification].get("short"),
157
- }
149
+ return {}
158
150
 
159
151
  points = get_ccv_points(ccv_terms)
160
152
 
scout/utils/link.py CHANGED
@@ -36,15 +36,16 @@ def genes_by_alias(hgnc_genes):
36
36
  hgnc_symbol = gene["hgnc_symbol"]
37
37
 
38
38
  for alias in gene["previous_symbols"]:
39
+ alias = alias.upper()
39
40
  true_id = None
40
41
  if alias == hgnc_symbol:
41
42
  true_id = hgnc_id
42
43
  if alias in alias_genes:
43
- alias_genes[alias.upper()]["ids"].add(hgnc_id)
44
+ alias_genes[alias]["ids"].add(hgnc_id)
44
45
  if true_id:
45
- alias_genes[alias.upper()]["true"] = hgnc_id
46
+ alias_genes[alias]["true"] = hgnc_id
46
47
  else:
47
- alias_genes[alias.upper()] = {"true": true_id, "ids": set([hgnc_id])}
48
+ alias_genes[alias] = {"true": true_id, "ids": set([hgnc_id])}
48
49
 
49
50
  return alias_genes
50
51
 
scout/utils/md5.py CHANGED
File without changes
@@ -1,62 +1,76 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: scout-browser
3
- Version: 4.93.1
4
- Summary: Clinical DNA variant visualizer and browser.
5
- Home-page: https://github.com/Clinical-Genomics/scout
6
- Author: Måns Magnusson
7
- Author-email: mans.magnusson@scilifelab.se
3
+ Version: 4.95.0
4
+ Summary: Clinical DNA variant visualizer and browser
5
+ Project-URL: Repository, https://github.com/Clinical-Genomics/scout
6
+ Project-URL: Changelog, https://github.com/Clinical-Genomics/scout/blob/main/CHANGELOG.md
7
+ Project-URL: Documentation, https://clinical-genomics.github.io/scout/
8
+ Project-URL: Bug Tracker, https://github.com/Clinical-Genomics/scout/issues
9
+ Project-URL: Issues, https://github.com/Clinical-Genomics/scout/issues
10
+ Author-email: Chiara Rasi <chiara.rasi@scilifelab.se>, Daniel Nilsson <daniel.nilsson@ki.se>, Robin Andeer <robin.andeer@gmail.com>, Mans Magnuson <monsunas@gmail.com>
11
+ License: MIT License
12
+ License-File: LICENSE
13
+ Keywords: acmg,beacon,cancer,clingen-cgc-vicc,clinvar,cnv,coverage,dna,fusion,genome visualisation,lrs,matchmaker exchange,ogm,outliers,panel,panelapp,phenopacket,rna,sma,snv,str,sv,variant ranking,variant triage,variants,vcf,wes,wgs,wts
14
+ Classifier: Development Status :: 5 - Production/Stable
8
15
  Classifier: Environment :: Web Environment
9
16
  Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: BSD License
11
- Classifier: Operating System :: OS Independent
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: MacOS :: MacOS X
20
+ Classifier: Operating System :: Unix
12
21
  Classifier: Programming Language :: Python
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Programming Language :: Python :: Implementation :: CPython
13
28
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
14
- Classifier: Topic :: Software Development :: Libraries
15
- Classifier: Programming Language :: Python :: 3.6
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: werkzeug
19
- Requires-Dist: Flask>=2.0
20
- Requires-Dist: Flask-Bootstrap
21
- Requires-Dist: Flask-CORS
22
- Requires-Dist: path.py
23
- Requires-Dist: markdown
24
- Requires-Dist: WTForms
25
- Requires-Dist: Flask-WTF
26
- Requires-Dist: Flask-Mail
29
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
30
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
31
+ Requires-Python: >=3.9
32
+ Requires-Dist: anytree
33
+ Requires-Dist: authlib
34
+ Requires-Dist: cairosvg
35
+ Requires-Dist: click
27
36
  Requires-Dist: coloredlogs
28
- Requires-Dist: query_phenomizer
29
- Requires-Dist: Flask-Babel>=3
37
+ Requires-Dist: configobj
38
+ Requires-Dist: cryptography
39
+ Requires-Dist: cyvcf2
40
+ Requires-Dist: defusedxml
41
+ Requires-Dist: flask-babel>=3
42
+ Requires-Dist: flask-bootstrap
43
+ Requires-Dist: flask-cors
44
+ Requires-Dist: flask-ldapconn
45
+ Requires-Dist: flask-login
46
+ Requires-Dist: flask-mail
47
+ Requires-Dist: flask-wtf
48
+ Requires-Dist: flask>=2.0
49
+ Requires-Dist: importlib-resources
50
+ Requires-Dist: intervaltree==3.0.2
30
51
  Requires-Dist: livereload>=2.7
31
- Requires-Dist: tornado>=6.4.1
32
- Requires-Dist: python-dateutil
33
- Requires-Dist: pymongo
52
+ Requires-Dist: markdown
53
+ Requires-Dist: path-py
34
54
  Requires-Dist: pathlib
35
55
  Requires-Dist: pdfkit
56
+ Requires-Dist: ped-parser
36
57
  Requires-Dist: phenopackets
37
- Requires-Dist: xlsxwriter
38
- Requires-Dist: click
39
- Requires-Dist: cryptography
40
- Requires-Dist: defusedxml
41
- Requires-Dist: svglib
42
- Requires-Dist: cairosvg
43
- Requires-Dist: importlib_resources
44
- Requires-Dist: authlib
45
- Requires-Dist: flask_login
46
- Requires-Dist: flask-ldapconn
47
- Requires-Dist: cyvcf2
48
- Requires-Dist: configobj
49
- Requires-Dist: ped_parser
50
58
  Requires-Dist: pydantic>=2
51
- Requires-Dist: PyYaml>=5.1
52
- Requires-Dist: intervaltree==3.0.2
53
- Requires-Dist: anytree
59
+ Requires-Dist: pymongo
54
60
  Requires-Dist: python-dateutil
61
+ Requires-Dist: pyyaml>=5.1
62
+ Requires-Dist: query-phenomizer
63
+ Requires-Dist: svglib
55
64
  Requires-Dist: tabulate
65
+ Requires-Dist: tornado>=6.4.1
66
+ Requires-Dist: werkzeug
67
+ Requires-Dist: wtforms
68
+ Requires-Dist: xlsxwriter
56
69
  Provides-Extra: coverage
57
- Requires-Dist: chanjo-report; extra == "coverage"
58
- Requires-Dist: pymysql; extra == "coverage"
59
-
70
+ Requires-Dist: chanjo-report; extra == 'coverage'
71
+ Requires-Dist: pymysql; extra == 'coverage'
72
+ Requires-Dist: python-dateutil; extra == 'coverage'
73
+ Description-Content-Type: text/markdown
60
74
 
61
75
  <p align="center">
62
76
  <a href="https://Clinical-Genomics.github.io/scout/">
@@ -127,6 +141,13 @@ cd scout
127
141
  pip install --editable .
128
142
  ```
129
143
 
144
+ Scout is configured to use `uv` if you like; either run, install, install as a tool or
145
+ ```
146
+ uv sync --frozen
147
+ uv run scout
148
+ ```
149
+
150
+
130
151
  Scout PDF reports are created using [Flask-WeasyPrint](https://pythonhosted.org/Flask-WeasyPrint/). This library requires external dependencies which need be installed separately (namely Cairo and Pango). See platform-specific instructions for Linux, macOS and Windows available on the WeasyPrint installation [pages](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation).
131
152
 
132
153
  <b>NB</b>: in order to convert HTML reports into PDF reports, we have recently switched from the WeasyPrint lib to [python-pdfkit](https://github.com/JazzCore/python-pdfkit). For this reason, when upgrading to a Scout version >4.47, you need to install an additional [wkhtmltopdf system library](https://wkhtmltopdf.org/).