Mercurial > repos > bgruening > erga_ear
changeset 2:a34826ae0a73 draft
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 | 3dd6be0cd8dd |
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 = " " * 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 = " " * 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