changeset 2:a34826ae0a73 draft default tip

planemo upload for repository https://github.com/ERGA-consortium/EARs/tree/main commit e293d14e82a903a4cab64dd72dfa3f3798466176
author bgruening
date Fri, 30 Aug 2024 09:27:31 +0000
parents b61022e1b807
children
files macros.xml make_EAR.py make_EAR.xml test-data/EAR.pdf test-data/EAR_2.pdf
diffstat 5 files changed, 152 insertions(+), 119 deletions(-) [+]
line wrap: on
line diff
--- a/macros.xml	Tue Jul 09 07:48:46 2024 +0000
+++ b/macros.xml	Fri Aug 30 09:27:31 2024 +0000
@@ -1,6 +1,6 @@
 <macros>
-    <token name="@TOOL_VERSION@">1.0.0</token>
-    <token name="@VERSION_SUFFIX@">1</token>
+    <token name="@TOOL_VERSION@">24.08.26</token>
+    <token name="@VERSION_SUFFIX@">0</token>
     <token name="@PROFILE@">23.2</token>
     <xml name="creator">
         <creator>
@@ -21,4 +21,26 @@
             </citation>
         </citations>
     </xml>
+    <xml name="methods_tests">
+        <section name="method_data">
+                <repeat name="assembly_method_info">
+                    <param name="assembly_tools_info" value="Hifiasm: 0.19.4/HiC/l0"/>
+                </repeat>
+                <repeat name="assembly_method_info">
+                    <param name="assembly_tools_info" value="purge_dups: 1.2.6/"/>
+                </repeat>
+                <repeat name="assembly_method_info">
+                    <param name="assembly_tools_info" value="Bionano_solve: Galaxy_3.7.0"/>
+                </repeat>
+                <repeat name="assembly_method_info">
+                    <param name="assembly_tools_info" value="YaHS: 1.1"/>
+                </repeat>
+                <repeat name="curation_method_info">
+                    <param name="curation_tools_info" value="GRIT_Rapid: 2.0"/>
+                </repeat>
+                <repeat name="curation_method_info">
+                    <param name="curation_tools_info" value="HiGlass: 1.0"/>
+                </repeat>
+            </section>
+    </xml>
 </macros>
\ No newline at end of file
--- a/make_EAR.py	Tue Jul 09 07:48:46 2024 +0000
+++ b/make_EAR.py	Fri Aug 30 09:27:31 2024 +0000
@@ -1,6 +1,5 @@
 
 import argparse
-import glob
 import logging
 import math
 import os
@@ -22,7 +21,7 @@
 # CAUTION: This is for the Galaxy version!
 # by Diego De Panis
 # ERGA Sequencing and Assembly Committee
-EAR_version = "v24.05.20_glxy_beta"
+EAR_version = "v24.08.26"
 
 
 def make_report(yaml_file):
@@ -120,19 +119,9 @@
                     fifth_column_value = target_line.split('\t')[4].strip()
                     return fifth_column_value
         except Exception as e:
-            logging.warning(f"Error reading {file_path}: {str(e)}")
+            logging.error(f"Error reading {file_path} for tool {tool} and haplotype {haplotype}: {str(e)}")
             return ''
 
-    # Getting kmer plots for curated asm
-    def get_png_files(dir_path):
-        png_files = glob.glob(f"{dir_path}/*.ln.png")
-        if len(png_files) < 4:
-            logging.warning(f"Warning: Less than 4 png files found in {dir_path}. If this is diploid, some images may be missing.")
-            # fill missing with None
-            while len(png_files) < 4:
-                png_files.append(None)
-        return png_files[:4]
-
     # get unique part in file names
     def find_unique_parts(file1, file2):
         # Split filenames into parts
@@ -141,7 +130,6 @@
         # Find unique parts
         unique_parts1 = [part for part in parts1 if part not in parts2]
         unique_parts2 = [part for part in parts2 if part not in parts1]
-
         return ' '.join(unique_parts1), ' '.join(unique_parts2)
 
     # extract BUSCO values
@@ -274,33 +262,34 @@
     # Parse pipeline and generate "tree"
     def generate_pipeline_tree(pipeline_data):
         tree_lines = []
-        indent = "&nbsp;" * 2  # Adjust indent spacing as needed
-
-        for tool_version_param in pipeline_data:
-            parts = tool_version_param.split('|')
-            tool_version = parts[0]
-            tool, version = tool_version.split('_v') if '_v' in tool_version else (tool_version, "NA")
+        indent = "&nbsp;" * 2  # Adjust indent spacing
 
-            # Handle parameters: join all but the first (which is tool_version) with ', '
-            param_text = ', '.join(parts[1:]) if len(parts) > 1 else "NA"
+        if isinstance(pipeline_data, dict):
+            for tool, version_param in pipeline_data.items():
+                # Tool line
+                tool_line = f"- <b>{tool}</b>"
+                tree_lines.append(tool_line)
 
-            # Tool line
-            tool_line = f"- <b>{tool}</b>"
-            tree_lines.append(tool_line)
+                # Convert version_param to string and split
+                version_param_str = str(version_param)
+                parts = version_param_str.split('/')
+                version = parts[0]
+                params = [p for p in parts[1:] if p]  # This will remove empty strings
 
-            # Version line
-            version_line = f"{indent*2}|_ <i>ver:</i> {version}"
-            tree_lines.append(version_line)
+                # Version line
+                version_line = f"{indent * 2}|_ <i>ver:</i> {version}"
+                tree_lines.append(version_line)
 
-            # Param line(s)
-            if param_text != "NA":
-                for param in param_text.split(','):
-                    param = param.strip()
-                    param_line = f"{indent*2}|_ <i>key param:</i> {param if param else 'NA'}"
+                # Param line(s)
+                if params:
+                    for param in params:
+                        param_line = f"{indent * 2}|_ <i>key param:</i> {param}"
+                        tree_lines.append(param_line)
+                else:
+                    param_line = f"{indent * 2}|_ <i>key param:</i> NA"
                     tree_lines.append(param_line)
-            else:
-                param_line = f"{indent*2}|_ <i>key param:</i> NA"
-                tree_lines.append(param_line)
+        else:
+            tree_lines.append("Invalid pipeline data format")
 
         # Join lines with HTML break for paragraph
         tree_diagram = "<br/>".join(tree_lines)
@@ -330,10 +319,10 @@
     tags = yaml_data["Tags"]
 
     # Check if tag is valid
-    valid_tags = ["ERGA-BGE", "ERGA-Pilot", "ERGA-Satellite"]
+    valid_tags = ["ERGA-BGE", "ERGA-Pilot", "ERGA-Community", "ERGA-testing"]
     if tags not in valid_tags:
         tags += "[INVALID TAG]"
-        logging.warning("# SAMPLE INFORMATION section in the yaml file contains an invalid tag. Valid tags are ERGA-BGE, ERGA-Pilot and ERGA-Satellite")
+        logging.warning("# SAMPLE INFORMATION section in the yaml file contains an invalid tag. Valid tags are ERGA-BGE, ERGA-Pilot and ERGA-Community.")
 
     # Get data from GoaT based on species name
     # urllib.parse.quote to handle special characters and spaces in the species name
@@ -401,16 +390,15 @@
     # Create a list of lists for the table
     table_data = [headers, data_values]
 
-    # Extract pipeline data from 'Pre-curation' category
-    asm_pipeline_data = yaml_data.get('ASSEMBLIES', {}).get('Pre-curation', {}).get('pipeline', [])
-    asm_pipeline_tree = generate_pipeline_tree(asm_pipeline_data)
+    # Extract pipeline data
+    asm_pipeline_data = yaml_data.get('PIPELINES', {}).get('Assembly', {})
+    curation_pipeline_data = yaml_data.get('PIPELINES', {}).get('Curation', {})
 
     # Extract pipeline data from 'Curated' category
-    curation_pipeline_data = yaml_data.get('ASSEMBLIES', {}).get('Curated', {}).get('pipeline', [])
+    asm_pipeline_tree = generate_pipeline_tree(asm_pipeline_data)
     curation_pipeline_tree = generate_pipeline_tree(curation_pipeline_data)
 
     # Reading GENOME PROFILING DATA section from yaml #############################################
-
     profiling_data = yaml_data.get('PROFILING')
 
     # Check if profiling_data is available
@@ -418,38 +406,46 @@
         logging.error('Error: No profiling data found in the YAML file.')
         sys.exit(1)
 
-    # Handle GenomeScope specific processing
+    # Check for GenomeScope data (mandatory)
     genomescope_data = profiling_data.get('GenomeScope')
-    if genomescope_data:
-        summary_file = genomescope_data.get('genomescope_summary_txt')
-        if summary_file and os.path.exists(summary_file):
-            with open(summary_file, "r") as f:
-                summary_txt = f.read()
-            genome_haploid_length = re.search(r"Genome Haploid Length\s+([\d,]+) bp", summary_txt).group(1)
-            proposed_ploidy_match = re.search(r"p = (\d+)", summary_txt)
-            proposed_ploidy = proposed_ploidy_match.group(1) if proposed_ploidy_match else 'NA'
-        else:
-            logging.error(f"File {summary_file} not found for GenomeScope.")
-            sys.exit(1)
-    else:
-        logging.error("GenomeScope data is missing in the PROFILING section.")
+    if not genomescope_data:
+        logging.error("Error: GenomeScope data is missing in the YAML file. This is mandatory.")
+        sys.exit(1)
+
+    genomescope_summary = genomescope_data.get('genomescope_summary_txt')
+    if not genomescope_summary:
+        logging.error("Error: GenomeScope summary file path is missing in the YAML file.")
         sys.exit(1)
 
-    # Handle Smudgeplot specific processing
+    # Read the content of the GenomeScope summary file
+    try:
+        with open(genomescope_summary, "r") as f:
+            summary_txt = f.read()
+        # Extract values from summary.txt
+        genome_haploid_length = re.search(r"Genome Haploid Length\s+([\d,]+) bp", summary_txt).group(1)
+        proposed_ploidy = re.search(r"p = (\d+)", summary_txt).group(1)
+    except Exception as e:
+        logging.error(f"Error reading GenomeScope summary file: {str(e)}")
+        sys.exit(1)
+
+    # Check for Smudgeplot data (optional)
     smudgeplot_data = profiling_data.get('Smudgeplot')
     if smudgeplot_data:
-        verbose_summary_file = smudgeplot_data.get('smudgeplot_verbose_summary_txt')
-        if verbose_summary_file and os.path.exists(verbose_summary_file):
-            with open(verbose_summary_file, "r") as f:
-                smud_summary_txt = f.readlines()
-            for line in smud_summary_txt:
-                if line.startswith("* Proposed ploidy"):
-                    proposed_ploidy = line.split(":")[1].strip()
-                    break
+        smudgeplot_summary = smudgeplot_data.get('smudgeplot_verbose_summary_txt')
+        if smudgeplot_summary:
+            try:
+                with open(smudgeplot_summary, "r") as f:
+                    smud_summary_txt = f.readlines()
+                for line in smud_summary_txt:
+                    if line.startswith("* Proposed ploidy"):
+                        proposed_ploidy = line.split(":")[1].strip()
+                        break
+            except Exception as e:
+                logging.warning(f"Error reading Smudgeplot summary file: {str(e)}. Using GenomeScope ploidy.")
         else:
-            logging.warning(f"Verbose summary file {verbose_summary_file} not found for Smudgeplot; skipping detailed Smudgeplot analysis.")
+            logging.warning("Smudgeplot summary file path is missing. Using GenomeScope ploidy.")
     else:
-        logging.warning("Smudgeplot data is missing in the PROFILING section; skipping Smudgeplot analysis.")
+        logging.info("Smudgeplot data not provided. Using GenomeScope ploidy.")
 
     # Reading ASSEMBLY DATA section from yaml #####################################################
 
@@ -459,7 +455,7 @@
     asm_stages = []
     for asm_stage, stage_properties in asm_data.items():
         for haplotypes in stage_properties.keys():
-            if haplotypes != 'pipeline' and haplotypes not in asm_stages:
+            if haplotypes not in asm_stages:
                 asm_stages.append(haplotypes)
 
     # get gfastats-based data
@@ -483,7 +479,7 @@
         except (ValueError, ZeroDivisionError):
             gaps_per_gbp_data[(asm_stage, haplotypes)] = ''
 
-    # Define the contigging table (column names) DON'T MOVE THIS AGAIN!!!!!!!
+    # Define the contigging table (column names)
     asm_table_data = [["Metrics"] + [f'{asm_stage} \n {haplotypes}' for asm_stage in asm_data for haplotypes in asm_stages if haplotypes in asm_data[asm_stage]]]
 
     # Fill the table with the gfastats data
@@ -493,8 +489,6 @@
             asm_table_data.append([metric] + [format_number(gfastats_data.get((asm_stage, haplotypes), [''])[i]) if (asm_stage, haplotypes) in gfastats_data else '' for asm_stage in asm_data for haplotypes in asm_stages if haplotypes in asm_data[asm_stage]])
 
     # Add the gaps/gbp in between
-    gc_index = display_names.index("GC %")
-    gc_index
     asm_table_data.insert(gaps_index + 1, ['Gaps/Gbp'] + [format_number(gaps_per_gbp_data.get((asm_stage, haplotypes), '')) for asm_stage in asm_data for haplotypes in asm_stages if haplotypes in asm_data[asm_stage]])
 
     # get QV, Kmer completeness and BUSCO data
@@ -502,7 +496,7 @@
     completeness_data = {}
     busco_data = {metric: {} for metric in ['BUSCO sing.', 'BUSCO dupl.', 'BUSCO frag.', 'BUSCO miss.']}
     for asm_stage, stage_properties in asm_data.items():
-        asm_stage_elements = [element for element in stage_properties.keys() if element != 'pipeline']
+        asm_stage_elements = list(stage_properties.keys())
         for i, haplotypes in enumerate(asm_stage_elements):
             haplotype_properties = stage_properties[haplotypes]
             if isinstance(haplotype_properties, dict):
@@ -580,7 +574,7 @@
     styles.add(ParagraphStyle(name='subTitleStyle', fontName='Courier', fontSize=16))
     styles.add(ParagraphStyle(name='normalStyle', fontName='Courier', fontSize=12))
     styles.add(ParagraphStyle(name='midiStyle', fontName='Courier', fontSize=10))
-    styles.add(ParagraphStyle(name='LinkStyle', fontName='Courier', fontSize=10, textColor='blue', underline=True))
+    # styles.add(ParagraphStyle(name='LinkStyle', fontName='Courier', fontSize=10, textColor='blue', underline=True))
     styles.add(ParagraphStyle(name='treeStyle', fontName='Courier', fontSize=10, leftIndent=12))
     styles.add(ParagraphStyle(name='miniStyle', fontName='Courier', fontSize=8))
     styles.add(ParagraphStyle(name='FileNameStyle', fontName='Courier', fontSize=6))
@@ -659,7 +653,7 @@
 
     # Iterate over haplotypes in the Curated category to get data for EBP metrics
     curated_assemblies = yaml_data.get('ASSEMBLIES', {}).get('Curated', {})
-    haplotype_names = [key for key in curated_assemblies.keys() if key != 'pipeline']
+    haplotype_names = list(curated_assemblies.keys())
 
     for haplotype in haplotype_names:
         properties = curated_assemblies[haplotype]
@@ -756,7 +750,7 @@
     # Store BUSCO version and lineage information from each file in list
     busco_info_list = []
     for asm_stages, stage_properties in asm_data.items():
-        for haplotype_keys, haplotype_properties in stage_properties.items():
+        for i, haplotype_properties in stage_properties.items():
             if isinstance(haplotype_properties, dict):
                 if 'busco_short_summary_txt' in haplotype_properties:
                     busco_version, lineage_info = extract_busco_info(haplotype_properties['busco_short_summary_txt'])
@@ -787,9 +781,9 @@
     tool_count = 0
 
     # Add title and images for each step
-    for idx, (asm_stages, stage_properties) in enumerate(asm_data.items(), 1):
+    for asm_stages, stage_properties in asm_data.items():
         if asm_stages == 'Curated':
-            tool_elements = [element for element in stage_properties.keys() if element != 'pipeline']
+            tool_elements = list(stage_properties.keys())
 
             images_with_names = []
 
@@ -825,7 +819,7 @@
 
             # Add images and names to the elements in pairs
             for i in range(0, len(images_with_names), 4):  # Process two images (and their names) at a time
-                elements_to_add = images_with_names[i:i + 4]
+                elements_to_add = images_with_names[i: i + 4]
 
                 # Create table for the images and names
                 table = Table(elements_to_add)
@@ -856,7 +850,6 @@
 
     # Iterate over haplotypes in the Curated category to get K-mer spectra images
     curated_assemblies = yaml_data.get('ASSEMBLIES', {}).get('Curated', {})
-    haplotype_names = [key for key in curated_assemblies.keys() if key != 'pipeline']
 
     # Get paths for spectra files
     spectra_files = {
@@ -974,9 +967,9 @@
     tool_count = 0
 
     # Add title and images for each step
-    for idx, (asm_stages, stage_properties) in enumerate(asm_data.items(), 1):
+    for asm_stages, stage_properties in asm_data.items():
         if asm_stages == 'Curated':  # Check if the current stage is 'Curated'
-            tool_elements = [element for element in stage_properties.keys() if element != 'pipeline']
+            tool_elements = list(stage_properties.keys())
 
             for haplotype in tool_elements:
                 haplotype_properties = stage_properties[haplotype]
--- a/make_EAR.xml	Tue Jul 09 07:48:46 2024 +0000
+++ b/make_EAR.xml	Fri Aug 30 09:27:31 2024 +0000
@@ -39,7 +39,6 @@
 # ASSEMBLY DATA
 ASSEMBLIES:
     Pre-curation:
-        pipeline: [Hifiasm_v0.19.4|HiC|l0, Purge_Dups_v1.2.6|, Bionano_vGalaxy_3.7.0, YaHS_v1.1]
         '${pre_curation_assembly_data.haplotype_selection}': 
             gfastats--nstar-report_txt: '${pre_curation_assembly_data.gfstats_nstar_report_precuration}'
             busco_short_summary_txt: '${pre_curation_assembly_data.busco_short_summary_precuration}'
@@ -52,7 +51,6 @@
             merqury_completeness_stats: '${pre_curation_assembly_data.hap2_precuration_data.merqury_completeness_stats_hap2_precuration}'
 
     Curated:
-        pipeline: [GRIT_rapid_v2.0, HiGlass_v1.0]
         '${pre_curation_assembly_data.haplotype_selection}':
             gfastats--nstar-report_txt: '${curated_assembly_data.gfstats_nstar_report_curated}'
             busco_short_summary_txt: '${curated_assembly_data.busco_short_summary_curated}'
@@ -80,7 +78,6 @@
 # ASSEMBLY DATA
 ASSEMBLIES:
     Pre-curation:
-        pipeline: [Hifiasm_v0.19.4|HiC|l0, Purge_Dups_v1.2.6|, Bionano_vGalaxy_3.7.0, YaHS_v1.1]
         '${pre_curation_assembly_data.haplotype_selection}': 
             gfastats--nstar-report_txt: '${pre_curation_assembly_data.gfstats_nstar_report_precuration}'
             busco_short_summary_txt: '${pre_curation_assembly_data.busco_short_summary_precuration}'
@@ -88,7 +85,6 @@
             merqury_completeness_stats: '${pre_curation_assembly_data.merqury_completeness_stats_precuration}'
 
     Curated:
-        pipeline: [GRIT_rapid_v2.0, HiGlass_v1.0]
         '${pre_curation_assembly_data.haplotype_selection}':
             gfastats--nstar-report_txt: '${curated_assembly_data.gfstats_nstar_report_curated}'
             busco_short_summary_txt: '${curated_assembly_data.busco_short_summary_curated}'
@@ -102,6 +98,18 @@
             blobplot_cont_png: '${curated_assembly_data.blobplot_cont_curated}'
 #end if
 
+# METHODS DATA
+PIPELINES:
+  Assembly:
+    #for $repeat in $method_data.assembly_method_info:
+    ${repeat.assembly_tools_info}
+    #end for
+
+  Curation:
+    #for $repeat in $method_data.curation_method_info:
+    ${repeat.curation_tools_info}
+    #end for
+
 # CURATION NOTES
 NOTES:
     Obs_Haploid_num: '${curation_notes.obs_haploid_num}'
@@ -131,7 +139,7 @@
             <param name="tags" type="select" label="Select a valid tag"> 
                 <option value="ERGA-BGE" selected="true">ERGA-BGE</option>
                 <option value="ERGA-Pilot">ERGA-Pilot</option>
-                <option value="ERGA-Satellite">ERGA-Satellite</option>
+                <option value="ERGA-Community">ERGA-Community</option>
             </param>
         </section>
 
@@ -150,7 +158,6 @@
 
         <!-- Input parameters for Assembly data precurated -->
         <section name="pre_curation_assembly_data" title="Pre-Curation Assembly Information">
-            <param name="pipeline_tools_precuration" type="text" label="Input tool names along with version and paramters used" help="[Insert ToolA_v1.2.3|ParamX|ParamY, Insert ToolB_v2.3.4] valid input is empty or between brackets ToolName followed by _v followed by versionNumber followed by | followed by keyToolParameter"/>
             <param name="haplotype_selection" type="select"  label="Select a valid haplotype"> 
                 <option value="hap1">hap1</option>
                 <option value="pri">pri</option>
@@ -177,7 +184,6 @@
         
         <!-- Input parameters for Assembly data Curated -->    
         <section name="curated_assembly_data" title="Curated Assembly Data Information">    
-            <param name="pipeline_tools_curation" type="text" label="Input tool names along with version and paramters used" help="[Insert ToolA_v1.2.3|ParamX|ParamY, Insert ToolB_v2.3.4] valid input is empty or between brackets ToolName followed by _v followed by versionNumber followed by | followed by keyToolParameter"/>
             <param name="gfstats_nstar_report_curated" type="data" format="txt" label="Select curated gfastats--nstar report file"/>
             <param name="busco_short_summary_curated" type="data" format="txt" label="Select curated busco_short_summary.txt file"/>
             <param name="mercury_qv_curated" type="data" format="txt" label="Select curated Merqury results .qv file"/>
@@ -210,6 +216,16 @@
             </conditional>
         </section>
         
+        <!-- Input parameters for Methods data -->
+        <section name="method_data" title="Method Information">
+            <repeat name='assembly_method_info' title="Specify the method used for Assembly" min="1" default="2">
+                <param name="assembly_tools_info" type="text" label="Input tool names along with version and paramters used for assembly" optional="False" help="Specify ToolName:Tool_Version/Tool_parameters"/>
+            </repeat>
+            <repeat name='curation_method_info' title="Specify the method used for Curation" min="1" default="2">
+                <param name="curation_tools_info" type="text" label="Input tool names along with version and paramters used for curation" optional="False" help="Specify ToolName:Tool_Version/Tool_parameters"/>
+            </repeat>
+        </section>
+
         <!-- Input parameters for Curation notes --> 
         <section name="curation_notes" title="Curation Notes">
             <param name="obs_haploid_num" type="text" optional="False" label="Insert observed haploid number"/>
@@ -282,6 +298,7 @@
                     <param name="hap2_exists_curated" value="no"/>
                 </conditional>
             </section>
+            <expand macro="methods_tests"></expand>
             <section name="curation_notes">
                 <param name="obs_haploid_num" value="28"/>
                 <param name="obs_sex" value="XX"/>
@@ -356,6 +373,7 @@
                     <param name="blobplot_cont_hap2_curated" value="blob2.png"/>
                 </conditional>
             </section>
+            <expand macro="methods_tests"></expand>
             <section name="curation_notes">
                 <param name="obs_haploid_num" value="28"/>
                 <param name="obs_sex" value="XX"/>
--- a/test-data/EAR.pdf	Tue Jul 09 07:48:46 2024 +0000
+++ b/test-data/EAR.pdf	Fri Aug 30 09:27:31 2024 +0000
@@ -153,7 +153,7 @@
 endobj
 20 0 obj
 <<
-/Author (\(anonymous\)) /CreationDate (D:20240704122313+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20240704122313+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) 
+/Author (\(anonymous\)) /CreationDate (D:20240829084627+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20240829084627+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) 
   /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
 >>
 endobj
@@ -164,10 +164,10 @@
 endobj
 22 0 obj
 <<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 1481
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1471
 >>
 stream
-Gat=+D,91O&H9tY(p.mXG?$ur]JSl.P84Y<^#NLOkd2Fn<c6s+=XA(>EIluM^TmAs>*>+='f6gn1\6Pgo>Dq_j+j$%o*jP6Js-QDKL3$Gi`[s&=)]3$rB)8=d\SW(-<:\2!IE[UD&24'#^o8XmPo_F_cRVOD.6?_BlnfJq&)@4"j^fF(@Ls$8G*Rh*tWkKBl<IIRqgq?WV2[+k9(/E-;bLWP'Ee*AU>3#^G"(!mIqK"Clj9>H9!tD4#o]F=\M,"a78EUq-\Ns]`f['/F#n1n85d+_?t-5#Uo%h,HY5J&J$4ELL^9.3@m@&7/\h?Z_UZYZNFp.Anl<mC-g:MJk9AG&=V&i7F,??BU..<&K?(tK$hqb8O.Wa\d9`j),FN)#<]P9#0;JMW3u6go':%;Og7K_+(;)f+$=;nn(2rYn=lE5`5atT&@80H&gn)'R#DJR776*SQEm54Y2Na(/m4mD=GqDPUi2nb1?Pk]'nd%]H4qY^an#6WBnt1Yp)!`,W"/doo#UC[WTfu_-T\3;,_^ST[i;B]A$]C<HG.KdUl?66CTAO8<d_!NY2<@OZ*mQ.Z/T8-29i392I3HP\$&<Go7P=#$[p4L'BXVdY>#jRoO/0Q&++-lWX"1/G'W44(E&!!Z:+Am#XL2BNPkQJ[]/8+2qFZ*LU5"WbR,sEqli8!YCZYoeu;2L.$FTi4H*n<LQ=Slr6V1GQSZ&V)1/eRVSqk1@lp"VJp1I49W9tJiM8-@*\qqpeFb/XosHi#oi>*'\CD+b6>Y_Q:B>g6(J;c_8rsB\;L-]`)ru=dhBD")CGOX.)K$#]2%I4Qg9ClMOCkJcpFKOH*:EWD2maQa')V(N:tZkV_iis3g3$a88o)0eeE^E>7M8>b^*JroI7YihqiXcZ)bDF_;eIYR>4R>Ml^.0QSd+8mV7i3.QQ3`(*4sIT^i*S>:;9-8Pt^<D'UpX`Ef3X)_gE\s8jj0":cn-<=1k%Gm,R2$%U[n&6`YWF]9t+6j?*Xkn)oG%M:tum@3HntfU>J7UKb5,+?@3/SEh1@8)-j_d0eu8UoAT2nn#.U:E3B$D3W+\kN4>7X!YQ=QV4E3'enYR2s!Ro(KncL(`*O5S'>L!m-k:=Mj)*]A*"%5;ImQnFe'!XlLnO#*I@LlfR4)Be7oH)dK`@Kca)ROn=!m;$Ti9?iBEF6s2$!R2dE[]Gu9)q0g4YnV[RUOah&5dS`*W-@A3rUT'OT@4MnStTY^M$WVL7lnpIE;O:"\\4#k!g/[O>].b&h6YFRJO+.2u!Hr.:23sj[4Z'3Zlmu&sQbD+LLoq$QQQ1!F4Mcat>3L`A23\s-8_3r@ZbEBLaE`;a@&>O"ACWQsEmn+-!4Q$R8X29I<&gcZjOiK4c.4iZCdM!,#jI'];CIVnc'Ij9e:8\O"U!;R$f-L\YA;826M(9p>*-8#,>8K8LP-6lEOQ8*&8T1\+2&0l<7R9`=7kP0T8%D6*44VNg_pSLooqXb~>endstream
+Gat=+hbW8l&:WfG(p.mXG?$ur3,-7I8INRZHcNr(eS-+ME>uC3.Z(ct$hXGp)`p8T99QTZJk"d=B"kN_^:4;Nr"/aTScP?UiT(@I6=IG*_Aid//%CrfrB)8=P#7U"'.43`&3Y!K[0p_pKYF5gG9"IV_cRVOCL^3TBln?=q&)@4Kudlj(@K+E.fBsV5@F?0Uq+IjDQ8QNUtT/_]RqcjRRC.P8`Gjf`sj%-$tf3)De:YKBBCXM(8kOuiV;d1@PBt0/t>>$6bQ%sJa9a830EN^+bfl5Um2l`ikkohE,;,1JAimpBKd2>Qcog,Fs-2#Cb>"M(*q]cR"S37OZi<`o_IepXjYoOiaPDX0H,4qKX:tu>&WDen`85L^lpG.!1=,IZZ^e(ITp[(V#6AA7i3^'!2!HhqmDe=U?Epa#r#-Y`%5Vk`!M2&-K>2.dE"A=VnD5MC\_&993u9Xf.nBS)-H*^MM:=bKi*K6hSdN0nU/&Cg*'1GrIo_cW^FP54E>goC:Ye13=`)B8`AO[h8\aJf\@7B?LRHGBOf'&g)<A='d%3A$Q:NZelM&n<dZI#[[Bk,VQ5^Tp@X7;6.e2,;;err_O0fjg4U'a*6-A'd(Y70Ah]&,Q=hgjR0f61T'2a]+Xk16EO^852Us#>`d3m-E7W[2=Wu:)+#ZC'[BMmk3gE8F7Im($LXBr:n?Ia?&37,ko#<:*A)Cj-8luTCf5lN[]Z7Z>V>ciOPJ6g(aOh!%lV0;'CN[M<<`T^?eISEADU_p]!qs8TZrDI=+;LuRnt]\QM##-K,N[,g1>=u2XG6Zl#?,O-fRk><Zdd?KSukolpYr_&>)l(/rVh5Dljh6J8RS]@h3WfCg+Nd*HC)C3K[RLd$)McO&k_K(,a?kL'$A$I)9\qD4JC=1V**G/!P)UFpF=&$iN($SCMj8@qa,Du%5OVW1,#<QA/SHQCm#Xt)oZqDF3(37H2kP:jb6PR05kn+U.-)*m<dR39_0MG0c5@(2`sh,[c.T$WF3]sj:j;ZF$"W)nq)=2bWeXYYYKAj(!<RN;u)[=b@^XC"KF*3BURi)c$>0_9RIX0QWODE?mZG&b>D_$ZV9F7/ef3_S#a'Pi=QhA-]8/Jau(`7bWo.mH9,.Vlb+oso^I".q\1$KeJ`V-&!CCoA]Hmj29Gh5p>405I3`b2Kh%_PKOL.'U&_mdB$?`>)<BkAG:3AO_l^_l6k.Wi2jlaP6uV*O_tTd$o7JA)q,4C=Q'hJnn:#^gk+MHJW;k`339Naq0_n$1TWC(6M;%O*PLQr-M_`o-L=?@NSp\ejl.k'M=E/@A4,*6JAr"T]F.$g8-$`/E:>pgoN34J.PpcdL.mYfWJ1G<9$O?Z&[%+=1*ijslYUltM:H79)A?p?]);2"9W"cm*SMKtq2N&MZ=!6ml=XGdIY..XBbakVY?/P]4YngS3P/D[Dr3894<9iih"Yn+@Kdob:jXQtk<%n+Y8,i[M-(l[~>endstream
 endobj
 23 0 obj
 <<
@@ -188,7 +188,7 @@
 /Filter [ /ASCII85Decode /FlateDecode ] /Length 501
 >>
 stream
-GatUo]l"cD&;9q-MCmoi4EHi^EnM@f)lF9e7Cn#a!J2;USE.P*Dd&$0fhqks#(^RPAnSk%LnOX]rAW%&2\THR+s=_O!-r+B5/X:4pbh!u5FK<F+%:d'7i1f(=M>7#MoTg0@%-sD!n-sKoJC9)VrkS+p1MX^-,QNF^0fm&(5U^^S^WXT/""&go\h6?h9YCl'99VA,iULm'DQsO4jM.VY@&?3KTXC)_+7$b6qmnHa?_kO1L/gTQMJ]NNS8-P`^U_<JN+iR?!2OtVgY?B+#e,(mlA[bjBV*]q73i\Ki]SgijS9hl:eA7m$+`Yi_3KD5uu7['V!Y!eZG@@G3l@rG"E06B>"-YIIf%G-pEq6X[D[4QOo*P,[4HVkO^EImk:C0R)Par<mW;HkZ+Y).83(>iuC]?M=k4KE5;B)gr,Ft$/)al9J*].\JBUsTNcf`MmokHJ]beP2m)sWVkhu2VGd&f4L2F[j;1dRnKR8pr!eeYa+prA@!Dg!<TsY~>endstream
+GatUo9lC\2%#46J.sqarZaDsRf*ZhT9;F^i6_5qVe5]li%fC[N$!kt++OOr"\TmN9I@hMn?pAbGo*fS"_9Eu$0h`%tN!?/?n9u:i&+o`l_r-.h6\qF[oE$'AbEnpsdQ1,,R-$SjJak^X+!thgW4=Obs&S`"&_Jt^58H9/!j^_83Sj^_"4L>=hEGUjqg#3Bj/W!X`$UMCUUdaRN,rUi8Jh[Pib7!8WKD<?M3(Np(f$n@SDiUJ\'1IfBgdlqC+CVR7lE\=Z'sSq47pAnhX-o&mlJacl!.jjk)n?-iD#Gem-EW:7%:Gr<)qPM%,.Y3`iQ3/??30/kVft[)LL(C=gFgMBcgZG^A4(i"><3^3b"_0M,=Kn[\ZE>RJr9&lQHsY`cW<`P=ZbrOV6*l+e[-sdgEq%B`ACTJMI1gRgl9#@^[j)?&S+:>SH!,oG4(`l<hS57('2^k^?DgLS4KR,8F*'Q'hCP[_,'E1"W1<06`5[?Pd98_!h`dqWX^~>endstream
 endobj
 26 0 obj
 <<
@@ -199,10 +199,10 @@
 endobj
 27 0 obj
 <<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 820
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 825
 >>
 stream
-Gau`R9lo#B&A@Zcp.-ul,0@]f\Ift29W)TVHg$;`)FKedK,SEeRJuuaAK)'1Zsi6C(pIWHl0[6UC,#gJ+(fsdLB(Bs>[B3o*5"9c5m2H$@-"`0[9/G'*$"2S.:%clcp<X9=gW-$gX:3iGTh*Ii7)`9Pc6fFCB_m,T'O1A"5X]9(Ff;'IeH.]ei#qL`@s-X*!m**CEP7s9$-&]M5hcT-Z$"m@RCA:M%B394jg7M4COhZ>=sTJ/:tjhClJ4Rl4V8(,(5K?k\FI`@a*\k?0O=H;-\`q,`r9$2N7:$)J=]%F*eIJpCjt(CXfnJk<M>*CEcpr,A&'!$^*qDG7TiXcjbGro"s7E>GMWkMW0E,H-2/j!2db.[mmrAUH;<2Y1V"$XVI1jeXUT92N'/SZ1J#pHa_Wg*1kVlX5%]=gl/].<]4IQEia>c6@ZE6_h[.RQ?qd#3]_O.CuNr:&8kW;q67**0e&'P75TF1'GNU%h#S2^o%M5lfY)@.<afkuG,2_')7KD3F&juOJdFNEG<jU]8si8)0qZAZe]3!87"r\*PPgI=A_M\&=HBNU@?W8?fl49&2,u>"33erDm!\n5@cck(5ZVA0W99M873"YoI<(t0AD5gaYm(lA)%XSr[H!r;3F4_>#+J#_Yn-hI64$s[,ctt_KX1Dh#LrPJXZ&o&;Em'^K`$>lj@#%2[;'Z>1mq9::KY!4-b0'N5>GO,,qF3$FRp(/obs$IS+oVZ_t[bLEllT8e=Yc*W\uf/QObVaI68CjWIAVW?1tfan>4S='g9E;LOKbP-l3ohF=82kk]*ZD//X>ChLR:mr;q2qU(7~>endstream
+Gau`R9lo#B&A@Zcp.-ul,0<0;\KrMPAif<)\;-XmV(nrIIo$5]16Wg;[?/4PQo7Xo%Kk;K5.uBV=tO>On\a_^f*("oK[VFUN'fQ*-A[3A"'gS_+"cW:JqL7SU/[e)#<Jf+j:<itb>r^*"_!!f*&t?2]O5J]kdcF^SkVlu]n>ed_NTTN#!\S#Upg\.FYO<L4^WcdWZ"Dn]1R(qI\NhVpB_-]+;-`Fa<R(BCks]3\qIUe[uq)r=9O["fi[n^eJ4>+7='_-ROb]d`L'noSXV1\Bi]lY'i?:J;j*UKI[gOFkB9(:MK8Y,eYii]G<eHdCEe'=,;)9`(8T*'_$6\pTPOHh]#*;6>,2NjbMjnbH,5Na(e3(K=%9c0UME]jhQr\u.k<6=<dV[eg+HE6`ejjLm*!r5iH%N(>WKjLDDOo8h:mmBbP.%1$>6\JPha520K%<-2X/'T+@T*S5m10oe\f84`hY<^18=/WX.[rKm1.dIVcTI2@J<NUSgDZemO31bQe;7tf5\nX8g`oGhA/g)-t*,gepInhpB^)]F.MYQ&)A9%bHs!0ZgLk0]#YtfnbloXCT:d$E$CcBm,Qcm`8#GQca*<pC+?]DNqN"efHWSRs$"CTH8o[-e6%>0eI2a-=m_<Lb`=^.WW"*nF6nWGRT"giN_6KZ5GocVl^/UG^(88nH>;l;3^Wg1H?*['Z(<g49Ua2)VC+K0gCl^ErSJtU]961+EjW>Lb^>FB_KGQ(Ko>;*?ZO,G%!4DDG?&-k5gKlj\GU_.QG<IU[;a5tA`0V%"?O5O\OiM:hG>gc<-<G9Yb$q>mR."p'Q3/eeh:2MUr<~>endstream
 endobj
 xref
 0 28
@@ -229,15 +229,15 @@
 0000330272 00000 n 
 0000330556 00000 n 
 0000330649 00000 n 
-0000332222 00000 n 
-0000333508 00000 n 
-0000333921 00000 n 
-0000334513 00000 n 
-0000335058 00000 n 
+0000332212 00000 n 
+0000333498 00000 n 
+0000333911 00000 n 
+0000334503 00000 n 
+0000335048 00000 n 
 trailer
 <<
 /ID 
-[<d7f6d7ff6a3023e1867515d20f8886eb><d7f6d7ff6a3023e1867515d20f8886eb>]
+[<8c056efa70858ae5ac5250f2ea61c6b1><8c056efa70858ae5ac5250f2ea61c6b1>]
 % ReportLab generated PDF document -- digest (http://www.reportlab.com)
 
 /Info 20 0 R
@@ -245,5 +245,5 @@
 /Size 28
 >>
 startxref
-335969
+335964
 %%EOF
--- a/test-data/EAR_2.pdf	Tue Jul 09 07:48:46 2024 +0000
+++ b/test-data/EAR_2.pdf	Fri Aug 30 09:27:31 2024 +0000
@@ -180,7 +180,7 @@
 endobj
 23 0 obj
 <<
-/Author (\(anonymous\)) /CreationDate (D:20240704122404+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20240704122404+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) 
+/Author (\(anonymous\)) /CreationDate (D:20240829084715+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20240829084715+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) 
   /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
 >>
 endobj
@@ -191,10 +191,10 @@
 endobj
 25 0 obj
 <<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 1506
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1495
 >>
 stream
-Gau0CgMYb*&:Ml+$jnQD(60S:(JB3ZTZJP6G-ac((lLQl-tnOI<CiiWd7LuX^I.9qfob@F^qrVn%+!.f+_QgK!]GL`q*.LQc6mRS7=\4%b^m9i$^2O>S=KL>+XPR50FC9M&;1(1i.>e]jPg;j8KI4UJ4YX'-rA[:U,p`\o&9EiI*?Z':6Q,Ln<K+#4\U=?jF^3KO.Y6-,HjkM^Z7H]=kFIGZClff'6Pn]Z`*hNOh>ME:UsrJ*'DZk_$Q1`0O]le%!k!1c`]!Ke!'JW+JE:BB,#l7gC4crBR<$1@K.9\A_N2i0L@mll6j\^rW+8/NrbUo]gA508@(-OMn?(p8N-u'J`T74:4FsCq=I1PZmL[_fNTY\L5nn.g4@aJ3:_rOiatF5fp1#R-H&BUS6m%=QCFq.4d#[KIHC7^;<.Ycij5$8m3G1cjsbkK,t[)EAh_89XJ[Nd)Ma6(jW/=Mrt9M"E[A[s7"$5,"20m1QgL^oXAQIm.%X%sc'E?*V!bJ5q"LOGoS;`GbCeuhHW$BtT^7gE*f]!YXnIe"DP4$&2ZJliApg\]ITThEZ*LriD8jQ<8tF[nO+l+@ZVFVH[C4u-*\ktO>":.9/R)#<lT)^<@DpQKd5O:sX5Tuu$t@u1RIa+J*%$ifWhVugMD;4R/mkhW'p%(R%OF*)(%uO6F8==>ohg&Flqb&^6l\K6s3'BU@(Q4.^4V7a>.!j@L,L2BcZY).\/fC%a7?]aa*!-$H:&U2]&N$!F*C12Y1%)dWtSQQAa+_LrG@XS[@HbA@*eFL$/&;b;Sbst:E$Tm>S#LLUmQF%B%'2dX^Jo`6'ke9G$kU(/2[!]V5o9e,CaE_(8F`7^OGQ#]*37X&mSYQmRolTm?WE?^!5G*6=J`;KYk;R$&*M/&diD+$,p<;N5\^_Sks5$6CuD"O'K3%o2=S2_j?Nb\%WILc)]7f(J)*"nLTHCSYf(uYXp%+XX-oNa<2.2kPb"0QP5$X]U[C..iYU)2/d;T;oRaT8"f\V>dG\VS!#/CKnLaVOYL6cle_(VkBm4N"'RDbHRX37cM__mT,VcSktRJBn01HBF.=iI'7Apjr+rK'MMF-#$/Tjq3j;^o)(^Ol8%>NgnsN$ISjg/N.m._sU\^&W=BuWek85^J0UEse?4-V?[ntf7#isW-Yb6;N^omN#SF<e#Gn%G6fbg"Q-(>lKo)9_U@`b'S<XI)n:eR[NVJasK:,FqXTC5DM=,L45LP[dfGc!:+n-Ipmj61Kj[_JsXSh8G)&qS/S#NrS*ANkE6hiO9"Ap-7cAl3m@c`@L+Ss]GKG#I%^SW'#kY`mQk0?YQkG!da2H;?_99$^2.'g+^mSrTMs">emM>_l(Y1sR!H5DF@d0t7t&P!%oP;#i>kegdA]at<BM<arKO=X7LC^nS=d5,5JUSr1OaDIDU(Tgl<XFGf#FROmlG?%OcjD4#-2gb?:sQodml.`]r'I)VM*3.om^H:*+\M,9ZVC2ob,ELH,RMH*!4WOD[6-iX4cZf-Z~>endstream
+Gau0CgMYb*&:Ml+$jnQD(60S:(JB3ZTZJP6G-ac((lLT=KskZ!Kd=5DS](Eu4/=Kp*D'!8/<%NanBB)oEor0)@,m$0s0EEtHidq^":0_'*/JC@0Y)bXaiVjB;BfBRUL7U95o?7"0%ac%/l'c[eDX.)R%tRW!jt`RWZqs*+0IiGk<.ql1tfYH?O<\q_W1qq53Xoupt8A!o'03O9>5J/KjBfh$S"\0;qJ;WMu(,T4jobO$'3:m@>fLAH4s'"Y:k7OPP2jbkZH=P"lS-YK7jun?4]rcs$g1[p*1W;&.b]88TL2J_MJ%u!+ng+&8)6r/Bu](DJMPdNIj`0q%/uN+&$"Ur6ZppM'=j$<!f=nj-"q9+Ed"0]HWrWfai=Njn+W@YrsW#f>Y5\jsjfcA3WCf3Bh];\3I.k/EV4>acVX08g%_;OuS=:bV:"$WJZGMi?D]U7pNsHKO(oh8[/n*?V''>mps@J4":MOeEo;SVikVtA_[]n47"Ksr?r3TiVLL`i2,k5'^%G(]-MqtA%-6S]jnAfW+s#j5GY\)+"OB6elI1"XKGo7nZfAtVmbSp@5Z<2'@pHkmItNhbk$J<&IX$1ee@1lcEBqZEV*]3bn'2V33!49fQM8s4;00!M.EmVBG#h/9P]F,=):gSUNW_+#nCU%]pn.h5'LF`<*TP&PH>H08!(dkEd/Kr=&0o"R/!@D.kbOK'/K2rG=[SF_8^XLIuAMUB1T!\UuA7&^)f%RLXb]PST_D%EV3@u%_1%bK3YM>N/n!dT'&_eY"Fc_<`47lTH3*>aPJ)Zb\Ju'/fJU2q_\kpgrcl(6]boRToUa1,oe!%CHJm?N<9"UHXGGXR)>*Fg=g[V&diE`,ZO)Hr^IM-o=cIaepU+<XS\/W=DLJV##f.cSf70Q>3d!qA=e,._PNf)&XOKOMO_9NNPKI=LsEb$b*@ou3TTK_ATEDt'#AB9R>OE;R8?!6T/XEQ+RJ+LN"04EBMGOQ:V3)G(2#`2c0sha-*c0g4CKif/D5o>!M`ln]sfE1p\mA]ZsjU?;Xd/Hqr8Y[2r_9oRk.Fq7?WIn3*Xl[pqfo'fij4H%r]H#]]8!o@'RL&=:S8iE)VhY1QeOsMf9h@Ghu+`ocXjM\gX/r0ik/Z/k$,['_lZdTsVEDaK^_461r*F?1in2he1X5)L>&^0e3&n2E@5erBKeqOu:jm&t\9ZQ:rf'h5l$pBQ3`1hj*u?'Xn,S'4k;99P88%&\aILdGntYIo@XU>Grhn!gEWoc>W0pI.Wf?,;\`3lurBPU@d*1=Vk7(1&4-&UpMp>))\g1-`LT^Q:7tDL/\<KSp\YESs`8&G^ag!*CI(<B"T_*&=I!AS2D"0$m,meE%><BH-YrS!+Ltj<C/L,>OZj<`,$m.8B*337<)+N6"uHD'oblB^s&Q?NFM#6'Z!!j=kquI"C]=)0\MW!@<01=GET11Nh\9'MDGEU#.(/uLAFj_Tf+FNi]);I1=pX_X+WB0D>,&GrWA2Ch/W~>endstream
 endobj
 26 0 obj
 <<
@@ -212,10 +212,10 @@
 endobj
 28 0 obj
 <<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 556
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 557
 >>
 stream
-GatU.9lErb&A71-pm<9$e[o/B]@RC9JP2h`i.ffi8;J[UKEM5q2k_C36Uci4HC&o`ZSQ)FD@GmHAS'*1Cp%]m!1LG;TS^nj_7Z6Rh\YeZ:I:7):!CJ,0`\Zf2P1e<$cdCUc+Ft,d%K.^_Y['NpNG(FB)A]qh+#Z#S+:'a]2jo/[r%DFrk(#X(Gih:f=W'o=P(_npBcKuPOh,e:YOP`3T6*gBTQdL%!Wnhk6\r_!>[jaLN"$\\*M<(j#Tsg3"A,#YsUfT^ucPT&r\Fl<qCM5jS5D^_hl\7b^^`i4$@gkFf4!&qpYc\Q-^<?Cl*JCb1\F_D??(EU0IHoFW.?rAr.+skS*u_qrY;/.Km"H%^'DEm`qTg*[HI7h#/>*b<ng+4_I;DjQ%j8K,$e];IW0lnhGe5aM/QY1dnP;h,>u$S@#;N#ae5a,+_Xde1T]rlg=hT>^`\2MNoUG;N8ZX?I1_RD+c<JX2,tY>MlW*N.e_MV9hm.XbQ@L@b4I3$q`#=;U^sLYAV"Kf!6kk7n\AD6ia"LbR?N'd<MYdI)gI.0oKC\X<+>~>endstream
+GatUnc#-HG%#+G$$82M0gl=Oh)1.:7)q\-$$[-34OUtA4#n,U]8O>euL5HUH\f6]Yc?FXJlmOuDb6m1P$obX$82,6QSf$T#n8;Q[?f:C>9FrLF+t@)32!gp@q7IQNGZn__k1/*Q5k_F?p7%B:c)PVgqlQ.>-Gd8\q6<N'<Zu=hD3_m8Hj0*XQs];>mm]IiFaDJae0<Jc3p!9*%QJ):*l%n$d>>uoEKH\aLmF'<Q"0+scJmu0]ifbgkK/$#hYG*!8Vn*YZ]-7\"WJ+TGe8E8IcpoaVK4aRjSR0H%52MNV/=t88hNWs4I\mU(tMTDFR'@d@'t]1+5J\OmaIR"CR,ZR$80JNd5RCqkJA'^&8u?M['r>i]@Z8a;u/_!f8&:SbFX;(Q/2t2.#d<N9XE8lO`1ZD2TsF$&:jR&gcbA*8Re\Kq1'/PXK##DOfr'h`N.`D:oe@A96V^Z7)N'2Om(>[*23T#-e'thrS,"m7SHLd<%YhNiMHD@[tL?mM/eKmK$Ra!)K;TR;*#X8A_afYAeLAF]!TU3lNpf5ocj1eTj[BuMAdO.~>endstream
 endobj
 29 0 obj
 <<
@@ -233,10 +233,10 @@
 endobj
 31 0 obj
 <<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 817
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 825
 >>
 stream
-Gau`R968f@&AI`dp.-ul,0@]f\Ift29W)TVHg$;`)FKed5n13C9RhHAZ4."Sg+[Y2%!ZFZe$i+)AjGn(bjX^+i6$\qCBf`M2[:[c!:Lqe][jP-bE7C/,97u)/+E`\W%fQU(7o)A\rHnH6nSt>)D7U:[>/M!@%"rS*VaQgHNNX[V8'po^'i]L\gX$b]&o;'_-uFBLeG?XE]AGU<"Y]82V9XeTn>\k_u*sskVU@C@@jB$8qg1pK#Q/S9_>0[9.6]roJpZ\YB;^[0mcE6V;k*8E=L?Y6l*ku0[bSgS.2@@m6U[+:Xm7bM9[D0#7C<4j3aUb0a"[aO=VB_H]YU#2u%gD$[Y*UOY6P[MB;o_.6ECf9T>?UQ#oelGG)fXELL*9/W,tIF>u].@1-ZuD.'BiUn,?\3IN79L:cJSZt6^e(fQW.SU2+JX-Fbj">@"/90^eNanoHR[k7gBQ:!&Z$ca0L6Jn2$1Zp*:,#2fq^j*Q*[W3$Vmp)nnR&c,YBjC$6>Q+=W/!r`@KG3$#TP4KSbU(-)cf+feA%bj9_IM_"IRjAmD*W<Ha4:$j8J_;A\65u'.MsD#fM*Xb^4.W;noeajX0E#]o?5?%%\`nd\PHrqD6@6I_1ZRSK<SMGH73Wj=)OtQ-;eQKK0S'dK%DVHX'<?4D#W,l284S+bn@G6O%m$Ds,WS<k]*7r%Bbg?F/kW7Mq6/VGCTY5ac!-/jf+bf*^[PHA!1dEIu.PMT:AP,fpRlsF*pX2jp(JKKa"64-[e#*Dlp"\4uWmtl1@81O0I3/hnpDU7>EoEMX;!=/p\oS7T8au/nk1bGO_C=~>endstream
+Gau`RgN)"%&:N^lp5rqk7?W?Vo0s"PAif;^gkS'J8n1bq^/+oG16[df>0()(V@?J5_gjVV7q^/N<f7hlr%Ra>2$UPti,ZV`E<p0l";)2mJ:>l?d9m_=3-`jr;Yh#MTS".Qp<^UgCdZg;&B)Z/JQ'J*F/J*-=t5nPktFV+3sZlN\B*oQnZs1OFZ(Dc/r$(1j%8`B=Te2.>=oD&:K3V;52h+Z',Oi^a?l8"m"iqUg4["2\!.5t=9OE@fbjB.eKpI37XBt2l%)$Y@a*Yk?0O1C8Qp];,`r9$;k3(.EL[E6cHT\l'P?VRXAJ'nDLmC'=%%-Y7U1RJ,tJdPr>eFX5WAId["TM2)l*p-MrMdmH-VGn#U[P`2b(AgUK^RR5-gY>)_3Pm<cc,<g+5-i`ps5UmH`<RiGhN*?<P>9@5GL#IC'l5Qfjlc'tT;l(uIb80\>nMd`KW.i!R4kK(-qrb(k[fNCdRFF[X^A7R<^]ROp3j0*dM[@JA'S>/\usm)X<,f@],Ef4i&H$7>,\c5o[n-t,:O`dA9ZpCQYe1)1ldO6dn_bHql;Z6!%^^<.M6l2>A2W2j<bE8oH2fZ3EZ`9_T70%/r4X=LcRMU_7@e0,m,s$"CTC,DpYe6%\:<=B2mZH==fRNHI<;nZ2.QIlXr1oCJ[*0j:2H;;$KX^c**INM8(C9I$k3^Wg1H?,g:O]p6o9^9j$U*gpafb<0@rSJtU\rr>jEjE97ol>m\_G0kYJa;r=?ZO96%+Lj,];dbJTOS3BoKSt,jW/hpc!BbJ1@WgM"I7c[;TE2ND^ZDR.U1[u=@[TVeqRL\'("k7`\1KfUr*~>endstream
 endobj
 xref
 0 32
@@ -266,16 +266,16 @@
 0000371924 00000 n 
 0000372208 00000 n 
 0000372309 00000 n 
-0000373907 00000 n 
-0000375591 00000 n 
-0000376043 00000 n 
-0000376690 00000 n 
-0000377240 00000 n 
-0000377710 00000 n 
+0000373896 00000 n 
+0000375580 00000 n 
+0000376032 00000 n 
+0000376680 00000 n 
+0000377230 00000 n 
+0000377700 00000 n 
 trailer
 <<
 /ID 
-[<8576d3492d1e783b7da52a0f2ceb1828><8576d3492d1e783b7da52a0f2ceb1828>]
+[<a5464be9d6a7776348217da4d36ba9da><a5464be9d6a7776348217da4d36ba9da>]
 % ReportLab generated PDF document -- digest (http://www.reportlab.com)
 
 /Info 23 0 R
@@ -283,5 +283,5 @@
 /Size 32
 >>
 startxref
-378618
+378616
 %%EOF