From cb6faef8fd2379ce3827b638e776a0c18d42c75d Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Sun, 26 Oct 2025 19:15:06 +0300 Subject: [PATCH 01/20] Performance improvements: to the tool and to the ruleset --- deepsecrets/cli.py | 15 +- deepsecrets/config.py | 17 +- deepsecrets/core/model/file.py | 10 + deepsecrets/core/model/finding.py | 24 +- deepsecrets/core/modes/iscan_mode.py | 196 ++- deepsecrets/core/utils/file_analyzer.py | 119 +- deepsecrets/core/utils/lifecycle_hooks.py | 45 + deepsecrets/core/utils/progress.py | 70 ++ deepsecrets/core/utils/sarif_helper.py | 6 +- deepsecrets/rules/regexes.json | 15 +- deepsecrets/scan_modes/cli.py | 22 +- poetry.lock | 1347 +++++++++++---------- pyproject.toml | 24 +- 13 files changed, 1090 insertions(+), 820 deletions(-) create mode 100644 deepsecrets/core/utils/lifecycle_hooks.py create mode 100644 deepsecrets/core/utils/progress.py diff --git a/deepsecrets/cli.py b/deepsecrets/cli.py index c54ac48..dd5df06 100644 --- a/deepsecrets/cli.py +++ b/deepsecrets/cli.py @@ -26,7 +26,7 @@ TaskProgressColumn, TimeRemainingColumn, ) -from rich.table import Table +from rich.table import Table, Column from rich import box from rich.text import Text from rich.align import Align @@ -43,13 +43,14 @@ class ReturnCodes: progress_bar = Progress( SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - TextColumn("[bold red]{task.fields[findings]}", justify="left"), + TextColumn("[progress.description]{task.description}", table_column=Column(max_width=60, no_wrap=True)), + TextColumn("[bold blue]{task.fields[size]}"), BarColumn(bar_width=None), TaskProgressColumn(), TimeRemainingColumn(), + TextColumn("[bold red]{task.fields[findings]}", justify="right"), console=console, - refresh_per_second=10, + refresh_per_second=5, expand=True, ) @@ -182,7 +183,7 @@ def _build_argparser(self) -> None: type=str, default='spawn', choices=['fork', 'spawn', 'forkserver'], - help='Experimental: control the multiprocessing context\n', + help='Control the multiprocessing context\n', ) parser.add_argument('--outfile', required=True, type=str) @@ -197,7 +198,7 @@ def _build_argparser(self) -> None: parser.add_argument( '--disable-masking', action='store_true', - help='Secrets are rendered masked inside reports by default.\n' + help='Secrets are rendered masked inside the report by default.\n' 'Use this flag if you want to render found secrets in plaintext.', ) @@ -299,7 +300,7 @@ def start(self) -> int: # pragma: nocover table.add_column(justify='right') table.add_row( Align('Files (Tokens) Processed', vertical='middle'), - f'{str(len(mode.filepaths))} ({mode.get_total_tokens_processed()})', + f'{str(len(mode.filepaths))} ({mode.stats.tokens_processed})', ) table.add_row(Align('Elapsed', vertical='middle'), f'{(finish_time-startup_time).total_seconds():.1f}s') findings_line_color = '[bold red]' if len(findings) > 0 else '[bold green]' diff --git a/deepsecrets/config.py b/deepsecrets/config.py index 263b43f..d9c6ae3 100644 --- a/deepsecrets/config.py +++ b/deepsecrets/config.py @@ -11,9 +11,11 @@ FALLBACK_PROCESS_COUNT = 4 SCANNER_NAME = "DeepSecrets" -SCANNER_VERSION = "1.4.0" +SCANNER_VERSION = "1.5.0" SCANNER_URL = "https://github.com/ntoskernel/deepsecrets" +MAX_LINE_LENGTH_FOR_CONTEXT = 300 + class Output(BaseModel): type: str @@ -72,14 +74,21 @@ def set_process_count(self, count: int) -> None: count = CpuHelper().get_limit() if count > 0: self.process_count = count - console.print(f'[bold yellow]:warning: Process count[/bold yellow] was not specified. Setting it to [bold magenta]{self.process_count}[/bold magenta] based on the [cyan]machine\'s CPU config[/cyan]') + console.print( + f'[bold yellow]:warning: Process count[/bold yellow] was not specified. Setting it to [bold magenta]{self.process_count}[/bold magenta] based on the [cyan]machine\'s CPU config[/cyan]' + ) return self.process_count = FALLBACK_PROCESS_COUNT - console.print(f'[bold yellow]:warning:[/bold yellow]: Process count was not specified. Setting it to [bold magenta]{self.process_count}[/bold magenta] as a [yellow]fallback[/yellow]') + console.print( + f'[bold yellow]:warning:[/bold yellow]: Process count was not specified. Setting it to [bold magenta]{self.process_count}[/bold magenta] as a [yellow]fallback[/yellow]' + ) def set_global_exclusion_paths(self, paths: List[str]) -> None: for path in paths: + if path == 'disable': + continue + if not path_exists(path): raise FileNotFoundException(f'global_exclusion_path does not exist ({path})') self.global_exclusion_paths.append(path) @@ -102,4 +111,4 @@ def _validate_paths(self, paths: List[str]) -> None: return -config = Config() \ No newline at end of file +config = Config() diff --git a/deepsecrets/core/model/file.py b/deepsecrets/core/model/file.py index 3e7f75b..9e72ec2 100644 --- a/deepsecrets/core/model/file.py +++ b/deepsecrets/core/model/file.py @@ -123,5 +123,15 @@ def get_column_number(self, position: int) -> int: line_number = self.get_line_number(position=position) return position - self.line_offsets[line_number][0] + def is_one_liner(self) -> bool: + return len(self.line_offsets) == 1 + + def get_line_length(self, line_number: int) -> int: + offsets = self.line_offsets.get(line_number) + return offsets[1] - offsets[0] + + def get_line_offset(self, line_number: int) -> int: + return self.line_offsets.get(line_number)[0] + def __repr__(self) -> str: # pragma: no cover return self.path diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index 76005fa..24513f1 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -9,7 +9,7 @@ from deepsecrets.core.model.rules.rule import Rule import sarif_om as om -from deepsecrets.config import SCANNER_NAME, SCANNER_URL, SCANNER_VERSION +from deepsecrets.config import MAX_LINE_LENGTH_FOR_CONTEXT, SCANNER_NAME, SCANNER_URL, SCANNER_VERSION class Finding(BaseModel): @@ -17,6 +17,7 @@ class Finding(BaseModel): rules: List[Rule] = Field(default=[]) detection: str full_line: Optional[str] = Field(default=None) + full_line_partial: bool = Field(default=False) linum: Optional[int] = Field(default=None) start_pos: int end_pos: int @@ -40,9 +41,20 @@ def map_on_file(self, relative_start: int, file: Optional['File'] = None) -> Non self.linum = self.file.get_line_number(self.end_pos) if not self.full_line: - self.full_line = self.file.get_line_contents(self.linum) + self._populate_full_line() + self._mapped_on_file = True + def _populate_full_line(self): + self.full_line_partial = False + if self.file.get_line_length(self.linum) <= MAX_LINE_LENGTH_FOR_CONTEXT: + self.full_line = self.file.get_line_contents(self.linum) + return + + self.full_line_partial = True + return + # TODO: boundaries = self._get_context_boundaries() + def get_reason(self) -> str: if self.final_rule is None: self.choose_final_rule() @@ -138,7 +150,7 @@ def from_list(cls, list: List[Finding], disable_masking: bool = False) -> Dict[s return resp @classmethod - def dojo_sarif_from_list(cls, list: List[Finding], disable_masking: bool = False) -> om.SarifLog: + def dojo_sarif_from_list(cls, list: List[Finding], disable_masking: bool = False) -> om.SarifLog: # type: ignore report = om.SarifLog( schema_uri='https://json.schemastore.org/sarif-2.1.0.json', @@ -213,13 +225,15 @@ def dojo_sarif_from_list(cls, list: List[Finding], disable_masking: bool = False class FindingApiModel(BaseModel): - line: str + line: Optional[str] string: str line_number: int rule: str reason: str confidence: int fingerprint: str + file_start_offset: int + file_end_offset: int @classmethod def from_finding(cls, finding: Finding) -> FindingApiModel: @@ -232,6 +246,8 @@ def from_finding(cls, finding: Finding) -> FindingApiModel: reason=finding.get_reason(), confidence=finding.final_rule.confidence, fingerprint=finding.get_fingerprint(), + file_start_offset=finding.start_pos, + file_end_offset=finding.end_pos, ) diff --git a/deepsecrets/core/modes/iscan_mode.py b/deepsecrets/core/modes/iscan_mode.py index 75382c4..95c348c 100644 --- a/deepsecrets/core/modes/iscan_mode.py +++ b/deepsecrets/core/modes/iscan_mode.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from multiprocessing.pool import AsyncResult import regex as re @@ -19,6 +20,24 @@ from deepsecrets.core.utils.fs import get_abspath from rich.progress import Progress as ProgressBar +from rich.live import Live +from rich.text import Text + + +@dataclass +class FileJob: + internal_id: int + name: str + pb_task_id: Optional[int] + result: AsyncResult + + +@dataclass +class Stats: + total_files: int = 0 + finished: int = 0 + tokens_processed: int = 0 + total_findings: int = 0 class ScanMode: @@ -30,20 +49,16 @@ class ScanMode: rulesets: Dict[str, List] engines_enabled: Dict[Type, bool] - task_reporter: DictProxy + active_task_reporter: DictProxy progress_bar: ProgressBar - file_jobs: List[AsyncResult] - _mp_manager = None + file_results: List[AsyncResult] + file_jobs: Dict[int, FileJob] - def get_total_tokens_processed(self): - result = 0 - for _, tr in self.task_reporter.items(): - result += int(tr.get('total_tokens')) - return result + _mp_manager = None def __init__(self, config: Config, pool_engine: Optional[Any] = None) -> None: - console.print('[*] Looking for applicable files...') + console.print('[*] Looking for applicable files...', end='') console.line() if pool_engine is None: self.pool_engine = get_context(config.mp_context).Pool @@ -51,11 +66,13 @@ def __init__(self, config: Config, pool_engine: Optional[Any] = None) -> None: self.pool_engine = pool_engine self._mp_manager = Manager() - self.task_reporter = self._mp_manager.dict({}) + self.active_task_reporter = self._mp_manager.dict({}) self.progress_bar = None self.config = config - self.file_jobs = [] + self.file_results = [] + self.file_jobs = {} + self.stats = Stats() self.filepaths = self._get_files_list() self.prepare_for_scan() @@ -71,32 +88,67 @@ def _get_process_count_for_runner(self) -> int: return 0 return limit if file_count >= limit else file_count - def refresh_progress_bar(self, overall_progress_task, n_finished, final=False): - if self.task_reporter is None: + def refresh_jobs_progress_bars(self): + if self.active_task_reporter is None: return - total_findings = 0 - for task_id, current_state in self.task_reporter.items(): + tasks_to_remove = [] + for internal_task_id, current_state in self.active_task_reporter.items(): + job = self.file_jobs.get(internal_task_id) + if job is None: + raise Exception() + + started: bool = current_state.get('started') + finished: bool = current_state.get('finished') + size: str = current_state.get('file_size') + + if started is True and finished is False and job.pb_task_id is None: + job.pb_task_id = self.progress_bar.add_task( + f'[{job.internal_id}] {job.name.split("/")[-1]}', + findings='0', + size='| ? Kb', + ) + + processed = current_state.get('processed', 0) + findings = current_state.get('findings', 0) + + if finished is True: + self.stats.tokens_processed += processed + self.stats.total_findings += findings + + tasks_to_remove.append(internal_task_id) + if job.pb_task_id is not None: + try: + self.progress_bar.remove_task(job.pb_task_id) + except Exception: + pass + continue + total = current_state.get('total_tokens') - processed = current_state.get('processed') - started = current_state.get('started') - finished = current_state.get('finished') - visible = started is True and finished is False - findings = current_state.get('findings') - total_findings += findings - # update the progress bar for this task: - self.progress_bar.update( - task_id, - completed=processed, - total=total, - visible=visible if not final else False, - findings=f'FINDINGS: {findings}', - ) + + if job.pb_task_id is not None: + self.progress_bar.update( + job.pb_task_id, + completed=processed, + total=total, + visible=True, + findings=findings, + size=f'| {size}', + ) + + for to_remove in tasks_to_remove: + self.stats.finished += 1 + self.active_task_reporter.pop(to_remove) + + def refresh_overall_progress_bar(self, pb_task_id): + if pb_task_id is None: + return + self.progress_bar.update( - overall_progress_task, - completed=n_finished, - total=len(self.file_jobs), - findings=f'RAW FINDINGS (BEFORE FILTERING): {total_findings}', + pb_task_id, + completed=self.stats.finished, + total=self.stats.total_files, + findings=f'FOUND: {self.stats.total_findings}', ) def run(self) -> List[Finding]: @@ -106,38 +158,43 @@ def run(self) -> List[Finding]: if proc_count == 0: return final - if self.progress_bar is not None: - overall_progress_task = self.progress_bar.add_task( - "[green bold]OVERALL PROGRESS", visible=True, findings='RAW FINDINGS (BEFORE FILTERING): 0' - ) + overall_progress_task = self.progress_bar.add_task( + "[green bold]OVERALL PROGRESS\n", visible=True, findings='FOUND: 0', size='' + ) if PROFILER_ON: for file in self.filepaths: - task_id = self.progress_bar.add_task(file, findings='FINDINGS: 0') final.extend( - self._per_file_analyzer(file=file, bundle=bundle, task_id=task_id, task_reporter=self.task_reporter) + self._per_file_analyzer(file=file, bundle=bundle, task_id=0, task_reporter=self.task_reporter) ) else: with self.pool_engine(processes=proc_count) as pool: + tid = 0 for file in self.filepaths: - task_id = self.progress_bar.add_task(file, findings='FINDINGS: 0', visible=False) + tid += 1 # runnable = partial(pool_wrapper, bundle, self._per_file_analyzer, self.task_reporter) - self.file_jobs.append( - pool.apply_async( - pool_wrapper, - (bundle, self._per_file_analyzer, task_id, self.task_reporter, file), - ) + result = pool.apply_async( + pool_wrapper, + (bundle, self._per_file_analyzer, tid, self.active_task_reporter, file), + ) + self.file_results.append(result) + self.file_jobs[tid] = FileJob( + name=file, + internal_id=tid, + result=result, + pb_task_id=None, ) - - while (n_finished := sum([job.ready() for job in self.file_jobs])) < len(self.file_jobs): - self.refresh_progress_bar(overall_progress_task, n_finished) pool.close() - # final refresh - self.refresh_progress_bar(overall_progress_task, 100, final=True) + self.stats.total_files = len(self.file_jobs.keys()) + while self.stats.finished < self.stats.total_files: + self.refresh_jobs_progress_bars() + self.refresh_overall_progress_bar(overall_progress_task) + pool.join() + self.progress_bar.stop() - for job_result in self.file_jobs: + for job_result in self.file_results: file_findings = job_result.get() if file_findings is None or len(file_findings) == 0: continue @@ -164,21 +221,30 @@ def _get_files_list(self) -> List[str]: excl_paths_builder.with_rules_from_file(path) self.path_exclusion_rules = excl_paths_builder.rules + with Live(console=console, refresh_per_second=5) as live: + + total_files = 0 + skipped = 0 + for fpath, _, files in os.walk(get_abspath(self.config.workdir_path)): + for filename in files: + live.update(Text(text=f'Found {total_files} files, {skipped} will be skipped')) + total_files += 1 + full_path = os.path.join(fpath, filename) + rel_path = full_path.replace(f'{self.config.workdir_path}/', '') + if not self._path_included(rel_path): + skipped += 1 + continue + + if not self._size_check(full_path): + skipped += 1 + ''' + console.print( + f'[bold yellow]:warning: {rel_path}[/bold yellow]: File size exceeds [magenta]--max-file-path[/magenta] of {self.config.max_file_size} bytes and will be [bold]skipped[/bold]' + ) + ''' + continue - for fpath, _, files in os.walk(get_abspath(self.config.workdir_path)): - for filename in files: - full_path = os.path.join(fpath, filename) - rel_path = full_path.replace(f'{self.config.workdir_path}/', '') - if not self._path_included(rel_path): - continue - - if not self._size_check(full_path): - console.print( - f'[bold yellow]:warning: {rel_path}[/bold yellow]: File size exceeds [magenta]--max-file-path[/magenta] of {self.config.max_file_size} bytes and will be [bold]skipped[/bold]' - ) - continue - - flist.append(full_path) + flist.append(full_path) return flist diff --git a/deepsecrets/core/utils/file_analyzer.py b/deepsecrets/core/utils/file_analyzer.py index 7652e6b..4476ef3 100644 --- a/deepsecrets/core/utils/file_analyzer.py +++ b/deepsecrets/core/utils/file_analyzer.py @@ -1,15 +1,15 @@ -from multiprocessing import RLock -from multiprocessing.pool import Pool -from typing import Any, Dict, List, Optional, Type +from typing import Any, Dict, List, Optional from pydantic import BaseModel, ConfigDict +from deepsecrets.core.utils.lifecycle_hooks import FileLifecycleHooks from deepsecrets.core.utils.log import logger from deepsecrets.core.engines.iengine import IEngine from deepsecrets.core.model.file import File from deepsecrets.core.model.finding import Finding from deepsecrets.core.model.token import Token from deepsecrets.core.tokenizers.itokenizer import Tokenizer +from deepsecrets.core.utils.progress import FileProgress class EngineWithTokenizer(BaseModel): @@ -19,119 +19,59 @@ class EngineWithTokenizer(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) -class Progress: - started: bool - finished: bool - total_tokens: int - processed_count: int - - findings: int - - def __init__(self): - self.started = False - self.finished = False - self.total_tokens = 0 - self.processed_count = 0 - self.findings = 0 - - def on_tokenization_finished(self, token_count: int): - self.total_tokens += token_count - - def on_token_processing_start(self): - self.started = True - self.processed_count += 1 - - def on_finish(self): - self.started = False - self.finished = True - - def add_findings_count(self, count: int): - self.findings += count - - def report(self): - return { - 'started': self.started, - 'finished': self.finished, - 'total_tokens': self.total_tokens, - 'processed': self.processed_count, - 'findings': self.findings, - } - - class FileAnalyzer: file: File engine_tokenizers: List[EngineWithTokenizer] - tokens: Dict[Type, List[Token]] - pool_class: Type - progress: Progress + tokens: Dict[Tokenizer, List[Token]] + progress: FileProgress task_reporter: Any - task_id: str - - def __init__(self, file: File, pool_class: Optional[Type] = None): - if pool_class is not None: - self.pool_class = Pool - else: - self.pool_class = pool_class + task_id: Optional[int] + def __init__(self, file: File): self.engine_tokenizers = [] self.file = file self.tokens = {} - self.tokenizers_lock = RLock() - self.progress = Progress() + self.progress = FileProgress(tokenizers_total=len(self.engine_tokenizers)) + self.lifecycle = FileLifecycleHooks(reporter=None, task_id=None, progress=self.progress) self.task_reporter = None self.task_id = None + self.progress.set_file_size(self.file.length) def attach_global_task_reporter(self, task_reporter, task_id): self.task_reporter = task_reporter self.task_id = task_id - self.global_report() - - def global_report(self): - if self.task_reporter is None: - return - - self.task_reporter[self.task_id] = self.progress.report() + self.lifecycle.task_id = self.task_id + self.lifecycle.reporter = self.task_reporter def add_engine(self, engine: IEngine, tokenizers: List[Tokenizer]) -> None: for tokenizer in tokenizers: self.engine_tokenizers.append(EngineWithTokenizer(engine=engine, tokenizer=tokenizer)) + self.progress.tokenizers_total += 1 - def process(self, threaded: bool = False) -> List[Finding]: + def process(self) -> List[Finding]: results: List[Finding] = [] - - if threaded: # pragma: nocover - with self.pool_class(2) as pool: - engine_results = pool.imap(self._run_engine, self.engine_tokenizers) - pool.close() - pool.join() - - if engine_results is None: - return results - - for er in engine_results: - if not er: - continue - results.extend(er) - - else: + self.lifecycle.on_start() + try: for et in self.engine_tokenizers: results.extend(self._run_engine(et)) + except Exception: + pass + self.lifecycle.on_finish() return results def _run_engine(self, et: EngineWithTokenizer) -> List[Finding]: results: List[Finding] = [] processed_values: Dict[int, bool] = {} - with self.tokenizers_lock: - if et.tokenizer not in self.tokens: - self.tokens[et.tokenizer] = et.tokenizer.tokenize(self.file) - self.progress.on_tokenization_finished(len(self.tokens[et.tokenizer])) + if et.tokenizer not in self.tokens: + self.tokens[et.tokenizer] = et.tokenizer.tokenize(self.file) + self.progress.on_tokenization_finished(token_count=len(self.tokens[et.tokenizer])) tokens: List[Token] = self.tokens[et.tokenizer] for token in tokens: - self.on_token_processing_start(token) + self.lifecycle.on_token_processing_start(token) is_known_content = processed_values.get(token.val_hash()) if is_known_content is not None and is_known_content is False: @@ -146,19 +86,10 @@ def _run_engine(self, et: EngineWithTokenizer) -> List[Finding]: results.append(finding) processed_values[token.val_hash()] = True - self.on_token_processing_end(len(findings)) + self.lifecycle.on_token_processing_end(len(findings)) except Exception as e: - logger.exception('Unable to process token') + logger.exception('Unable to process token', extra={'info': e}) continue - self.progress.on_finish() return results - - def on_token_processing_start(self, token: Token): - self.progress.on_token_processing_start() - self.global_report() - - def on_token_processing_end(self, findings_count: int): - self.progress.add_findings_count(findings_count) - self.global_report() diff --git a/deepsecrets/core/utils/lifecycle_hooks.py b/deepsecrets/core/utils/lifecycle_hooks.py new file mode 100644 index 0000000..3a7f7bc --- /dev/null +++ b/deepsecrets/core/utils/lifecycle_hooks.py @@ -0,0 +1,45 @@ +from typing import Optional +from deepsecrets.core.model.token import Token +from deepsecrets.core.utils.progress import FileProgress, Progress +from multiprocessing.managers import DictProxy + + +class LifecycleHooks: + progress: Progress + reporter: DictProxy + task_id: int + + def __init__(self, task_id: int, progress: Progress, reporter: DictProxy) -> None: + self.task_id = task_id + self.progress = progress + self.reporter = reporter + + def on_start(self): + self.progress.on_start() + self._report() + + def on_finish(self, child_report: Optional[dict] = None): + self.progress.on_finish() + self._report(child_report) + + def _report(self, child_report: Optional[dict] = None): + if self.reporter is None: + return + + self.reporter[self.task_id] = self.progress.report(child_report) + + +class JobLifecycleHooks(LifecycleHooks): + pass + + +class FileLifecycleHooks(LifecycleHooks): + progress: FileProgress + + def on_token_processing_start(self, token: Token): + self.progress.on_token_processing_start() + self._report() + + def on_token_processing_end(self, findings_count: int): + self.progress.add_findings_count(findings_count) + self._report() diff --git a/deepsecrets/core/utils/progress.py b/deepsecrets/core/utils/progress.py new file mode 100644 index 0000000..db724a5 --- /dev/null +++ b/deepsecrets/core/utils/progress.py @@ -0,0 +1,70 @@ +from typing import Optional + + +class Progress: + + started: bool + finished: bool + + def __init__(self) -> None: + self.started = False + self.finished = False + + def on_start(self): + self.started = True + + def on_finish(self): + self.finished = True + self.started = False + + def report(self, child_report: Optional[dict] = None): + child_report = child_report if child_report is not None else dict() + return child_report | { + 'started': self.started, + 'finished': self.finished, + } + + +class FileProgress(Progress): + total_tokens: int + processed_count: int + findings: int + file_size: str + + tokenizers_total: int + tokenizers_done: int + + def __init__(self, tokenizers_total: int, tokenizers_done: int = 0): + super().__init__() + self.total_tokens = 0 + self.processed_count = 0 + self.findings = 0 + + self.tokenizers_total = tokenizers_total + self.tokenizers_done = tokenizers_done + + def on_tokenization_finished(self, token_count: int): + self.total_tokens += token_count + self.tokenizers_done += 1 + + def on_token_processing_start(self): + self.processed_count += 1 + + def add_findings_count(self, count: int): + self.findings += count + + def set_file_size(self, file_size: int): + self.file_size = f'{round(file_size / 1024)} Kb' + + def report(self, child_report: Optional[dict] = None): + if self.tokenizers_total > 0 and self.tokenizers_done > 0: + total_tokens = self.total_tokens / (self.tokenizers_done / self.tokenizers_total) + else: + total_tokens = self.total_tokens + + return super().report() | { + 'total_tokens': total_tokens, + 'processed': self.processed_count, + 'findings': self.findings, + 'file_size': self.file_size, + } diff --git a/deepsecrets/core/utils/sarif_helper.py b/deepsecrets/core/utils/sarif_helper.py index ab6cd54..be76746 100644 --- a/deepsecrets/core/utils/sarif_helper.py +++ b/deepsecrets/core/utils/sarif_helper.py @@ -1,7 +1,5 @@ import sarif_om as om -MAX_LINE_LENGTH_FOR_CONTEXT = 300 - class SarifHelper: @@ -12,7 +10,8 @@ def get_context_region_for_finding(cls, finding: 'Finding', masking: bool = True end_column = finding.file.get_column_number(position=finding.end_pos) boundaries = cls._get_context_boundaries(finding, start_column, end_column) - snippet = finding.full_line[boundaries[0] : boundaries[1]] + base_offset = finding.file.get_line_offset(finding.linum) + snippet = finding.file.content[base_offset + boundaries[0] : base_offset + boundaries[1]] if masking: snippet = cls._mask(snippet=snippet, detection=finding.detection) @@ -61,4 +60,5 @@ def _mask(cls, snippet: str, detection: str): return snippet.replace(detection, masked_detection) +from deepsecrets.config import MAX_LINE_LENGTH_FOR_CONTEXT from deepsecrets.core.model.finding import Finding diff --git a/deepsecrets/rules/regexes.json b/deepsecrets/rules/regexes.json index 8b48d37..0d95924 100644 --- a/deepsecrets/rules/regexes.json +++ b/deepsecrets/rules/regexes.json @@ -39,13 +39,14 @@ "id": "S7", "name": "Facebook Oauth", "confidence": 9, - "pattern": "facebook.*['|\"][0-9a-f]{32}['|\"]" + "pattern": "facebook.{0,100}?['\"][0-9a-f]{32}['\"]" + }, { "id": "S8", "name": "Twitter Oauth", "confidence": 9, - "pattern": "twitter.*['|\"][0-9a-zA-Z]{35,44}['|\"]" + "pattern": "twitter.{0,100}?['|\"][0-9a-zA-Z]{35,44}['|\"]" }, { "id": "S10", @@ -57,7 +58,7 @@ "id": "S12", "name": "Heroku API Key", "confidence": 9, - "pattern": "heroku.*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}" + "pattern": "heroku.*?[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}" }, { "id": "S17", @@ -145,7 +146,7 @@ "id": "S31", "name": "Github App Token", "confidence": 9, - "pattern": "\b((?:ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,255})\b" + "pattern": "\\b((?:ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,255})\\b" }, { "id": "S32", @@ -165,5 +166,11 @@ "name": "Gitlab Personal Access Token", "confidence": 9, "pattern": "glpat-[0-9a-zA-Z\\-\\_]{20}" + }, + { + "id": "S35", + "name": "AWS Access Key ID", + "confidence": 9, + "pattern": "\\b(A3T[A-Z0-9]|AKIA|AGPA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\\b" } ] \ No newline at end of file diff --git a/deepsecrets/scan_modes/cli.py b/deepsecrets/scan_modes/cli.py index 973c0c6..7b2b0bd 100644 --- a/deepsecrets/scan_modes/cli.py +++ b/deepsecrets/scan_modes/cli.py @@ -15,8 +15,10 @@ from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.core.tokenizers.full_content import FullContentTokenizer from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from deepsecrets.core.utils.lifecycle_hooks import JobLifecycleHooks from deepsecrets.core.utils.log import logger from deepsecrets.core.utils.file_analyzer import FileAnalyzer +from deepsecrets.core.utils.progress import Progress class CliScanMode(ScanMode): @@ -50,6 +52,14 @@ def analyzer_bundle(self) -> DotWiz: @staticmethod def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, task_reporter: Optional[Any] = None) -> List[Finding]: # type: ignore + progress = Progress() + lifecycle = JobLifecycleHooks( + task_id=task_id, + progress=progress, + reporter=task_reporter, + ) + + lifecycle.on_start() if logger.level == logging.DEBUG: pass @@ -58,8 +68,15 @@ def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, ta if not isinstance(file, str): raise Exception('Filepath as str expected') - file = File(path=file, relative_path=file.replace(f'{bundle.workdir}/', '')) + try: + file = File(path=file, relative_path=file.replace(f'{bundle.workdir}/', '')) + except Exception as e: + logger.error('Unable to open the file', extra={'message': e}) + lifecycle.on_finish() + return results + if file.length == 0: + lifecycle.on_finish() return results file_analyzer = FileAnalyzer(file) @@ -90,11 +107,12 @@ def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, ta file_analyzer.add_engine(semantic_engine, [lex]) try: - results = file_analyzer.process(threaded=False) + results = file_analyzer.process() except Exception as e: logger.exception(e) if PROFILER_ON: pass + lifecycle.on_finish() return results diff --git a/poetry.lock b/poetry.lock index 9cc6ab5..4aea9aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,16 +1,15 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aenum" -version = "3.1.15" +version = "3.1.16" description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "aenum-3.1.15-py2-none-any.whl", hash = "sha256:27b1710b9d084de6e2e695dab78fe9f269de924b51ae2850170ee7e1ca6288a5"}, - {file = "aenum-3.1.15-py3-none-any.whl", hash = "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288"}, - {file = "aenum-3.1.15.tar.gz", hash = "sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559"}, + {file = "aenum-3.1.16-py2-none-any.whl", hash = "sha256:7810cbb6b4054b7654e5a7bafbe16e9ee1d25ef8e397be699f63f2f3a5800433"}, + {file = "aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf"}, ] [[package]] @@ -27,54 +26,46 @@ files = [ [[package]] name = "attrs" -version = "25.1.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] - [[package]] name = "black" -version = "25.1.0" +version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, + {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, + {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, + {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, + {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, + {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, + {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, + {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, + {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, + {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, + {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, + {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, + {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, + {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, + {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, + {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, + {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, + {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, + {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, + {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, + {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, + {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, + {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, ] [package.dependencies] @@ -83,6 +74,7 @@ mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" +pytokens = ">=0.1.10" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} @@ -122,75 +114,116 @@ markers = {dev = "platform_system == \"Windows\"", test = "sys_platform == \"win [[package]] name = "coverage" -version = "7.6.12" +version = "7.10.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, - {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, - {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, - {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, - {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, - {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, - {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, - {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, - {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, - {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, - {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, - {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, - {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, - {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, - {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, - {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, - {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, - {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, - {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, + {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, + {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, + {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, + {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, + {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, + {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, + {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, + {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, + {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, + {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, + {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, + {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, + {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, + {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, + {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, + {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, + {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, + {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, + {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, + {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, + {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, + {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, + {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, + {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, ] [package.dependencies] @@ -216,30 +249,33 @@ pyheck = "0.1.5" [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["test"] markers = "python_version < \"3.11\"" files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["test"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -261,21 +297,21 @@ pbr = "*" [[package]] name = "jsonpickle" -version = "4.0.1" +version = "4.1.1" description = "jsonpickle encodes/decodes any Python object to/from JSON" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "jsonpickle-4.0.1-py3-none-any.whl", hash = "sha256:2973c0b0d988c6792ed6c446fa582c48352e79c2880fa2c013f1abde15905555"}, - {file = "jsonpickle-4.0.1.tar.gz", hash = "sha256:b5336144d902958b92cb08bc1e76bfa47199b8afd454303693894defd2fa50c5"}, + {file = "jsonpickle-4.1.1-py3-none-any.whl", hash = "sha256:bb141da6057898aa2438ff268362b126826c812a1721e31cf08a6e142910dc91"}, + {file = "jsonpickle-4.1.1.tar.gz", hash = "sha256:f86e18f13e2b96c1c1eede0b7b90095bbb61d99fedc14813c44dc2f361dbbae1"}, ] [package.extras] cov = ["pytest-cov"] dev = ["black", "pyupgrade"] docs = ["furo", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -packaging = ["build", "setuptools (>=61.2)", "setuptools-scm[toml] (>=6.0)", "twine"] +packaging = ["build", "setuptools (>=61.2)", "setuptools_scm[toml] (>=6.0)", "twine"] testing = ["PyYAML", "atheris (>=2.3.0,<2.4.0) ; python_version < \"3.12\"", "bson", "ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=6.0,!=8.1.*)", "pytest-benchmark", "pytest-benchmark[histogram]", "pytest-checkdocs (>=1.2.3)", "pytest-enabler (>=1.0.1)", "pytest-ruff (>=0.2.1)", "scikit-learn", "scipy (>=1.9.3) ; python_version > \"3.10\"", "scipy ; python_version <= \"3.10\"", "simplejson", "sqlalchemy", "ujson"] [[package]] @@ -293,27 +329,6 @@ files = [ [package.dependencies] Pygments = ">=2.12.0" -[[package]] -name = "linkify-it-py" -version = "2.0.3" -description = "Links recognition library with FULL unicode support." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, - {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, -] - -[package.dependencies] -uc-micro-py = "*" - -[package.extras] -benchmark = ["pytest", "pytest-benchmark"] -dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] -doc = ["myst-parser", "sphinx", "sphinx-book-theme"] -test = ["coverage", "pytest", "pytest-cov"] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -327,8 +342,6 @@ files = [ ] [package.dependencies] -linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""} -mdit-py-plugins = {version = "*", optional = true, markers = "extra == \"plugins\""} mdurl = ">=0.1,<1.0" [package.extras] @@ -341,26 +354,6 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] -[[package]] -name = "mdit-py-plugins" -version = "0.4.2" -description = "Collection of plugins for markdown-it-py" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, - {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, -] - -[package.dependencies] -markdown-it-py = ">=1.0.0,<4.0.0" - -[package.extras] -code-style = ["pre-commit"] -rtd = ["myst-parser", "sphinx-book-theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - [[package]] name = "mdurl" version = "0.1.2" @@ -375,113 +368,153 @@ files = [ [[package]] name = "mmh3" -version = "5.1.0" +version = "5.2.0" description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "mmh3-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eaf4ac5c6ee18ca9232238364d7f2a213278ae5ca97897cafaa123fcc7bb8bec"}, - {file = "mmh3-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:48f9aa8ccb9ad1d577a16104834ac44ff640d8de8c0caed09a2300df7ce8460a"}, - {file = "mmh3-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4ba8cac21e1f2d4e436ce03a82a7f87cda80378691f760e9ea55045ec480a3d"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69281c281cb01994f054d862a6bb02a2e7acfe64917795c58934b0872b9ece4"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d05ed3962312fbda2a1589b97359d2467f677166952f6bd410d8c916a55febf"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78ae6a03f4cff4aa92ddd690611168856f8c33a141bd3e5a1e0a85521dc21ea0"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f983535b39795d9fb7336438faae117424c6798f763d67c6624f6caf2c4c01"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d46fdd80d4c7ecadd9faa6181e92ccc6fe91c50991c9af0e371fdf8b8a7a6150"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16e976af7365ea3b5c425124b2a7f0147eed97fdbb36d99857f173c8d8e096"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6fa97f7d1e1f74ad1565127229d510f3fd65d931fdedd707c1e15100bc9e5ebb"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4052fa4a8561bd62648e9eb993c8f3af3bdedadf3d9687aa4770d10e3709a80c"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3f0e8ae9f961037f812afe3cce7da57abf734285961fffbeff9a4c011b737732"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:99297f207db967814f1f02135bb7fe7628b9eacb046134a34e1015b26b06edce"}, - {file = "mmh3-5.1.0-cp310-cp310-win32.whl", hash = "sha256:2e6c8dc3631a5e22007fbdb55e993b2dbce7985c14b25b572dd78403c2e79182"}, - {file = "mmh3-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:e4e8c7ad5a4dddcfde35fd28ef96744c1ee0f9d9570108aa5f7e77cf9cfdf0bf"}, - {file = "mmh3-5.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:45da549269883208912868a07d0364e1418d8292c4259ca11699ba1b2475bd26"}, - {file = "mmh3-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b529dcda3f951ff363a51d5866bc6d63cf57f1e73e8961f864ae5010647079d"}, - {file = "mmh3-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db1079b3ace965e562cdfc95847312f9273eb2ad3ebea983435c8423e06acd7"}, - {file = "mmh3-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22d31e3a0ff89b8eb3b826d6fc8e19532998b2aa6b9143698043a1268da413e1"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2139bfbd354cd6cb0afed51c4b504f29bcd687a3b1460b7e89498329cc28a894"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c8105c6a435bc2cd6ea2ef59558ab1a2976fd4a4437026f562856d08996673a"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57730067174a7f36fcd6ce012fe359bd5510fdaa5fe067bc94ed03e65dafb769"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde80eb196d7fdc765a318604ded74a4378f02c5b46c17aa48a27d742edaded2"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c8eddcb441abddeb419c16c56fd74b3e2df9e57f7aa2903221996718435c7a"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:99e07e4acafbccc7a28c076a847fb060ffc1406036bc2005acb1b2af620e53c3"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e25ba5b530e9a7d65f41a08d48f4b3fedc1e89c26486361166a5544aa4cad33"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bb9bf7475b4d99156ce2f0cf277c061a17560c8c10199c910a680869a278ddc7"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a1b0878dd281ea3003368ab53ff6f568e175f1b39f281df1da319e58a19c23a"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25f565093ac8b8aefe0f61f8f95c9a9d11dd69e6a9e9832ff0d293511bc36258"}, - {file = "mmh3-5.1.0-cp311-cp311-win32.whl", hash = "sha256:1e3554d8792387eac73c99c6eaea0b3f884e7130eb67986e11c403e4f9b6d372"}, - {file = "mmh3-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad777a48197882492af50bf3098085424993ce850bdda406a358b6ab74be759"}, - {file = "mmh3-5.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f29dc4efd99bdd29fe85ed6c81915b17b2ef2cf853abf7213a48ac6fb3eaabe1"}, - {file = "mmh3-5.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45712987367cb9235026e3cbf4334670522a97751abfd00b5bc8bfa022c3311d"}, - {file = "mmh3-5.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1020735eb35086ab24affbea59bb9082f7f6a0ad517cb89f0fc14f16cea4dae"}, - {file = "mmh3-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:babf2a78ce5513d120c358722a2e3aa7762d6071cd10cede026f8b32452be322"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f47f58cd5cbef968c84a7c1ddc192fef0a36b48b0b8a3cb67354531aa33b00"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2044a601c113c981f2c1e14fa33adc9b826c9017034fe193e9eb49a6882dbb06"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94d999c9f2eb2da44d7c2826d3fbffdbbbbcde8488d353fee7c848ecc42b968"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a015dcb24fa0c7a78f88e9419ac74f5001c1ed6a92e70fd1803f74afb26a4c83"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457da019c491a2d20e2022c7d4ce723675e4c081d9efc3b4d8b9f28a5ea789bd"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71408579a570193a4ac9c77344d68ddefa440b00468a0b566dcc2ba282a9c559"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8b3a04bc214a6e16c81f02f855e285c6df274a2084787eeafaa45f2fbdef1b63"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:832dae26a35514f6d3c1e267fa48e8de3c7b978afdafa0529c808ad72e13ada3"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf658a61fc92ef8a48945ebb1076ef4ad74269e353fffcb642dfa0890b13673b"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3313577453582b03383731b66447cdcdd28a68f78df28f10d275d7d19010c1df"}, - {file = "mmh3-5.1.0-cp312-cp312-win32.whl", hash = "sha256:1d6508504c531ab86c4424b5a5ff07c1132d063863339cf92f6657ff7a580f76"}, - {file = "mmh3-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:aa75981fcdf3f21759d94f2c81b6a6e04a49dfbcdad88b152ba49b8e20544776"}, - {file = "mmh3-5.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4c1a76808dfea47f7407a0b07aaff9087447ef6280716fd0783409b3088bb3c"}, - {file = "mmh3-5.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a523899ca29cfb8a5239618474a435f3d892b22004b91779fcb83504c0d5b8c"}, - {file = "mmh3-5.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:17cef2c3a6ca2391ca7171a35ed574b5dab8398163129a3e3a4c05ab85a4ff40"}, - {file = "mmh3-5.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52e12895b30110f3d89dae59a888683cc886ed0472dd2eca77497edef6161997"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d6719045cda75c3f40397fc24ab67b18e0cb8f69d3429ab4c39763c4c608dd"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19fa07d303a91f8858982c37e6939834cb11893cb3ff20e6ee6fa2a7563826a"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31b47a620d622fbde8ca1ca0435c5d25de0ac57ab507209245e918128e38e676"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f810647c22c179b6821079f7aa306d51953ac893587ee09cf1afb35adf87cb"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6128b610b577eed1e89ac7177ab0c33d06ade2aba93f5c89306032306b5f1c6"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1e550a45d2ff87a1c11b42015107f1778c93f4c6f8e731bf1b8fa770321b8cc4"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:785ae09276342f79fd8092633e2d52c0f7c44d56e8cfda8274ccc9b76612dba2"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0f4be3703a867ef976434afd3661a33884abe73ceb4ee436cac49d3b4c2aaa7b"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e513983830c4ff1f205ab97152a0050cf7164f1b4783d702256d39c637b9d107"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9135c300535c828c0bae311b659f33a31c941572eae278568d1a953c4a57b59"}, - {file = "mmh3-5.1.0-cp313-cp313-win32.whl", hash = "sha256:c65dbd12885a5598b70140d24de5839551af5a99b29f9804bb2484b29ef07692"}, - {file = "mmh3-5.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:10db7765201fc65003fa998faa067417ef6283eb5f9bba8f323c48fd9c33e91f"}, - {file = "mmh3-5.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:b22fe2e54be81f6c07dcb36b96fa250fb72effe08aa52fbb83eade6e1e2d5fd7"}, - {file = "mmh3-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:166b67749a1d8c93b06f5e90576f1ba838a65c8e79f28ffd9dfafba7c7d0a084"}, - {file = "mmh3-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adba83c7ba5cc8ea201ee1e235f8413a68e7f7b8a657d582cc6c6c9d73f2830e"}, - {file = "mmh3-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a61f434736106804eb0b1612d503c4e6eb22ba31b16e6a2f987473de4226fa55"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba9ce59816b30866093f048b3312c2204ff59806d3a02adee71ff7bd22b87554"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd51597bef1e503363b05cb579db09269e6e6c39d419486626b255048daf545b"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d51a1ed642d3fb37b8f4cab966811c52eb246c3e1740985f701ef5ad4cdd2145"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:709bfe81c53bf8a3609efcbd65c72305ade60944f66138f697eefc1a86b6e356"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e01a9b0092b6f82e861137c8e9bb9899375125b24012eb5219e61708be320032"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:27e46a2c13c9a805e03c9ec7de0ca8e096794688ab2125bdce4229daf60c4a56"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5766299c1d26f6bfd0a638e070bd17dbd98d4ccb067d64db3745bf178e700ef0"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7785205e3e4443fdcbb73766798c7647f94c2f538b90f666688f3e757546069e"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8e574fbd39afb433b3ab95683b1b4bf18313dc46456fc9daaddc2693c19ca565"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1b6727a5a20e32cbf605743749f3862abe5f5e097cbf2afc7be5aafd32a549ae"}, - {file = "mmh3-5.1.0-cp39-cp39-win32.whl", hash = "sha256:d6eaa711d4b9220fe5252032a44bf68e5dcfb7b21745a96efc9e769b0dd57ec2"}, - {file = "mmh3-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:49d444913f6c02980e5241a53fe9af2338f2043d6ce5b6f5ea7d302c52c604ac"}, - {file = "mmh3-5.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:0daaeaedd78773b70378f2413c7d6b10239a75d955d30d54f460fb25d599942d"}, - {file = "mmh3-5.1.0.tar.gz", hash = "sha256:136e1e670500f177f49ec106a4ebf0adf20d18d96990cc36ea492c651d2b406c"}, + {file = "mmh3-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81c504ad11c588c8629536b032940f2a359dda3b6cbfd4ad8f74cb24dcd1b0bc"}, + {file = "mmh3-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b898cecff57442724a0f52bf42c2de42de63083a91008fb452887e372f9c328"}, + {file = "mmh3-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be1374df449465c9f2500e62eee73a39db62152a8bdfbe12ec5b5c1cd451344d"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0d753ad566c721faa33db7e2e0eddd74b224cdd3eaf8481d76c926603c7a00e"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dfbead5575f6470c17e955b94f92d62a03dfc3d07f2e6f817d9b93dc211a1515"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7434a27754049144539d2099a6d2da5d88b8bdeedf935180bf42ad59b3607aa3"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cadc16e8ea64b5d9a47363013e2bea469e121e6e7cb416a7593aeb24f2ad122e"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d765058da196f68dc721116cab335e696e87e76720e6ef8ee5a24801af65e63d"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8b0c53fe0994beade1ad7c0f13bd6fec980a0664bfbe5a6a7d64500b9ab76772"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:49037d417419863b222ae47ee562b2de9c3416add0a45c8d7f4e864be8dc4f89"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6ecb4e750d712abde046858ee6992b65c93f1f71b397fce7975c3860c07365d2"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:382a6bb3f8c6532ea084e7acc5be6ae0c6effa529240836d59352398f002e3fc"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7733ec52296fc1ba22e9b90a245c821adbb943e98c91d8a330a2254612726106"}, + {file = "mmh3-5.2.0-cp310-cp310-win32.whl", hash = "sha256:127c95336f2a98c51e7682341ab7cb0be3adb9df0819ab8505a726ed1801876d"}, + {file = "mmh3-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:419005f84ba1cab47a77465a2a843562dadadd6671b8758bf179d82a15ca63eb"}, + {file = "mmh3-5.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:d22c9dcafed659fadc605538946c041722b6d1104fe619dbf5cc73b3c8a0ded8"}, + {file = "mmh3-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7901c893e704ee3c65f92d39b951f8f34ccf8e8566768c58103fb10e55afb8c1"}, + {file = "mmh3-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5f5536b1cbfa72318ab3bfc8a8188b949260baed186b75f0abc75b95d8c051"}, + {file = "mmh3-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cedac4f4054b8f7859e5aed41aaa31ad03fce6851901a7fdc2af0275ac533c10"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb756caf8975882630ce4e9fbbeb9d3401242a72528230422c9ab3a0d278e60c"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:097e13c8b8a66c5753c6968b7640faefe85d8e38992703c1f666eda6ef4c3762"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7c0c7845566b9686480e6a7e9044db4afb60038d5fabd19227443f0104eeee4"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61ac226af521a572700f863d6ecddc6ece97220ce7174e311948ff8c8919a363"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:582f9dbeefe15c32a5fa528b79b088b599a1dfe290a4436351c6090f90ddebb8"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ebfc46b39168ab1cd44670a32ea5489bcbc74a25795c61b6d888c5c2cf654ed"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1556e31e4bd0ac0c17eaf220be17a09c171d7396919c3794274cb3415a9d3646"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81df0dae22cd0da87f1c978602750f33d17fb3d21fb0f326c89dc89834fea79b"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:eba01ec3bd4a49b9ac5ca2bc6a73ff5f3af53374b8556fcc2966dd2af9eb7779"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9a011469b47b752e7d20de296bb34591cdfcbe76c99c2e863ceaa2aa61113d2"}, + {file = "mmh3-5.2.0-cp311-cp311-win32.whl", hash = "sha256:bc44fc2b886243d7c0d8daeb37864e16f232e5b56aaec27cc781d848264cfd28"}, + {file = "mmh3-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ebf241072cf2777a492d0e09252f8cc2b3edd07dfdb9404b9757bffeb4f2cee"}, + {file = "mmh3-5.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5f317a727bba0e633a12e71228bc6a4acb4f471a98b1c003163b917311ea9a9"}, + {file = "mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be"}, + {file = "mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd"}, + {file = "mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c"}, + {file = "mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49"}, + {file = "mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3"}, + {file = "mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0"}, + {file = "mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065"}, + {file = "mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de"}, + {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044"}, + {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73"}, + {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504"}, + {file = "mmh3-5.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ddc63328889bcaee77b743309e5c7d2d52cee0d7d577837c91b6e7cc9e755e0b"}, + {file = "mmh3-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb0fdc451fb6d86d81ab8f23d881b8d6e37fc373a2deae1c02d27002d2ad7a05"}, + {file = "mmh3-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b29044e1ffdb84fe164d0a7ea05c7316afea93c00f8ed9449cf357c36fc4f814"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58981d6ea9646dbbf9e59a30890cbf9f610df0e4a57dbfe09215116fd90b0093"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e5634565367b6d98dc4aa2983703526ef556b3688ba3065edb4b9b90ede1c54"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0271ac12415afd3171ab9a3c7cbfc71dee2c68760a7dc9d05bf8ed6ddfa3a7a"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:45b590e31bc552c6f8e2150ff1ad0c28dd151e9f87589e7eaf508fbdd8e8e908"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bdde97310d59604f2a9119322f61b31546748499a21b44f6715e8ced9308a6c5"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc9c5f280438cf1c1a8f9abb87dc8ce9630a964120cfb5dd50d1e7ce79690c7a"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c903e71fd8debb35ad2a4184c1316b3cb22f64ce517b4e6747f25b0a34e41266"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:eed4bba7ff8a0d37106ba931ab03bdd3915fbb025bcf4e1f0aa02bc8114960c5"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fdb36b940e9261aff0b5177c5b74a36936b902f473180f6c15bde26143681a9"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7303aab41e97adcf010a09efd8f1403e719e59b7705d5e3cfed3dd7571589290"}, + {file = "mmh3-5.2.0-cp313-cp313-win32.whl", hash = "sha256:03e08c6ebaf666ec1e3d6ea657a2d363bb01effd1a9acfe41f9197decaef0051"}, + {file = "mmh3-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7fddccd4113e7b736706e17a239a696332360cbaddf25ae75b57ba1acce65081"}, + {file = "mmh3-5.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa0c966ee727aad5406d516375593c5f058c766b21236ab8985693934bb5085b"}, + {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e5015f0bb6eb50008bed2d4b1ce0f2a294698a926111e4bb202c0987b4f89078"}, + {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0f3ed828d709f5b82d8bfe14f8856120718ec4bd44a5b26102c3030a1e12501"}, + {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:f35727c5118aba95f0397e18a1a5b8405425581bfe53e821f0fb444cbdc2bc9b"}, + {file = "mmh3-5.2.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bc244802ccab5220008cb712ca1508cb6a12f0eb64ad62997156410579a1770"}, + {file = "mmh3-5.2.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ff3d50dc3fe8a98059f99b445dfb62792b5d006c5e0b8f03c6de2813b8376110"}, + {file = "mmh3-5.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:37a358cc881fe796e099c1db6ce07ff757f088827b4e8467ac52b7a7ffdca647"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b9a87025121d1c448f24f27ff53a5fe7b6ef980574b4a4f11acaabe702420d63"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ba55d6ca32eeef8b2625e1e4bfc3b3db52bc63014bd7e5df8cc11bf2b036b12"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9ff37ba9f15637e424c2ab57a1a590c52897c845b768e4e0a4958084ec87f22"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a094319ec0db52a04af9fdc391b4d39a1bc72bc8424b47c4411afb05413a44b5"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5584061fd3da584659b13587f26c6cad25a096246a481636d64375d0c1f6c07"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecbfc0437ddfdced5e7822d1ce4855c9c64f46819d0fdc4482c53f56c707b935"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7b986d506a8e8ea345791897ba5d8ba0d9d8820cd4fc3e52dbe6de19388de2e7"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:38d899a156549da8ef6a9f1d6f7ef231228d29f8f69bce2ee12f5fba6d6fd7c5"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d86651fa45799530885ba4dab3d21144486ed15285e8784181a0ab37a4552384"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c463d7c1c4cfc9d751efeaadd936bbba07b5b0ed81a012b3a9f5a12f0872bd6e"}, + {file = "mmh3-5.2.0-cp314-cp314-win32.whl", hash = "sha256:bb4fe46bdc6104fbc28db7a6bacb115ee6368ff993366bbd8a2a7f0076e6f0c0"}, + {file = "mmh3-5.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c7f0b342fd06044bedd0b6e72177ddc0076f54fd89ee239447f8b271d919d9b"}, + {file = "mmh3-5.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:3193752fc05ea72366c2b63ff24b9a190f422e32d75fdeae71087c08fff26115"}, + {file = "mmh3-5.2.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:69fc339d7202bea69ef9bd7c39bfdf9fdabc8e6822a01eba62fb43233c1b3932"}, + {file = "mmh3-5.2.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:12da42c0a55c9d86ab566395324213c319c73ecb0c239fad4726324212b9441c"}, + {file = "mmh3-5.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7f9034c7cf05ddfaac8d7a2e63a3c97a840d4615d0a0e65ba8bdf6f8576e3be"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11730eeb16dfcf9674fdea9bb6b8e6dd9b40813b7eb839bc35113649eef38aeb"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:932a6eec1d2e2c3c9e630d10f7128d80e70e2d47fe6b8c7ea5e1afbd98733e65"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca975c51c5028947bbcfc24966517aac06a01d6c921e30f7c5383c195f87991"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b0b58215befe0f0e120b828f7645e97719bbba9f23b69e268ed0ac7adde8645"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29c2b9ce61886809d0492a274a5a53047742dea0f703f9c4d5d223c3ea6377d3"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a367d4741ac0103f8198c82f429bccb9359f543ca542b06a51f4f0332e8de279"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5a5dba98e514fb26241868f6eb90a7f7ca0e039aed779342965ce24ea32ba513"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:941603bfd75a46023807511c1ac2f1b0f39cccc393c15039969806063b27e6db"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:132dd943451a7c7546978863d2f5a64977928410782e1a87d583cb60eb89e667"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f698733a8a494466432d611a8f0d1e026f5286dee051beea4b3c3146817e35d5"}, + {file = "mmh3-5.2.0-cp314-cp314t-win32.whl", hash = "sha256:6d541038b3fc360ec538fc116de87462627944765a6750308118f8b509a8eec7"}, + {file = "mmh3-5.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e912b19cf2378f2967d0c08e86ff4c6c360129887f678e27e4dde970d21b3f4d"}, + {file = "mmh3-5.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e7884931fe5e788163e7b3c511614130c2c59feffdc21112290a194487efb2e9"}, + {file = "mmh3-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3c6041fd9d5fb5fcac57d5c80f521a36b74aea06b8566431c63e4ffc49aced51"}, + {file = "mmh3-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:58477cf9ef16664d1ce2b038f87d2dc96d70fe50733a34a7f07da6c9a5e3538c"}, + {file = "mmh3-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be7d3dca9358e01dab1bad881fb2b4e8730cec58d36dd44482bc068bfcd3bc65"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:931d47e08c9c8a67bf75d82f0ada8399eac18b03388818b62bfa42882d571d72"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd966df3489ec13848d6c6303429bbace94a153f43d1ae2a55115fd36fd5ca5d"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c677d78887244bf3095020b73c42b505b700f801c690f8eaa90ad12d3179612f"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63830f846797187c5d3e2dae50f0848fdc86032f5bfdc58ae352f02f857e9025"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c3f563e8901960e2eaa64c8e8821895818acabeb41c96f2efbb936f65dbe486c"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96f1e1ac44cbb42bcc406e509f70c9af42c594e72ccc7b1257f97554204445f0"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7bbb0df897944b5ec830f3ad883e32c5a7375370a521565f5fe24443bfb2c4f7"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1fae471339ae1b9c641f19cf46dfe6ffd7f64b1fba7c4333b99fa3dd7f21ae0a"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:aa6e5d31fdc5ed9e3e95f9873508615a778fe9b523d52c17fc770a3eb39ab6e4"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:746a5ee71c6d1103d9b560fa147881b5e68fd35da56e54e03d5acefad0e7c055"}, + {file = "mmh3-5.2.0-cp39-cp39-win32.whl", hash = "sha256:10983c10f5c77683bd845751905ba535ec47409874acc759d5ce3ff7ef34398a"}, + {file = "mmh3-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fdfd3fb739f4e22746e13ad7ba0c6eedf5f454b18d11249724a388868e308ee4"}, + {file = "mmh3-5.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:33576136c06b46a7046b6d83a3d75fbca7d25f84cec743f1ae156362608dc6d2"}, + {file = "mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8"}, ] [package.extras] -benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.8.1)", "xxhash (==3.5.0)"] -docs = ["myst-parser (==4.0.0)", "shibuya (==2024.12.21)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)"] -lint = ["black (==24.10.0)", "clang-format (==19.1.7)", "isort (==5.13.2)", "pylint (==3.3.3)"] -plot = ["matplotlib (==3.10.0)", "pandas (==2.2.3)"] -test = ["pytest (==8.3.4)", "pytest-sugar (==1.0.0)"] -type = ["mypy (==1.14.1)"] +benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.9.0)", "xxhash (==3.5.0)"] +docs = ["myst-parser (==4.0.1)", "shibuya (==2025.7.24)", "sphinx (==8.2.3)", "sphinx-copybutton (==0.5.2)"] +lint = ["black (==25.1.0)", "clang-format (==20.1.8)", "isort (==6.0.1)", "pylint (==3.3.7)"] +plot = ["matplotlib (==3.10.3)", "pandas (==2.3.1)"] +test = ["pytest (==8.4.1)", "pytest-sugar (==1.0.0)"] +type = ["mypy (==1.17.0)"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] @@ -501,14 +534,14 @@ dev = ["black", "mypy", "pytest"] [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev", "test"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -525,14 +558,14 @@ files = [ [[package]] name = "pbr" -version = "6.1.1" +version = "7.0.1" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" groups = ["main"] files = [ - {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, - {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, + {file = "pbr-7.0.1-py2.py3-none-any.whl", hash = "sha256:32df5156fbeccb6f8a858d1ebc4e465dcf47d6cc7a4895d5df9aa951c712fc35"}, + {file = "pbr-7.0.1.tar.gz", hash = "sha256:3ecbcb11d2b8551588ec816b3756b1eb4394186c3b689b17e04850dfc20f7e57"}, ] [package.dependencies] @@ -540,36 +573,36 @@ setuptools = "*" [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "ply" @@ -600,20 +633,21 @@ ply = "3.11" [[package]] name = "pydantic" -version = "2.10.6" +version = "2.12.3" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, + {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, + {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" -typing-extensions = ">=4.12.2" +pydantic-core = "2.41.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -621,127 +655,144 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, + {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "test"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -775,229 +826,286 @@ files = [ [[package]] name = "pytest" -version = "8.3.4" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, - {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" pluggy = ">=1.5,<2" +pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "6.0.0" +version = "7.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, - {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, + {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, + {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, ] [package.dependencies] -coverage = {version = ">=7.5", extras = ["toml"]} -pytest = ">=4.6" +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytokens" +version = "0.2.0" +description = "A Fast, spec compliant Python 3.13+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8"}, + {file = "pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43"}, +] [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] name = "regex" -version = "2024.11.6" +version = "2025.10.23" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, - {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, - {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, - {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, - {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, + {file = "regex-2025.10.23-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:17bbcde374bef1c5fad9b131f0e28a6a24856dd90368d8c0201e2b5a69533daa"}, + {file = "regex-2025.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4e10434279cc8567f99ca6e018e9025d14f2fded2a603380b6be2090f476426"}, + {file = "regex-2025.10.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c9bb421cbe7012c744a5a56cf4d6c80829c72edb1a2991677299c988d6339c8"}, + {file = "regex-2025.10.23-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275cd1c2ed8c4a78ebfa489618d7aee762e8b4732da73573c3e38236ec5f65de"}, + {file = "regex-2025.10.23-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b426ae7952f3dc1e73a86056d520bd4e5f021397484a6835902fc5648bcacce"}, + {file = "regex-2025.10.23-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5cdaf5b6d37c7da1967dbe729d819461aab6a98a072feef65bbcff0a6e60649"}, + {file = "regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bfeff0b08f296ab28b4332a7e03ca31c437ee78b541ebc874bbf540e5932f8d"}, + {file = "regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f97236a67307b775f30a74ef722b64b38b7ab7ba3bb4a2508518a5de545459c"}, + {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be19e7de499940cd72475fb8e46ab2ecb1cf5906bebdd18a89f9329afb1df82f"}, + {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:883df76ee42d9ecb82b37ff8d01caea5895b3f49630a64d21111078bbf8ef64c"}, + {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2e9117d1d35fc2addae6281019ecc70dc21c30014b0004f657558b91c6a8f1a7"}, + {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ff1307f531a5d8cf5c20ea517254551ff0a8dc722193aab66c656c5a900ea68"}, + {file = "regex-2025.10.23-cp310-cp310-win32.whl", hash = "sha256:7888475787cbfee4a7cd32998eeffe9a28129fa44ae0f691b96cb3939183ef41"}, + {file = "regex-2025.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ec41a905908496ce4906dab20fb103c814558db1d69afc12c2f384549c17936a"}, + {file = "regex-2025.10.23-cp310-cp310-win_arm64.whl", hash = "sha256:b2b7f19a764d5e966d5a62bf2c28a8b4093cc864c6734510bdb4aeb840aec5e6"}, + {file = "regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd"}, + {file = "regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb"}, + {file = "regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f"}, + {file = "regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33"}, + {file = "regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca"}, + {file = "regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490"}, + {file = "regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2"}, + {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40"}, + {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82"}, + {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83"}, + {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd"}, + {file = "regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575"}, + {file = "regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8"}, + {file = "regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566"}, + {file = "regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc"}, + {file = "regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2"}, + {file = "regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45"}, + {file = "regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e"}, + {file = "regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43"}, + {file = "regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca"}, + {file = "regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc"}, + {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a"}, + {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32"}, + {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288"}, + {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147"}, + {file = "regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079"}, + {file = "regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842"}, + {file = "regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335"}, + {file = "regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193"}, + {file = "regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b"}, + {file = "regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f"}, + {file = "regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a"}, + {file = "regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b"}, + {file = "regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb"}, + {file = "regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368"}, + {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351"}, + {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673"}, + {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313"}, + {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87"}, + {file = "regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1"}, + {file = "regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0"}, + {file = "regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f"}, + {file = "regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044"}, + {file = "regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc"}, + {file = "regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656"}, + {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58"}, + {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e"}, + {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a"}, + {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e"}, + {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8"}, + {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6"}, + {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8"}, + {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494"}, + {file = "regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9"}, + {file = "regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6"}, + {file = "regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473"}, + {file = "regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6"}, + {file = "regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5"}, + {file = "regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10"}, + {file = "regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34"}, + {file = "regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a"}, + {file = "regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c"}, + {file = "regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224"}, + {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4"}, + {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462"}, + {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627"}, + {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec"}, + {file = "regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8"}, + {file = "regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796"}, + {file = "regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9"}, + {file = "regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422"}, + {file = "regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788"}, + {file = "regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10"}, + {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7"}, + {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f"}, + {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61"}, + {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c"}, + {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432"}, + {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e"}, + {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43"}, + {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3"}, + {file = "regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521"}, + {file = "regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741"}, + {file = "regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed"}, + {file = "regex-2025.10.23-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8d286760ee5b77fd21cf6b33cc45e0bffd1deeda59ca65b9be996f590a9828a"}, + {file = "regex-2025.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e72e3b84b170fec02193d32620a0a7060a22e52c46e45957dcd14742e0d28fb"}, + {file = "regex-2025.10.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec506e8114fa12d21616deb44800f536d6bf2e1a69253dbf611f69af92395c99"}, + {file = "regex-2025.10.23-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7e481f9710e8e24228ce2c77b41db7662a3f68853395da86a292b49dadca2aa"}, + {file = "regex-2025.10.23-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4663ff2fc367735ae7b90b4f0e05b25554446df4addafc76fdaacaaa0ba852b5"}, + {file = "regex-2025.10.23-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0879dd3251a42d2e9b938e1e03b1e9f60de90b4d153015193f5077a376a18439"}, + {file = "regex-2025.10.23-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:651c58aecbab7e97bdf8ec76298a28d2bf2b6238c099ec6bf32e6d41e2f9a9cb"}, + {file = "regex-2025.10.23-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ceabc62a0e879169cd1bf066063bd6991c3e41e437628936a2ce66e0e2071c32"}, + {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bfdf4e9aa3e7b7d02fda97509b4ceeed34542361694ecc0a81db1688373ecfbd"}, + {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:92f565ff9beb9f51bc7cc8c578a7e92eb5c4576b69043a4c58cd05d73fda83c5"}, + {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:abbea548b1076eaf8635caf1071c9d86efdf0fa74abe71fca26c05a2d64cda80"}, + {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:33535dcf34f47821381e341f7b715cbd027deda4223af4d3932adcd371d3192a"}, + {file = "regex-2025.10.23-cp39-cp39-win32.whl", hash = "sha256:345c9df49a15bf6460534b104b336581bc5f35c286cac526416e7a63d389b09b"}, + {file = "regex-2025.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:f668fe1fd3358c5423355a289a4a003e58005ce829d217b828f80bd605a90145"}, + {file = "regex-2025.10.23-cp39-cp39-win_arm64.whl", hash = "sha256:07a3fd25d9074923e4d7258b551ae35ab6bdfe01904b8f0d5341c7d8b20eb18d"}, + {file = "regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26"}, ] [[package]] name = "rich" -version = "13.9.4" +version = "14.2.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, + {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, + {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -1020,118 +1128,107 @@ pbr = "*" [[package]] name = "setuptools" -version = "75.8.0" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, - {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] -[[package]] -name = "textual" -version = "1.0.0" -description = "Modern Text User Interface framework" -optional = false -python-versions = "<4.0.0,>=3.8.1" -groups = ["main"] -files = [ - {file = "textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f"}, - {file = "textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399"}, -] - -[package.dependencies] -markdown-it-py = {version = ">=2.1.0", extras = ["linkify", "plugins"]} -platformdirs = ">=3.6.0,<5" -rich = ">=13.3.3" -typing-extensions = ">=4.4.0,<5.0.0" - -[package.extras] -syntax = ["tree-sitter (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-bash (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-css (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-go (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-html (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-java (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-javascript (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-json (>=0.24.0) ; python_version >= \"3.9\"", "tree-sitter-markdown (>=0.3.0) ; python_version >= \"3.9\"", "tree-sitter-python (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-regex (>=0.24.0) ; python_version >= \"3.9\"", "tree-sitter-rust (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-sql (>=0.3.0) ; python_version >= \"3.9\"", "tree-sitter-toml (>=0.6.0) ; python_version >= \"3.9\"", "tree-sitter-xml (>=0.7.0) ; python_version >= \"3.9\"", "tree-sitter-yaml (>=0.6.0) ; python_version >= \"3.9\""] - [[package]] name = "tomli" -version = "2.2.1" +version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev", "test"] files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] markers = {dev = "python_version < \"3.11\"", test = "python_full_version <= \"3.11.0a6\""} [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" +groups = ["main", "dev", "test"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] -markers = {dev = "python_version < \"3.11\""} +markers = {dev = "python_version < \"3.11\"", test = "python_version < \"3.11\""} [[package]] -name = "uc-micro-py" -version = "1.0.3" -description = "Micro subset of unicode data files for linkify-it-py projects." +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, - {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] -[package.extras] -test = ["coverage", "pytest", "pytest-cov"] +[package.dependencies] +typing-extensions = ">=4.12.0" [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0.0" -content-hash = "1998c54eb79efe2b8779cfd3d7133d5ec31521bfcbd8125ccbb9aee0117c82b4" +content-hash = "aa1f1d4cc44f5c67732445578008b6d75bca2afecfa3d96fd4358011df62a476" diff --git a/pyproject.toml b/pyproject.toml index be04124..2f432c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,19 +27,19 @@ classifiers = [ ] dependencies = [ - "pydantic == 2.10.6", - "pyyaml == 6.0.2", - "pygments == 2.19.1", - "ordered-set == 4.1.0", - "dotwiz == 0.4.0", - "mmh3 == 5.1.0", - "regex == 2024.11.6", + "pydantic (==2.12.3)", + "pyyaml (==6.0.3)", + "pygments (==2.19.2)", + "ordered-set (==4.1.0)", + "dotwiz (==0.4.0)", + "mmh3 (==5.2.0)", + "regex (==2025.10.23)", "jsx-lexer == 2.0.1", - "aenum == 3.1.15", + "aenum (==3.1.16)", "puppetparser == 0.2.14", - "textual == 1.0.0", "sarif-om == 1.0.4", "jschema-to-python == 1.2.3", + "rich (==14.2.0)", ] [project.urls] @@ -52,9 +52,9 @@ deepsecrets = "deepsecrets:__main__.runnable_entrypoint" [tool.poetry.group.test.dependencies] -pytest = "^8.3.4" -coverage = "^7.6.12" -pytest-cov = "^6.0.0" +pytest = "8.4.2" +coverage = "7.10.7" +pytest-cov = "7.0.0" [tool.poetry.group.dev.dependencies] black = "^25.1.0" From 024d9feb16f7165d8982f4fd78bf87e3563936e1 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Tue, 28 Oct 2025 19:17:31 +0300 Subject: [PATCH 02/20] Refactored SARIF output, internal models, added error reporting both for progress and results + stability improvements. --- deepsecrets/cli.py | 110 +++++++++-- deepsecrets/config.py | 3 +- deepsecrets/core/engines/hashed_secret.py | 7 +- deepsecrets/core/engines/regex.py | 4 +- deepsecrets/core/engines/semantic.py | 16 +- deepsecrets/core/model/file.py | 3 +- deepsecrets/core/model/finding.py | 180 ++---------------- deepsecrets/core/model/internal/processing.py | 10 + deepsecrets/core/model/response/base.py | 55 ++++++ deepsecrets/core/model/response/builtin.py | 58 ++++++ deepsecrets/core/model/response/dojo_sarif.py | 143 ++++++++++++++ deepsecrets/core/modes/iscan_mode.py | 58 ++++-- deepsecrets/core/utils/file_analyzer.py | 8 +- deepsecrets/core/utils/finding_merger.py | 22 +++ deepsecrets/core/utils/lifecycle_hooks.py | 4 + deepsecrets/core/utils/log.py | 23 +++ deepsecrets/core/utils/progress.py | 11 +- deepsecrets/core/utils/sarif_helper.py | 64 ------- deepsecrets/scan_modes/cli.py | 31 +-- tests/core/engines/regex/test_regex.py | 9 +- tests/core/engines/semantic/test_semantic.py | 4 +- tests/core/model/test_finding.py | 16 +- 22 files changed, 526 insertions(+), 313 deletions(-) create mode 100644 deepsecrets/core/model/internal/processing.py create mode 100644 deepsecrets/core/model/response/base.py create mode 100644 deepsecrets/core/model/response/builtin.py create mode 100644 deepsecrets/core/model/response/dojo_sarif.py create mode 100644 deepsecrets/core/utils/finding_merger.py delete mode 100644 deepsecrets/core/utils/sarif_helper.py diff --git a/deepsecrets/cli.py b/deepsecrets/cli.py index dd5df06..b70c405 100644 --- a/deepsecrets/cli.py +++ b/deepsecrets/cli.py @@ -3,14 +3,16 @@ import json import logging from argparse import RawTextHelpFormatter -from typing import List +from typing import Dict, List from jschema_to_python.to_json import to_json from deepsecrets import MODULE_NAME, console -from deepsecrets.config import Config, config, Output +from deepsecrets.config import SCANNER_VERSION, SCANNER_VERSION_NUMERIC, Config, config, Output from deepsecrets.core.engines.regex import RegexEngine from deepsecrets.core.engines.semantic import SemanticEngine -from deepsecrets.core.model.finding import Finding, FindingResponse +from deepsecrets.core.model.finding import Finding +from deepsecrets.core.model.response.builtin import BuiltinFormatResponseBuilder +from deepsecrets.core.model.response.dojo_sarif import DojoSarifResponseBuilder from deepsecrets.core.rulesets.false_findings import FalseFindingsBuilder from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder @@ -18,14 +20,8 @@ from deepsecrets.core.utils.log import logger from deepsecrets.scan_modes.cli import CliScanMode -from rich.progress import ( - SpinnerColumn, - Progress, - TextColumn, - BarColumn, - TaskProgressColumn, - TimeRemainingColumn, -) +from rich.progress import SpinnerColumn, Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn +from rich.panel import Panel from rich.table import Table, Column from rich import box from rich.text import Text @@ -49,6 +45,7 @@ class ReturnCodes: TaskProgressColumn(), TimeRemainingColumn(), TextColumn("[bold red]{task.fields[findings]}", justify="right"), + TextColumn("[bold red]{task.fields[errors]}", justify="right"), console=console, refresh_per_second=5, expand=True, @@ -74,7 +71,7 @@ def say_hello(self) -> None: Text('____________________________________', style='reverse'), padding=(0, 0), title='A better tool for Secret Scanning ', - subtitle='version 1.4.0', + subtitle=f'version {SCANNER_VERSION}', ), align='center', ) @@ -191,15 +188,17 @@ def _build_argparser(self) -> None: '--outformat', default='json', type=str, - choices=['json', 'dojo-sarif'], - help='"json": internal format (default)\n' '"dojo-sarif": SARIF format compatible with DefectDojo\n', + choices=['json', 'sarif', 'dojo-sarif'], + help='"json": internal format (default)\n' + '"sarif": SARIF format (specification accurate)\n' + '"dojo-sarif": SARIF format (compatible with DefectDojo\'s parser)\n', ) parser.add_argument( '--disable-masking', action='store_true', - help='Secrets are rendered masked inside the report by default.\n' - 'Use this flag if you want to render found secrets in plaintext.', + help='Secrets are rendered MASKED inside the report by default.\n' + 'Use this flag if you want to render found secrets in plaintext but be extremely careful.', ) self.argparser = parser @@ -261,6 +260,51 @@ def start(self) -> int: # pragma: nocover logger.exception(e) return ReturnCodes.ERROR + if config.output.type == 'json': + console.print('\n') + if SCANNER_VERSION_NUMERIC[0] == 1 and SCANNER_VERSION_NUMERIC[1] < 5: + console.print( + Align( + Panel( + "The internal JSON report format is now DEPRECATED.\n\nThe tool will begin reporting in SARIF BY DEFAULT starting from the release 1.5.0 (January 2026)\n\nConsider switching now.", + padding=(1, 2), + title=Text('SWITCHING TO SARIF NEXT RELEASE', style='reverse'), + highlight=True, + subtitle=Text(' --outformat sarif ', style='reverse'), + title_align='center', + width=90, + subtitle_align='center', + box=box.HEAVY, + style='black on orange_red1', + expand=False, + ), + align='center', + ) + ) + + else: + console.print( + Align( + Panel( + f"The internal JSON report format was DEPRECATED in the release 1.4.1.\nNow ({SCANNER_VERSION}) it is REMOVED.\n.", + padding=(1, 2), + title=Text('SARIF IS NOW DEFAULT OUTPUT FORMAT', style='reverse'), + highlight=True, + subtitle=Text('', style='reverse'), + title_align='center', + width=90, + subtitle_align='center', + box=box.HEAVY, + style='black on orange_red1', + expand=False, + ), + align='center', + ) + ) + + console.print('\n\n') + return -1 + console.rule( f'Planning a scan against {config.workdir_path} using {config.process_count} process(es)', characters='=' ) @@ -284,7 +328,11 @@ def start(self) -> int: # pragma: nocover mode.set_progress_bar(progress_bar) progress_bar.start() - findings: List[Finding] = mode.run() + + findings: List[Finding] + errors: Dict[str, List[str]] + + findings, errors = mode.run() progress_bar.stop() finish_time = datetime.now() report_path = get_abspath(config.output.path) @@ -299,10 +347,17 @@ def start(self) -> int: # pragma: nocover table.add_column() table.add_column(justify='right') table.add_row( - Align('Files (Tokens) Processed', vertical='middle'), + Align('Processed Files (Tokens)', vertical='middle'), f'{str(len(mode.filepaths))} ({mode.stats.tokens_processed})', ) table.add_row(Align('Elapsed', vertical='middle'), f'{(finish_time-startup_time).total_seconds():.1f}s') + errors_line_color = '[bold red]' if len(errors.keys()) > 0 else '[bold green]' + table.add_row( + Align(f'{errors_line_color}File Errors', vertical='middle'), + f'{errors_line_color}{str(len(errors.keys()))}', + ) + table.add_row() + findings_line_color = '[bold red]' if len(findings) > 0 else '[bold green]' table.add_row( Align(f'{findings_line_color}Potential Findings', vertical='middle'), @@ -314,10 +369,25 @@ def start(self) -> int: # pragma: nocover with open(report_path, 'w+') as f: if config.output.type == 'json': - json.dump(FindingResponse.from_list(findings, config.disable_masking), f) + json.dump( + BuiltinFormatResponseBuilder() + .with_current_mode(mode) + .with_findings_list(findings) + .with_masking_enabled(not config.disable_masking) + .build(), + f, + ) if config.output.type == 'dojo-sarif': - f.write(to_json(FindingResponse.dojo_sarif_from_list(findings, config.disable_masking))) + f.write( + to_json( + DojoSarifResponseBuilder() + .with_current_mode(mode) + .with_findings_list(findings) + .with_masking_enabled(not config.disable_masking) + .build() + ) + ) if len(findings) > 0 and config.disable_masking: console.print( diff --git a/deepsecrets/config.py b/deepsecrets/config.py index d9c6ae3..21272c9 100644 --- a/deepsecrets/config.py +++ b/deepsecrets/config.py @@ -11,7 +11,8 @@ FALLBACK_PROCESS_COUNT = 4 SCANNER_NAME = "DeepSecrets" -SCANNER_VERSION = "1.5.0" +SCANNER_VERSION = "1.4.1" +SCANNER_VERSION_NUMERIC = [int(subver) for subver in SCANNER_VERSION.split('.')] SCANNER_URL = "https://github.com/ntoskernel/deepsecrets" MAX_LINE_LENGTH_FOR_CONTEXT = 300 diff --git a/deepsecrets/core/engines/hashed_secret.py b/deepsecrets/core/engines/hashed_secret.py index f91c483..d114d7e 100644 --- a/deepsecrets/core/engines/hashed_secret.py +++ b/deepsecrets/core/engines/hashed_secret.py @@ -37,12 +37,13 @@ def _check_rule(self, token: Token, rule: HashedSecretRule) -> List[Finding]: Finding( rules=[rule], detection=token.content, - start_pos=0, - end_pos=token.length, + start_offset=0, + end_offset=token.length, file=None, # filled higher final_rule=None, # filled higher, full_line=None, # filled higher - linum=None, # filled higher + start_line_number=None, # filled higher + end_line_number=None, # filled higher ) ) diff --git a/deepsecrets/core/engines/regex.py b/deepsecrets/core/engines/regex.py index 3f62486..7cfcd0e 100644 --- a/deepsecrets/core/engines/regex.py +++ b/deepsecrets/core/engines/regex.py @@ -31,8 +31,8 @@ def _check_rule(self, token: Token, rule: RegexRule) -> List[Finding]: Finding( rules=[rule], detection=token.content[start:end], - start_pos=start, - end_pos=end, + start_offset=start, + end_offset=end, ) ) diff --git a/deepsecrets/core/engines/semantic.py b/deepsecrets/core/engines/semantic.py index 77a7be3..36c8a64 100644 --- a/deepsecrets/core/engines/semantic.py +++ b/deepsecrets/core/engines/semantic.py @@ -42,7 +42,7 @@ 'change', 'mock', 'fake', - 'dummy' + 'dummy', ] @@ -75,8 +75,8 @@ def search(self, token: Token) -> List[Finding]: findings.append( Finding( detection=token.content, - start_pos=0, - end_pos=len(token.content), + start_offset=0, + end_offset=len(token.content), rules=[Rule(id='S107', name='Dangerous condition', confidence=9)], ) ) @@ -109,8 +109,8 @@ def search(self, token: Token) -> List[Finding]: findings.append( Finding( detection=token.content, - start_pos=0, - end_pos=len(token.content), + start_offset=0, + end_offset=len(token.content), rules=[Rule(id='S105', name='Entropy+Var naming', confidence=-1)], ) ) @@ -122,8 +122,8 @@ def search(self, token: Token) -> List[Finding]: findings.append( Finding( detection=token.content, - start_pos=0, - end_pos=len(token.content), + start_offset=0, + end_offset=len(token.content), rules=[Rule(id='S106', name='Var naming', confidence=-1)], ) ) @@ -156,4 +156,4 @@ def _if_dangerous_variable(self, token: Token) -> bool: def normalize_punctuation(self, string: str): normalized = string.replace(' ', '_').replace('-', ' ').replace('_', ' ') parts = StringUtils.camel_case_divide(normalized).split(' ') - return normalized.lower(), parts \ No newline at end of file + return normalized.lower(), parts diff --git a/deepsecrets/core/model/file.py b/deepsecrets/core/model/file.py index 9e72ec2..381c529 100644 --- a/deepsecrets/core/model/file.py +++ b/deepsecrets/core/model/file.py @@ -38,6 +38,7 @@ def __init__( self.content = self._get_contents() except Exception as e: logger.error(f'Error during fetching file contents: {e}') + raise self.length = len(self.content) @@ -130,7 +131,7 @@ def get_line_length(self, line_number: int) -> int: offsets = self.line_offsets.get(line_number) return offsets[1] - offsets[0] - def get_line_offset(self, line_number: int) -> int: + def get_line_start_offset(self, line_number: int) -> int: return self.line_offsets.get(line_number)[0] def __repr__(self) -> str: # pragma: no cover diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index 24513f1..4926a59 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -1,15 +1,14 @@ from __future__ import annotations from hashlib import sha256 -from typing import Any, Dict, List, Optional +from typing import Any, List, Optional from pydantic import BaseModel, ConfigDict, Field, PrivateAttr from deepsecrets.core.model.file import File from deepsecrets.core.model.rules.rule import Rule -import sarif_om as om -from deepsecrets.config import MAX_LINE_LENGTH_FOR_CONTEXT, SCANNER_NAME, SCANNER_URL, SCANNER_VERSION +from deepsecrets.config import MAX_LINE_LENGTH_FOR_CONTEXT class Finding(BaseModel): @@ -18,9 +17,10 @@ class Finding(BaseModel): detection: str full_line: Optional[str] = Field(default=None) full_line_partial: bool = Field(default=False) - linum: Optional[int] = Field(default=None) - start_pos: int - end_pos: int + start_line_number: Optional[int] = Field(default=None) + end_line_number: Optional[int] = Field(default=None) + start_offset: int + end_offset: int reason: str = Field(default='') final_rule: Optional[Rule] = Field(default=None) _mapped_on_file: bool = PrivateAttr(default=False) @@ -36,9 +36,11 @@ def map_on_file(self, relative_start: int, file: Optional['File'] = None) -> Non if self.file is None: self.file = file - self.start_pos += relative_start - self.end_pos += relative_start - self.linum = self.file.get_line_number(self.end_pos) + self.start_offset += relative_start + self.end_offset += relative_start + + self.start_line_number = self.file.get_line_number(self.start_offset) + self.end_line_number = self.file.get_line_number(self.end_offset) if not self.full_line: self._populate_full_line() @@ -47,8 +49,8 @@ def map_on_file(self, relative_start: int, file: Optional['File'] = None) -> Non def _populate_full_line(self): self.full_line_partial = False - if self.file.get_line_length(self.linum) <= MAX_LINE_LENGTH_FOR_CONTEXT: - self.full_line = self.file.get_line_contents(self.linum) + if self.file.get_line_length(self.start_line_number) <= MAX_LINE_LENGTH_FOR_CONTEXT: + self.full_line = self.file.get_line_contents(self.start_line_number) return self.full_line_partial = True @@ -71,7 +73,7 @@ def __hash__(self) -> int: # pragma: nocover if not self.file: raise Exception() - return hash(f'{self.file.path}{self.detection}{self.start_pos}{self.end_pos}') + return hash(f'{self.file.path}{self.detection}{self.start_offset}{self.end_offset}') def __eq__(self, other: Any) -> bool: if not isinstance(other, Finding): @@ -85,12 +87,12 @@ def __eq__(self, other: Any) -> bool: if other.detection != self.detection: return False - if other.start_pos and self.start_pos: - if other.start_pos != self.start_pos: + if other.start_offset and self.start_offset: + if other.start_offset != self.start_offset: return False - if other.end_pos and self.end_pos: - if other.end_pos != self.end_pos: + if other.end_offset and self.end_offset: + if other.end_offset != self.end_offset: return False return True @@ -106,149 +108,3 @@ def merge(self, other: Any) -> bool: self.rules = list(set(self.rules)) return True - - -class FindingMerger: - all: List[Finding] - - def __init__(self, full_list: List[Finding]) -> None: - self.all = full_list - - def merge(self) -> List[Finding]: - interm_dict: Dict[int, Finding] = {} - - for elem in self.all: - hash = elem.__hash__() - if hash not in interm_dict: - interm_dict[hash] = elem - - interm_dict[hash].merge(elem) - - return list(interm_dict.values()) - - -class FindingResponse: - @classmethod - def from_list(cls, list: List[Finding], disable_masking: bool = False) -> Dict[str, List[Dict]]: - resp: Dict[str, List[Dict]] = {} - for finding in list: - if finding.file is None: - continue - - if finding.file.path not in resp: - resp[finding.file.path] = [] - - resp_finding = FindingApiModel.from_finding(finding) - - if not disable_masking: - - resp_finding.line = resp_finding.line.replace(resp_finding.string, '*' * len(resp_finding.string)) - resp_finding.string = '*' * len(resp_finding.string) - - resp[finding.file.path].append(resp_finding.model_dump()) - - return resp - - @classmethod - def dojo_sarif_from_list(cls, list: List[Finding], disable_masking: bool = False) -> om.SarifLog: # type: ignore - - report = om.SarifLog( - schema_uri='https://json.schemastore.org/sarif-2.1.0.json', - version='2.1.0', - runs=[ - om.Run( - tool=om.Tool( - driver=om.ToolComponent( - name=SCANNER_NAME, semantic_version=SCANNER_VERSION, information_uri=SCANNER_URL, rules=[] - ) - ), - results=[], - ) - ], - ) - - sarif_rules: Dict[str, om.ReportingDescriptor] = {} - - for finding in list: - - finding.choose_final_rule() - - if finding.final_rule.confidence > 5: - precision = 'high' - security_severity = 'High' - level = 'error' - elif finding.final_rule.confidence > 0: - precision = 'medium' - security_severity = 'High' - level = 'error' - else: - precision = 'low' - security_severity = 'Medium' - level = 'warning' - - rule = om.ReportingDescriptor( - id=finding.final_rule.id, - short_description={'text': finding.final_rule.name}, - full_description={'text': finding.final_rule.name}, - help={'text': finding.final_rule.name}, - properties={'security_severity': security_severity, 'precision': precision}, - default_configuration={'level': level}, - ) - - sarif_rules[finding.final_rule.id] = rule - - region = SarifHelper.get_region_for_finding(finding=finding, masking=disable_masking is False) - context_region = SarifHelper.get_context_region_for_finding( - finding=finding, masking=disable_masking is False - ) - - result = om.Result( - rule_id=finding.final_rule.id, - message=om.Message(text=f'Secret in code ({finding.final_rule.name})'), - locations=[ - om.Location( - physical_location=om.PhysicalLocation( - artifact_location=om.ArtifactLocation( - uri=finding.file.relative_path, uri_base_id='%SRCROOT%' - ), - region=region, - context_region=context_region, - ) - ) - ], - ) - - report.runs[0].results.append(result) - - report.runs[0].tool.driver.rules = [rule for rule in sarif_rules.values()] - return report - - -class FindingApiModel(BaseModel): - line: Optional[str] - string: str - line_number: int - rule: str - reason: str - confidence: int - fingerprint: str - file_start_offset: int - file_end_offset: int - - @classmethod - def from_finding(cls, finding: Finding) -> FindingApiModel: - finding.choose_final_rule() - return FindingApiModel( - line=finding.full_line, - string=finding.detection, - line_number=finding.linum, - rule=finding.final_rule.id, - reason=finding.get_reason(), - confidence=finding.final_rule.confidence, - fingerprint=finding.get_fingerprint(), - file_start_offset=finding.start_pos, - file_end_offset=finding.end_pos, - ) - - -from deepsecrets.core.utils.sarif_helper import SarifHelper diff --git a/deepsecrets/core/model/internal/processing.py b/deepsecrets/core/model/internal/processing.py new file mode 100644 index 0000000..43e4519 --- /dev/null +++ b/deepsecrets/core/model/internal/processing.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass +from typing import List +from deepsecrets.core.model.finding import Finding + + +@dataclass +class PerFileAnalysisResult: + internal_task_id: int + findings: List[Finding] + errors: List[str] diff --git a/deepsecrets/core/model/response/base.py b/deepsecrets/core/model/response/base.py new file mode 100644 index 0000000..ca87efc --- /dev/null +++ b/deepsecrets/core/model/response/base.py @@ -0,0 +1,55 @@ +from abc import abstractmethod +from typing import Any, List + +from deepsecrets.config import MAX_LINE_LENGTH_FOR_CONTEXT +from deepsecrets.core.model.finding import Finding +from deepsecrets.core.modes.iscan_mode import ScanMode + + +class BaseResponseBuilder: + + findings: List[Finding] + mode: ScanMode + masking_enabled: bool + + def __init__(self) -> None: + self.masking_enabled = False + + def with_findings_list(self, findings: List[Finding]): + self.findings = findings + return self + + def with_current_mode(self, mode: ScanMode): + self.mode = mode + return self + + def with_masking_enabled(self, masking_enabled: bool): + self.masking_enabled = masking_enabled + return self + + @abstractmethod + def build(self) -> Any: + pass + + def _get_context_boundaries(self, finding: Finding, start_column: int, end_column: int): + line_length = finding.file.get_line_length(finding.start_line_number) + boundaries = [0, line_length] + line_partial = False + + if line_length <= MAX_LINE_LENGTH_FOR_CONTEXT: + return boundaries + + line_partial = True + boundaries[0] = int(start_column - (MAX_LINE_LENGTH_FOR_CONTEXT / 2)) + if boundaries[0] < 0: + boundaries[0] = 0 + + boundaries[1] = int(end_column + (MAX_LINE_LENGTH_FOR_CONTEXT / 2)) + if boundaries[1] > line_length: + boundaries[1] = line_length + + return boundaries, line_partial + + def _mask(self, snippet: str, detection: str): + masked_detection = '*' * len(detection) + return snippet.replace(detection, masked_detection) diff --git a/deepsecrets/core/model/response/builtin.py b/deepsecrets/core/model/response/builtin.py new file mode 100644 index 0000000..2c7d074 --- /dev/null +++ b/deepsecrets/core/model/response/builtin.py @@ -0,0 +1,58 @@ +from typing import Dict, List, Optional + +from pydantic import BaseModel +from deepsecrets.core.model.finding import Finding +from deepsecrets.core.model.response.base import BaseResponseBuilder + + +class FindingApiModel(BaseModel): + line: Optional[str] + string: str + line_number: int + start_line_number: int + end_line_number: int + rule: str + reason: str + confidence: int + fingerprint: str + file_start_offset: int + file_end_offset: int + + @classmethod + def from_finding(cls, finding: Finding) -> 'FindingApiModel': + finding.choose_final_rule() + return FindingApiModel( + line=finding.full_line, + string=finding.detection, + line_number=finding.start_line_number, + start_line_number=finding.start_line_number, + end_line_number=finding.end_line_number, + rule=finding.final_rule.id, + reason=finding.get_reason(), + confidence=finding.final_rule.confidence, + fingerprint=finding.get_fingerprint(), + file_start_offset=finding.start_offset, + file_end_offset=finding.end_offset, + ) + + +class BuiltinFormatResponseBuilder(BaseResponseBuilder): + + def build(self) -> Dict[str, List[Dict]]: + resp: Dict[str, List[Dict]] = {} + for finding in self.findings: + if finding.file is None: + continue + + if finding.file.path not in resp: + resp[finding.file.path] = [] + + resp_finding = FindingApiModel.from_finding(finding) + + if self.masking_enabled: + resp_finding.line = resp_finding.line.replace(resp_finding.string, '*' * len(resp_finding.string)) + resp_finding.string = '*' * len(resp_finding.string) + + resp[finding.file.path].append(resp_finding.model_dump()) + + return resp diff --git a/deepsecrets/core/model/response/dojo_sarif.py b/deepsecrets/core/model/response/dojo_sarif.py new file mode 100644 index 0000000..ca1f9b3 --- /dev/null +++ b/deepsecrets/core/model/response/dojo_sarif.py @@ -0,0 +1,143 @@ +from sarif_om import ( + SarifLog, + Run, + Tool, + ToolComponent, + ReportingDescriptor, + ArtifactContent, + Result, + Region, + Message, + ArtifactLocation, + PhysicalLocation, + Location, +) +from deepsecrets.config import SCANNER_NAME, SCANNER_URL, SCANNER_VERSION +from deepsecrets.core.model import Finding +from typing import Dict + +from deepsecrets.core.model.response.base import BaseResponseBuilder + + +class DojoSarifResponseBuilder(BaseResponseBuilder): + + def _get_levels(self, finding: Finding): + if finding.final_rule.confidence > 5: + precision = 'high' + security_severity = 'High' + level = 'error' + elif finding.final_rule.confidence > 0: + precision = 'medium' + security_severity = 'High' + level = 'error' + else: + precision = 'low' + security_severity = 'Medium' + level = 'warning' + + return { + 'precision': precision, + 'security_severity': security_severity, + 'level': level, + } + + def build(self) -> SarifLog: # type: ignore + + report = SarifLog( + schema_uri='https://json.schemastore.org/sarif-2.1.0.json', + version='2.1.0', + runs=[ + Run( + tool=Tool( + driver=ToolComponent( + name=SCANNER_NAME, + semantic_version=SCANNER_VERSION, + information_uri=SCANNER_URL, + rules=[], + ) + ), + results=[], + ) + ], + ) + + sarif_rules: Dict[str, ReportingDescriptor] = {} + + for finding in self.findings: + + finding.choose_final_rule() + levels = self._get_levels(finding=finding) + + rule = ReportingDescriptor( + id=finding.final_rule.id, + short_description={'text': finding.final_rule.name}, + full_description={'text': finding.final_rule.name}, + help={'text': finding.final_rule.name}, + properties={ + 'security_severity': levels.get('security_severity'), + 'precision': levels.get('precision'), + }, + default_configuration={'level': levels.get('level')}, + ) + + sarif_rules[finding.final_rule.id] = rule + + region = self.get_region(finding=finding, masking=self.masking_enabled) + context_region = self.get_context_region(finding=finding, masking=self.masking_enabled) + + result = Result( + rule_id=finding.final_rule.id, + message=Message(text=f'Secret in code ({finding.final_rule.name})'), + locations=[ + Location( + physical_location=PhysicalLocation( + artifact_location=ArtifactLocation(uri=finding.file.relative_path, uri_base_id='%SRCROOT%'), + region=region, + context_region=context_region, + ) + ) + ], + ) + + report.runs[0].results.append(result) + + report.runs[0].tool.driver.rules = [rule for rule in sarif_rules.values()] + return report + + def get_context_region(self, finding: Finding, masking: bool = True): + + start_column = finding.file.get_column_number(position=finding.start_offset) + end_column = finding.file.get_column_number(position=finding.end_offset) + + boundaries, _ = self._get_context_boundaries(finding, start_column, end_column) + base_offset = finding.file.get_line_start_offset(finding.start_line_number) + snippet = finding.file.content[base_offset + boundaries[0] : base_offset + boundaries[1]] + + if masking: + snippet = self._mask(snippet=snippet, detection=finding.detection) + + return Region( + start_line=finding.start_line_number, + end_line=finding.end_line_number, + start_column=boundaries[0], + end_column=boundaries[1], + snippet=ArtifactContent(text=snippet), + ) + + def get_region(self, finding: 'Finding', masking: bool = True): + + start_column = finding.file.get_column_number(position=finding.start_offset) + end_column = finding.file.get_column_number(position=finding.end_offset) + + snippet = finding.detection + + if masking: + snippet = self._mask(snippet=snippet, detection=finding.detection) + + return Region( + start_line=finding.start_line_number, + end_line=finding.end_line_number, + start_column=start_column, + end_column=end_column, + snippet=ArtifactContent(text=snippet), + ) diff --git a/deepsecrets/core/modes/iscan_mode.py b/deepsecrets/core/modes/iscan_mode.py index 95c348c..01c55da 100644 --- a/deepsecrets/core/modes/iscan_mode.py +++ b/deepsecrets/core/modes/iscan_mode.py @@ -6,17 +6,19 @@ from multiprocessing.managers import DictProxy import os from abc import abstractmethod -from typing import Any, Callable, Dict, List, Optional, Type +from typing import Any, Callable, Dict, List, Optional, Tuple, Type from dotwiz import DotWiz from deepsecrets import PROFILER_ON, console from deepsecrets.config import Config -from deepsecrets.core.model.finding import Finding, FindingMerger +from deepsecrets.core.model.finding import Finding +from deepsecrets.core.model.internal.processing import PerFileAnalysisResult from deepsecrets.core.model.rules.exlcuded_path import ExcludePathRule from deepsecrets.core.rulesets.excluded_paths import ExcludedPathsBuilder from deepsecrets.core.rulesets.false_findings import FalseFindingsBuilder from deepsecrets.core.utils.file_analyzer import FileAnalyzer +from deepsecrets.core.utils.finding_merger import FindingMerger from deepsecrets.core.utils.fs import get_abspath from rich.progress import Progress as ProgressBar @@ -27,15 +29,15 @@ @dataclass class FileJob: internal_id: int - name: str pb_task_id: Optional[int] - result: AsyncResult + name: str @dataclass class Stats: total_files: int = 0 finished: int = 0 + failed_files: int = 0 tokens_processed: int = 0 total_findings: int = 0 @@ -100,12 +102,15 @@ def refresh_jobs_progress_bars(self): started: bool = current_state.get('started') finished: bool = current_state.get('finished') + failure: bool = current_state.get('failure') + size: str = current_state.get('file_size') if started is True and finished is False and job.pb_task_id is None: job.pb_task_id = self.progress_bar.add_task( f'[{job.internal_id}] {job.name.split("/")[-1]}', findings='0', + errors='', size='| ? Kb', ) @@ -113,10 +118,14 @@ def refresh_jobs_progress_bars(self): findings = current_state.get('findings', 0) if finished is True: - self.stats.tokens_processed += processed - self.stats.total_findings += findings - tasks_to_remove.append(internal_task_id) + + if failure is True: + self.stats.failed_files += 1 + else: + self.stats.tokens_processed += processed + self.stats.total_findings += findings + if job.pb_task_id is not None: try: self.progress_bar.remove_task(job.pb_task_id) @@ -148,24 +157,33 @@ def refresh_overall_progress_bar(self, pb_task_id): pb_task_id, completed=self.stats.finished, total=self.stats.total_files, - findings=f'FOUND: {self.stats.total_findings}', + findings=f'F: {self.stats.total_findings}', + errors=f'ERR: {self.stats.failed_files}', ) - def run(self) -> List[Finding]: + def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: final: List[Finding] = [] + errors: Dict[str, List[str]] = dict() + bundle = self.analyzer_bundle() proc_count = self._get_process_count_for_runner() if proc_count == 0: - return final + return final, errors overall_progress_task = self.progress_bar.add_task( - "[green bold]OVERALL PROGRESS\n", visible=True, findings='FOUND: 0', size='' + "[green bold]OVERALL PROGRESS\n", + visible=True, + findings='F: 0', + errors='ERR: 0', + size='', ) if PROFILER_ON: for file in self.filepaths: final.extend( - self._per_file_analyzer(file=file, bundle=bundle, task_id=0, task_reporter=self.task_reporter) + self._per_file_analyzer( + file=file, bundle=bundle, task_id=0, task_reporter=self.task_reporter + ).findings ) else: with self.pool_engine(processes=proc_count) as pool: @@ -181,7 +199,6 @@ def run(self) -> List[Finding]: self.file_jobs[tid] = FileJob( name=file, internal_id=tid, - result=result, pb_task_id=None, ) pool.close() @@ -195,10 +212,13 @@ def run(self) -> List[Finding]: self.progress_bar.stop() for job_result in self.file_results: - file_findings = job_result.get() - if file_findings is None or len(file_findings) == 0: + analysis_result: PerFileAnalysisResult = job_result.get() + job = self.file_jobs.get(analysis_result.internal_task_id) + errors[job.name] = analysis_result.errors + + if analysis_result.findings is None or len(analysis_result.findings) == 0: continue - final.extend(file_findings) + final.extend(analysis_result.findings) console.line() console.print('[*] Merging similar findings..') @@ -206,7 +226,7 @@ def run(self) -> List[Finding]: console.print('[*] Filtering predefined false Findings..') fin = self.filter_false_positives(fin) - return fin + return fin, errors def dispose(self): self.task_reporter = None @@ -280,7 +300,7 @@ def analyzer_bundle(self) -> DotWiz: @staticmethod @abstractmethod - def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, task_reporter: Optional[Any] = None) -> List[Finding]: # type: ignore + def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, task_reporter: Optional[Any] = None) -> PerFileAnalysisResult: # type: ignore pass def filter_false_positives(self, results: List[Finding]) -> List[Finding]: @@ -305,6 +325,6 @@ def filter_false_positives(self, results: List[Finding]) -> List[Finding]: def pool_wrapper( bundle: DotWiz, runner: Callable, task_id: Optional[int], task_reporter: DictProxy, file: str -) -> List[Finding]: # pragma: nocover +) -> PerFileAnalysisResult: # pragma: nocover result = runner(bundle, file, task_id, task_reporter) return result diff --git a/deepsecrets/core/utils/file_analyzer.py b/deepsecrets/core/utils/file_analyzer.py index 4476ef3..0c2bf2c 100644 --- a/deepsecrets/core/utils/file_analyzer.py +++ b/deepsecrets/core/utils/file_analyzer.py @@ -54,8 +54,10 @@ def process(self) -> List[Finding]: try: for et in self.engine_tokenizers: results.extend(self._run_engine(et)) - except Exception: - pass + except Exception as e: + logger.exception(e) + self.lifecycle.on_failure() + return results self.lifecycle.on_finish() return results @@ -89,7 +91,7 @@ def _run_engine(self, et: EngineWithTokenizer) -> List[Finding]: self.lifecycle.on_token_processing_end(len(findings)) except Exception as e: - logger.exception('Unable to process token', extra={'info': e}) + logger.exception(f'Unable to process token: {e}') continue return results diff --git a/deepsecrets/core/utils/finding_merger.py b/deepsecrets/core/utils/finding_merger.py new file mode 100644 index 0000000..0fe0793 --- /dev/null +++ b/deepsecrets/core/utils/finding_merger.py @@ -0,0 +1,22 @@ +from typing import Dict, List + +from deepsecrets.core.model.finding import Finding + + +class FindingMerger: + all: List[Finding] + + def __init__(self, full_list: List[Finding]) -> None: + self.all = full_list + + def merge(self) -> List[Finding]: + interm_dict: Dict[int, Finding] = {} + + for elem in self.all: + hash = elem.__hash__() + if hash not in interm_dict: + interm_dict[hash] = elem + + interm_dict[hash].merge(elem) + + return list(interm_dict.values()) diff --git a/deepsecrets/core/utils/lifecycle_hooks.py b/deepsecrets/core/utils/lifecycle_hooks.py index 3a7f7bc..fb8229a 100644 --- a/deepsecrets/core/utils/lifecycle_hooks.py +++ b/deepsecrets/core/utils/lifecycle_hooks.py @@ -18,6 +18,10 @@ def on_start(self): self.progress.on_start() self._report() + def on_failure(self, child_report: Optional[dict] = None): + self.progress.on_failure() + self._report(child_report) + def on_finish(self, child_report: Optional[dict] = None): self.progress.on_finish() self._report(child_report) diff --git a/deepsecrets/core/utils/log.py b/deepsecrets/core/utils/log.py index 429019f..d9c4369 100644 --- a/deepsecrets/core/utils/log.py +++ b/deepsecrets/core/utils/log.py @@ -1,8 +1,18 @@ import logging import multiprocessing +from typing import List from deepsecrets import MODULE_NAME +class ErrorListHandler(logging.Handler): + def __init__(self): + super().__init__() + self.records = [] + + def emit(self, record): + self.records.append(self.format(record)) + + def set_logging_level(logger: logging.Logger, level: int) -> None: logger.setLevel(level) for handler in logger.handlers: @@ -19,7 +29,20 @@ def build_logger(level: int = logging.INFO) -> logging.Logger: logging.basicConfig(format=' %(message)s', level=level) logger = logging.getLogger(MODULE_NAME) set_logging_level(logger=logger, level=level) + logger.addHandler(ErrorListHandler()) return logger +def get_error_list() -> List[str]: + if logger.hasHandlers() is False: + return [] + + for handler in logger.handlers: + if not isinstance(handler, ErrorListHandler): + continue + + return handler.records + return [] + + logger = build_logger() diff --git a/deepsecrets/core/utils/progress.py b/deepsecrets/core/utils/progress.py index db724a5..3fb648f 100644 --- a/deepsecrets/core/utils/progress.py +++ b/deepsecrets/core/utils/progress.py @@ -5,10 +5,12 @@ class Progress: started: bool finished: bool + failure: bool def __init__(self) -> None: self.started = False self.finished = False + self.failure = False def on_start(self): self.started = True @@ -17,12 +19,19 @@ def on_finish(self): self.finished = True self.started = False + def on_failure(self): + self.failure = True + self.finished = True + def report(self, child_report: Optional[dict] = None): child_report = child_report if child_report is not None else dict() - return child_report | { + + merged = dict(child_report) | { 'started': self.started, + 'failure': self.failure, 'finished': self.finished, } + return merged class FileProgress(Progress): diff --git a/deepsecrets/core/utils/sarif_helper.py b/deepsecrets/core/utils/sarif_helper.py deleted file mode 100644 index be76746..0000000 --- a/deepsecrets/core/utils/sarif_helper.py +++ /dev/null @@ -1,64 +0,0 @@ -import sarif_om as om - - -class SarifHelper: - - @classmethod - def get_context_region_for_finding(cls, finding: 'Finding', masking: bool = True): - - start_column = finding.file.get_column_number(position=finding.start_pos) - end_column = finding.file.get_column_number(position=finding.end_pos) - - boundaries = cls._get_context_boundaries(finding, start_column, end_column) - base_offset = finding.file.get_line_offset(finding.linum) - snippet = finding.file.content[base_offset + boundaries[0] : base_offset + boundaries[1]] - - if masking: - snippet = cls._mask(snippet=snippet, detection=finding.detection) - - return om.Region( - start_line=finding.linum, - start_column=boundaries[0], - end_column=boundaries[1], - snippet=om.ArtifactContent(text=snippet), - ) - - @classmethod - def get_region_for_finding(cls, finding: 'Finding', masking: bool = True): - - start_column = finding.file.get_column_number(position=finding.start_pos) - end_column = finding.file.get_column_number(position=finding.end_pos) - - snippet = finding.detection - - if masking: - snippet = cls._mask(snippet=snippet, detection=finding.detection) - - return om.Region( - start_line=finding.linum, - end_line=finding.linum, - start_column=start_column, - end_column=end_column, - snippet=om.ArtifactContent(text=snippet), - ) - - @classmethod - def _get_context_boundaries(self, finding: 'Finding', start_column: int, end_column: int): - line_length = len(finding.full_line) - boundaries = [0, line_length] - - if start_column > MAX_LINE_LENGTH_FOR_CONTEXT: - boundaries[0] = int(start_column - MAX_LINE_LENGTH_FOR_CONTEXT / 2) - remaining_line = line_length - end_column - boundaries[1] = int(end_column + (MAX_LINE_LENGTH_FOR_CONTEXT / 2 - remaining_line)) - - return boundaries - - @classmethod - def _mask(cls, snippet: str, detection: str): - masked_detection = '*' * len(detection) - return snippet.replace(detection, masked_detection) - - -from deepsecrets.config import MAX_LINE_LENGTH_FOR_CONTEXT -from deepsecrets.core.model.finding import Finding diff --git a/deepsecrets/scan_modes/cli.py b/deepsecrets/scan_modes/cli.py index 7b2b0bd..aa1a741 100644 --- a/deepsecrets/scan_modes/cli.py +++ b/deepsecrets/scan_modes/cli.py @@ -1,6 +1,6 @@ import logging import os -from typing import Any, Dict, List, Type, Optional +from typing import Any, Dict, Type, Optional from dotwiz import DotWiz @@ -9,14 +9,14 @@ from deepsecrets.core.engines.regex import RegexEngine from deepsecrets.core.engines.semantic import SemanticEngine from deepsecrets.core.model.file import File -from deepsecrets.core.model.finding import Finding from deepsecrets.core.modes.iscan_mode import ScanMode +from deepsecrets.core.model.internal.processing import PerFileAnalysisResult from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.core.tokenizers.full_content import FullContentTokenizer from deepsecrets.core.tokenizers.lexer import LexerTokenizer from deepsecrets.core.utils.lifecycle_hooks import JobLifecycleHooks -from deepsecrets.core.utils.log import logger +from deepsecrets.core.utils.log import get_error_list, logger from deepsecrets.core.utils.file_analyzer import FileAnalyzer from deepsecrets.core.utils.progress import Progress @@ -51,7 +51,12 @@ def analyzer_bundle(self) -> DotWiz: return bundle @staticmethod - def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, task_reporter: Optional[Any] = None) -> List[Finding]: # type: ignore + def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, task_reporter: Optional[Any] = None) -> PerFileAnalysisResult: # type: ignore + + def __finalize(result: PerFileAnalysisResult): + result.errors = get_error_list() + return result + progress = Progress() lifecycle = JobLifecycleHooks( task_id=task_id, @@ -63,7 +68,7 @@ def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, ta if logger.level == logging.DEBUG: pass - results: List[Finding] = [] + result = PerFileAnalysisResult(findings=[], errors=[], internal_task_id=task_id) if not isinstance(file, str): raise Exception('Filepath as str expected') @@ -71,13 +76,13 @@ def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, ta try: file = File(path=file, relative_path=file.replace(f'{bundle.workdir}/', '')) except Exception as e: - logger.error('Unable to open the file', extra={'message': e}) - lifecycle.on_finish() - return results + logger.error(f'Unable to open the file: {e}') + lifecycle.on_failure(task_reporter[task_id]) + return __finalize(result) if file.length == 0: - lifecycle.on_finish() - return results + lifecycle.on_finish(task_reporter[task_id]) + return __finalize(result) file_analyzer = FileAnalyzer(file) file_analyzer.attach_global_task_reporter(task_reporter=task_reporter, task_id=task_id) @@ -107,12 +112,12 @@ def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, ta file_analyzer.add_engine(semantic_engine, [lex]) try: - results = file_analyzer.process() + result.findings = file_analyzer.process() except Exception as e: logger.exception(e) if PROFILER_ON: pass - lifecycle.on_finish() - return results + lifecycle.on_finish(task_reporter[task_id]) + return __finalize(result) diff --git a/tests/core/engines/regex/test_regex.py b/tests/core/engines/regex/test_regex.py index 3dd792f..2910be9 100644 --- a/tests/core/engines/regex/test_regex.py +++ b/tests/core/engines/regex/test_regex.py @@ -16,11 +16,13 @@ def file(): path = 'tests/fixtures/regex_checks.txt' return File(path=path, relative_path=path) + @pytest.fixture(scope='module') def file_extless(): path = 'tests/fixtures/extless/radius' return File(path=path, relative_path=path) + @pytest.fixture(scope='module') def file_go_7(): path = 'tests/fixtures/7.go' @@ -44,7 +46,7 @@ def test_1(file: File, regex_engine: RegexEngine): findings.append(finding) for finding in findings: - finding.map_on_file(file=file, relative_start=finding.start_pos) + finding.map_on_file(file=file, relative_start=finding.start_offset) finding.choose_final_rule() assert len(findings) == 10 @@ -79,14 +81,13 @@ def test_extless(file_extless: File, regex_engine: RegexEngine): findings.append(finding) for finding in findings: - finding.map_on_file(file=file_extless, relative_start=finding.start_pos) + finding.map_on_file(file=file_extless, relative_start=finding.start_offset) finding.choose_final_rule() assert len(findings) == 1 assert findings[0].rules[0].id == 'S28' - def test_go_7(file_go_7: File, regex_engine: RegexEngine): findings: List[Finding] = [] tokens = FullContentTokenizer().tokenize(file_go_7) @@ -98,7 +99,7 @@ def test_go_7(file_go_7: File, regex_engine: RegexEngine): findings.append(finding) for finding in findings: - finding.map_on_file(file=file_go_7, relative_start=finding.start_pos) + finding.map_on_file(file=file_go_7, relative_start=finding.start_offset) finding.choose_final_rule() assert len(findings) == 0 diff --git a/tests/core/engines/semantic/test_semantic.py b/tests/core/engines/semantic/test_semantic.py index 3d9d861..17c5ce3 100644 --- a/tests/core/engines/semantic/test_semantic.py +++ b/tests/core/engines/semantic/test_semantic.py @@ -123,7 +123,7 @@ def test_5_semantic_engine(file_sh_2: File): findings.extend(engine.search(token)) for finding in findings: - finding.map_on_file(file=file_sh_2, relative_start=finding.start_pos) + finding.map_on_file(file=file_sh_2, relative_start=finding.start_offset) finding.choose_final_rule() findings = FindingMerger(findings).merge() @@ -142,7 +142,7 @@ def test_6_semantic_engine(file_html_1: File): findings.extend(engine.search(token)) for finding in findings: - finding.map_on_file(file=file_html_1, relative_start=finding.start_pos) + finding.map_on_file(file=file_html_1, relative_start=finding.start_offset) finding.choose_final_rule() findings = FindingMerger(findings).merge() diff --git a/tests/core/model/test_finding.py b/tests/core/model/test_finding.py index ca3d91b..f49f7e9 100644 --- a/tests/core/model/test_finding.py +++ b/tests/core/model/test_finding.py @@ -5,9 +5,7 @@ from deepsecrets.core.model.rules.rule import Rule from deepsecrets.core.model.token import Token -TEST_TOKEN_CONTENTS = ( - '"amqp://fake_user:TESTSECRET1234@rabbitmq-esp01.miami.example.com:5672/esp"' -) +TEST_TOKEN_CONTENTS = '"amqp://fake_user:TESTSECRET1234@rabbitmq-esp01.miami.example.com:5672/esp"' TOKEN_SPAN = (76, 151) FINDING_CONTENT = 'TESTSECRET1234' @@ -40,15 +38,13 @@ def test_1_finding(file: File, token: Token, rule: Rule): new_finding = Finding( file=file, rules=[rule], - start_pos=FINDING_SPAN_INSIDE_TOKEN[0], - end_pos=FINDING_SPAN_INSIDE_TOKEN[1], - detection=token.content[ - FINDING_SPAN_INSIDE_TOKEN[0] : FINDING_SPAN_INSIDE_TOKEN[1] - ], + start_offset=FINDING_SPAN_INSIDE_TOKEN[0], + end_offset=FINDING_SPAN_INSIDE_TOKEN[1], + detection=token.content[FINDING_SPAN_INSIDE_TOKEN[0] : FINDING_SPAN_INSIDE_TOKEN[1]], ) assert new_finding.detection == FINDING_CONTENT new_finding.map_on_file(relative_start=token.span[0]) - assert new_finding.start_pos == TOKEN_SPAN[0] + FINDING_SPAN_INSIDE_TOKEN[0] - assert new_finding.end_pos == TOKEN_SPAN[0] + FINDING_SPAN_INSIDE_TOKEN[1] + assert new_finding.start_offset == TOKEN_SPAN[0] + FINDING_SPAN_INSIDE_TOKEN[0] + assert new_finding.end_offset == TOKEN_SPAN[0] + FINDING_SPAN_INSIDE_TOKEN[1] From 1f855eca641b366db164c43135c5f9efad04ce28 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Dec 2025 22:31:10 +0300 Subject: [PATCH 03/20] Massive improvements --- benchmarker/models/secretbench.py | 575 ++++++++ benchmarker/models/verification.py | 46 + benchmarker/per_bucket.py | 26 + benchmarker/run_bucket_benchmark.sh | 1 + deepsecrets/cli.py | 59 +- deepsecrets/config.py | 18 + deepsecrets/core/engines/semantic.py | 172 +-- .../core/helpers/variable_evaluator.py | 154 ++ deepsecrets/core/model/file.py | 41 +- deepsecrets/core/model/finding.py | 4 + deepsecrets/core/model/internal/processing.py | 6 +- deepsecrets/core/model/response/base.py | 2 +- deepsecrets/core/model/response/builtin.py | 4 +- deepsecrets/core/model/response/dojo_sarif.py | 133 +- deepsecrets/core/model/rules/rule.py | 13 +- deepsecrets/core/model/rules/semantic.py | 5 - .../core/model/rules/variable_scoring.py | 35 + deepsecrets/core/model/semantic.py | 69 + deepsecrets/core/model/token.py | 18 +- deepsecrets/core/model/tokenized_region.py | 12 + deepsecrets/core/modes/iscan_mode.py | 65 +- deepsecrets/core/rulesets/ibuilder.py | 6 +- deepsecrets/core/rulesets/variable_scoring.py | 7 + .../helpers/semantic/deep_analyzer.py | 125 ++ .../tokenizers/helpers/semantic/language.py | 3 +- .../semantic/var_detection/detector.py | 38 +- .../helpers/semantic/var_detection/rules.py | 35 +- ...provements.py => single_token_improver.py} | 21 +- .../helpers/subfile_regions_helper.py | 129 ++ .../core/tokenizers/helpers/type_stream.py | 9 +- deepsecrets/core/tokenizers/itokenizer.py | 25 + deepsecrets/core/tokenizers/lexer.py | 145 +- deepsecrets/core/utils/file_analyzer.py | 21 +- deepsecrets/core/utils/finding_merger.py | 5 +- deepsecrets/core/utils/guess_filetype.py | 26 +- deepsecrets/core/utils/lexer_finder.py | 34 +- deepsecrets/core/utils/lifecycle_hooks.py | 16 +- deepsecrets/core/utils/progress.py | 83 +- deepsecrets/core/utils/string.py | 24 +- deepsecrets/rules/regexes.json | 23 +- deepsecrets/rules/variable_scoring_rules.json | 95 ++ deepsecrets/scan_modes/cli.py | 13 +- poetry.lock | 84 +- pyproject.toml | 1 + tests/case_helpers.py | 36 + tests/cli/test_cli.py | 6 +- tests/conftest.py | 78 + tests/core/cohesive/test_specific_cases.py | 44 + tests/core/engines/hashed_secret/test_hs.py | 30 +- tests/core/engines/regex/test_regex.py | 121 +- tests/core/engines/semantic/test_ruleset.py | 8 + tests/core/engines/semantic/test_semantic.py | 157 +- tests/core/helpers/test_content_analyzer.py | 2 +- tests/core/helpers/test_entropy.py | 2 +- tests/core/helpers/test_variable_evaluator.py | 95 ++ tests/core/model/test_file.py | 118 +- tests/core/model/test_finding.py | 18 +- tests/core/model/test_token.py | 14 +- tests/core/model/test_variable_context.py | 57 + .../lexer/variable_detection/test_conf.py | 111 +- .../lexer/variable_detection/test_cs.py | 16 +- .../lexer/variable_detection/test_go.py | 97 +- .../lexer/variable_detection/test_html.py | 15 +- .../lexer/variable_detection/test_java.py | 19 +- .../lexer/variable_detection/test_js.py | 63 +- .../test_markdown_with_code_blocks.py | 11 + .../lexer/variable_detection/test_php.py | 9 +- .../lexer/variable_detection/test_py.py | 70 +- .../lexer/variable_detection/test_sh.py | 15 +- .../lexer/variable_detection/test_swift.py | 19 +- tests/core/tokenizers/test_full_content.py | 14 +- tests/core/tokenizers/test_per_line.py | 12 +- tests/core/utils/test_file_analyzer.py | 12 +- tests/core/utils/test_lexer_finder.py | 50 +- tests/core/utils/test_string.py | 3 +- tests/fixtures/1.go | 2 + tests/fixtures/1.pem | 10 + tests/fixtures/2.html | 1 + tests/fixtures/2.java | 59 + tests/fixtures/3.conf | 123 ++ tests/fixtures/3.html | 22 + tests/fixtures/8.go | 552 +++++++ tests/fixtures/cases/bash_in_markdown_1.md | 308 ++++ tests/fixtures/cases/code_in_markdown.md | 292 ++++ tests/fixtures/cases/code_in_markdown_2.md | 320 ++++ .../code_in_markdown_with_lang_labels.md | 87 ++ tests/fixtures/cases/html_inside_python.py | 1300 +++++++++++++++++ .../fixtures/cases/inline_yaml_in_markdown.md | 33 + .../cases/inline_yaml_inside_yaml.yaml | 24 + ...inline_yaml_inside_yaml_inside_markdown.md | 33 + tests/fixtures/cases/json_in_markdown.md | 292 ++++ tests/fixtures/cases/markdown_with_yaml.txt | 71 + tests/fixtures/cases/secret_in_querystring.py | 7 + tests/fixtures/cases/secret_in_yml_string.yml | 54 + tests/fixtures/cases/tricky_secrets.min.js | 26 + tests/fixtures/regex_checks.txt | 6 +- .../test_run_full_scan.py | 5 +- tests/output/test_sarif.py | 24 +- tests/scan_modes/test_cli_scan_mode.py | 4 +- 99 files changed, 6313 insertions(+), 1090 deletions(-) create mode 100644 benchmarker/models/secretbench.py create mode 100644 benchmarker/models/verification.py create mode 100644 benchmarker/per_bucket.py create mode 100755 benchmarker/run_bucket_benchmark.sh create mode 100644 deepsecrets/core/helpers/variable_evaluator.py delete mode 100644 deepsecrets/core/model/rules/semantic.py create mode 100644 deepsecrets/core/model/rules/variable_scoring.py create mode 100644 deepsecrets/core/model/tokenized_region.py create mode 100644 deepsecrets/core/rulesets/variable_scoring.py create mode 100644 deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py rename deepsecrets/core/tokenizers/helpers/{spot_improvements.py => single_token_improver.py} (74%) create mode 100644 deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py create mode 100644 deepsecrets/rules/variable_scoring_rules.json create mode 100644 tests/case_helpers.py create mode 100644 tests/conftest.py create mode 100644 tests/core/cohesive/test_specific_cases.py create mode 100644 tests/core/engines/semantic/test_ruleset.py create mode 100644 tests/core/helpers/test_variable_evaluator.py create mode 100644 tests/core/model/test_variable_context.py create mode 100644 tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py create mode 100644 tests/fixtures/1.pem create mode 100644 tests/fixtures/2.html create mode 100644 tests/fixtures/2.java create mode 100644 tests/fixtures/3.conf create mode 100644 tests/fixtures/3.html create mode 100644 tests/fixtures/8.go create mode 100644 tests/fixtures/cases/bash_in_markdown_1.md create mode 100644 tests/fixtures/cases/code_in_markdown.md create mode 100644 tests/fixtures/cases/code_in_markdown_2.md create mode 100644 tests/fixtures/cases/code_in_markdown_with_lang_labels.md create mode 100644 tests/fixtures/cases/html_inside_python.py create mode 100644 tests/fixtures/cases/inline_yaml_in_markdown.md create mode 100644 tests/fixtures/cases/inline_yaml_inside_yaml.yaml create mode 100644 tests/fixtures/cases/inline_yaml_inside_yaml_inside_markdown.md create mode 100644 tests/fixtures/cases/json_in_markdown.md create mode 100644 tests/fixtures/cases/markdown_with_yaml.txt create mode 100644 tests/fixtures/cases/secret_in_querystring.py create mode 100644 tests/fixtures/cases/secret_in_yml_string.yml create mode 100644 tests/fixtures/cases/tricky_secrets.min.js diff --git a/benchmarker/models/secretbench.py b/benchmarker/models/secretbench.py new file mode 100644 index 0000000..42aeff1 --- /dev/null +++ b/benchmarker/models/secretbench.py @@ -0,0 +1,575 @@ +import csv +from dataclasses import dataclass +from datetime import datetime +import json +import os +import sqlite3 +from typing import Dict, List + +from filelock import FileLock + +from benchmarker.models.verification import FileVerificationResult, SecretPointer +from deepsecrets.cli import DeepSecretsCliTool +from deepsecrets.core.model.file import File +from deepsecrets.core.model.finding import Finding +import pandas as pd + + +@dataclass +class Secret: + id: int + secret: str + repo_name: str + file_path: str + file_type: str + start_line: int + end_line: int + start_column: int + end_column: int + label: bool + in_url: bool + entropy: float + length: int + is_multiline: bool + file_identifier: str + comment: str + + def __hash__(self) -> int: + return self.id + + def __eq__(self, value: object) -> bool: + if not isinstance(value, Secret): + return False + + if value.file_identifier != self.file_identifier: + return False + + if value.secret != self.secret: + return False + + if value.start_line != self.start_line: + return False + + return True + + +class Comparison: + bench_secret: Secret = None + tool_finding: Secret = None + + def __init__(self, bench_secret: Secret) -> None: + self.bench_secret = bench_secret + + +prefix_all_found = 'AF' +prefix_not_all = 'NA' + + +class TestingScope: + files: set[str] + bucket_dir: str + target_dir: str + verification_dir: str + _bench_data: Dict[int, Secret] + _tool_results: List[Finding] + comparisons: List[Comparison] + verification_file: str + + verification_cache: Dict[str, FileVerificationResult] + db: sqlite3.Connection + + def _get_files_list(self, target_dir: str): + ''' + return ['arangodb-arangodb_e75b8f550387a7a4ea44a1a5ffa2e600eb645e92_3rdParty-curl-curl-7.50.3-CHANGES.0'] + ''' + files_list = os.listdir(target_dir) + return sorted(files_list, key=lambda x: os.stat(os.path.join(target_dir, x)).st_size) + return set(os.listdir(target_dir)) + + def _parse_tool_results(self, tool_results_path: str): + if tool_results_path is None: + return + + results = [] + report: dict + with open(tool_results_path) as file: + report = json.loads(file.read()) + + for result in report['runs'][0]['results']: + secret = result['locations'][0]['physicalLocation']['region']['snippet']['text'] + results.append( + Secret( + secret=secret, + file_identifier=result['locations'][0]['physicalLocation']['artifactLocation']['uri'], + start_line=result['locations'][0]['physicalLocation']['region']['startLine'], + end_line=result['locations'][0]['physicalLocation']['region']['endLine'], + start_column=result['locations'][0]['physicalLocation']['region']['startColumn'], + end_column=result['locations'][0]['physicalLocation']['region']['endColumn'], + length=len(secret), + repo_name=None, + file_type=None, + is_multiline=None, + label=None, + in_url=None, + id=None, + file_path=None, + entropy=None, + comment=None, + ) + ) + return results + + def write_verification_file(self, file, result: FileVerificationResult): + effective_prefix = prefix_all_found if result.all_found is True else prefix_not_all + path = f'{self.verification_dir}/{self.bucket_dir}/{effective_prefix}_{file}.json' + os.makedirs(f'{self.verification_dir}/{self.bucket_dir}', exist_ok=True) + + try: + with open(path, 'w+') as vf: + vf.write(result.model_dump_json(exclude_none=True, indent=2)) + except Exception: + pass + + self.update_summary_file(result) + + def update_summary_file(self, result: FileVerificationResult): + summary_file = f'{self.verification_dir}/summary.csv' + lock_file = f'{summary_file}.lock' + + lock = FileLock(lock_file) + with lock: + if os.path.exists(summary_file): + df = pd.read_csv(summary_file) + else: + # Create an empty DataFrame if file doesn't exist yet + df = pd.DataFrame( + columns=[ + 'file', + 'sb_secrets', + 'sb_valid', + 'sb_falses', + 'ds_found_sb_valids', + 'ds_found_sb_falses', + 'ds_found_extra', + 'updated_ts', + ] + ) + + df = pd.read_csv(summary_file) + data = { + 'file': result.file_identifier, + 'sb_secrets': result.sb_secrets_count, + 'sb_valid': result.sb_valid_secrets_count, + 'sb_falses': result.sb_false_secrets_count, + 'ds_found_sb_valids': len(result.found_valid_secret_ids), + 'ds_found_sb_falses': len(result.found_false_secret_ids), + 'ds_found_extra': result.extra_secrets_count, + 'updated_ts': datetime.now(), + } + + key_value = data['file'] + if key_value in df['file'].values: + mask = df['file'] == key_value + for col, value in data.items(): + df.loc[mask, col] = value + else: + df = pd.concat([df, pd.DataFrame([data])], ignore_index=True) + + df.to_csv(summary_file, index=False) + + def is_connection_active(self): + """ + Checks if a sqlite3 connection object is still active. + Returns True if active, False otherwise. + """ + if self.db is None: + return False + try: + # Attempt to create a cursor or execute a simple query + self.db.cursor() + return True + except sqlite3.ProgrammingError: + # This exception is typically raised if the connection is closed + return False + except Exception as e: + # Catch other potential exceptions if the connection is in a bad state + print(f"An unexpected error occurred: {e}") + return False + + def write_verification_db(self, file: str, result: FileVerificationResult): + + if self.is_connection_active() is False: + self.db = sqlite3.connect(os.path.join(self.verification_dir, 'db', 'verifications.db')) + + # Create a cursor object to execute SQL commands + cursor = self.db.cursor() + # Create a table + cursor.execute( + ''' + INSERT OR REPLACE + INTO FileReports + (file, sb_secrets_count, sb_valid_count, sb_falses, ds_found_sb_valids, ds_found_sb_falses, ds_found_extra, updated_ts) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', + ( + result.file_identifier, + result.sb_secrets_count, + result.sb_valid_secrets_count, + result.sb_false_secrets_count, + len(result.found_valid_secret_ids), + len(result.found_false_secret_ids), + result.extra_secrets_count, + int(datetime.now().timestamp()), + ), + ) + self.db.commit() + + pointer: SecretPointer + for secret_id, pointer in result.report.items(): + cursor.execute( + ''' + INSERT OR REPLACE + INTO Secrets + (id, file, content, line_number, line_offset, valid, extra, comment, ds_found) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', + ( + secret_id, + result.file_identifier, + pointer.detection, + pointer.line_number, + pointer.line_offset, + pointer.is_valid, + pointer.is_extra, + f'{pointer.internal_score} | {pointer.rule_id}', + pointer.found, + ), + ) + self.db.commit() + + def init( + self, + bucket_dir: str, + target_dir: str, + dataset_location: str, + verification_dir: str, + ): + + self._bench_data = {} + self._tool_results = [] + self.verification_dir = verification_dir + self.bucket_dir = bucket_dir + self.target_dir = target_dir + self.files = self._get_files_list(os.path.join(self.target_dir, self.bucket_dir)) + self.comparisons = [] + self.db = None + + with open(dataset_location) as file: + reader = csv.DictReader(file, delimiter=',') + for row in reader: + secret = Secret( + id=int(row['id']), + secret=row['secret'][1:-1], + repo_name=row['repo_name'], + file_path=row['file_path'], + file_type=row['file_type'], + start_line=int(row['start_line']), + start_column=int(row['start_column']), + end_line=int(row['end_line']), + end_column=int(row['end_column']), + label=True if row['label'] == 'Y' else False, + in_url=True if row['in_url'] == 'Y' else False, + entropy=float(row['entropy']), + length=int(row['length']), + is_multiline=True if row['is_multiline'] == 'Y' else False, + file_identifier=row['file_identifier'], + comment=row['comment'], + ) + + if secret.file_identifier not in self.files: + continue + self._bench_data[secret.id] = secret + + # self._tool_results = self._parse_tool_results(tool_results_path) + + def get_secrets_for_file(self, file: str, only_valid: bool): + return { + id: secret + for id, secret in self._bench_data.items() + if secret.file_identifier == file and (True if only_valid is False else secret.label == only_valid) + } + + def check_file_to_skip(self, file): + path = f'{self.verification_dir}/{self.bucket_dir}/{prefix_all_found}_{file}.json' + if os.path.exists(path): + return True + + path = f'{self.verification_dir}/{self.bucket_dir}/{prefix_not_all}_{file}.json' + if os.path.exists(path): + return True + + return False + + def start_incremental(self): + total_count = len(self.files) + for index, file in enumerate(self.files): + print(f'===================== FILE {index+1} of {total_count} =====================') + + skip_this_file = self.check_file_to_skip(file) + if skip_this_file is True: + continue + + path = f'{self.target_dir}/{self.bucket_dir}/{file}' + relevant_secrets = self.get_secrets_for_file(file, only_valid=False) + args = [ + '', + '--target-dir', + '', + '--oneshot', + f'{path}', + '--benchmarking-mode', + '--false-findings', + '/app/tests/fixtures/false_findings.json', + '--excluded-paths', + 'disable', + '--outfile', + f'./{file}_report.json', + '--outformat', + 'dojo-sarif', + ] + findings: set[Finding] + ds_file: File + findings, errors, ds_file = DeepSecretsCliTool(args).start() + findings = set(findings) + + file_verification_result: FileVerificationResult = FileVerificationResult(file_identifier=file) + file_verification_result.sb_secrets_count = len(relevant_secrets.keys()) + + for id, secret in relevant_secrets.items(): + if secret.label is True: + file_verification_result.sb_valid_secrets_count += 1 + else: + file_verification_result.sb_false_secrets_count += 1 + + found = False + # duplicates: [31860, 31861], [93022, 93024] + # others: bad secret values that not match with the original files + # easy to cover: [ + # 42815 (tokenimprover for links and querystrings), + # 94321 (rsa key without end header) + # ] + # currently unable to find: [94925, 24676(indented-md-yaml), 11549(html-inside-py)] + # potentially false positives: [32426] + # error files: + # - coinapi-coinapi-sdk_eb8cf18f1d5437e96e27df8a0d450cf65ae… + if id in [87344, 40571, 40526, 33888, 5956, 31860, 31861, 93022, 93024]: + found = True + file_verification_result.report[id] = SecretPointer( + found=found, + secret_id=id, + line_number=secret.start_line, + line_offset=secret.start_column, + detection=secret.secret, + context=None, + is_valid=secret.label, + ) + continue + + for finding in findings: + if finding.detection.replace(' ', '').replace('\n', '').replace('.', '') != secret.secret.replace( + ' ', '' + ).replace('\n', '').replace('.', ''): + continue + + if finding.start_line_number != secret.start_line: + continue + + found = True + file_verification_result.report[id] = SecretPointer( + found=found, + secret_id=id, + line_number=secret.start_line, + line_offset=secret.start_column, + detection=secret.secret, + is_valid=secret.label, + ) + findings.remove(finding) + break + + if found is False: + start_offset = ds_file.get_offset(line=secret.start_line, column=secret.start_column) + end_offset = ds_file.get_offset(line=secret.end_line, column=secret.end_column) + boundaries = ds_file.check_boundaries([start_offset - 50, end_offset + 50]) + + context = ds_file.content[boundaries[0] : boundaries[1]] + file_verification_result.report[id] = SecretPointer( + secret_id=id, + found=found, + line_number=secret.start_line, + is_valid=secret.label, + context=context, + ) + + if secret.label is True: + file_verification_result.all_found = False + file_verification_result.not_found_valid_secret_ids.append(id) + else: + file_verification_result.not_found_false_secret_ids.append(id) + + else: + if secret.label is True: + file_verification_result.found_valid_secret_ids.append(secret.id) + else: + file_verification_result.found_false_secret_ids.append(secret.id) + + for finding in findings: + boundaries = ds_file.check_boundaries([finding.start_offset - 50, finding.end_offset + 50]) + context = ds_file.content[boundaries[0] : boundaries[1]] + id = finding.get_id() + file_verification_result.report[id] = SecretPointer( + found=True, + secret_id=id, + line_number=finding.start_line_number, + context=context, + detection=finding.detection, + internal_score=finding.internal_score, + is_extra=True, + is_valid=True, + rule_id=finding.rules[0].id, + ) + file_verification_result.extra_secrets_count += 1 + + self.write_verification_file(file, file_verification_result) + + def start_per_bucket(self): + path = f'{self.target_dir}/{self.bucket_dir}' + args = [ + '', + '--target-dir', + f'{path}', + '--benchmarking-mode', + '--false-findings', + '/app/tests/fixtures/false_findings.json', + '--excluded-paths', + 'disable', + '--outfile', + f'./{self.bucket_dir}_report.json', + '--outformat', + 'dojo-sarif', + ] + + findings: set[Finding] + findings, _, _ = DeepSecretsCliTool(args).start() + findings = set(findings) + + findings: Dict[str, List[Finding]] = self.dictify_findings_list(findings) + for current_file in self.files: + relevant_secrets = self.get_secrets_for_file(current_file, only_valid=False) + file_verification_result: FileVerificationResult = FileVerificationResult(file_identifier=current_file) + file_verification_result.sb_secrets_count = len(relevant_secrets.keys()) + + for id, secret in relevant_secrets.items(): + if secret.label is True: + file_verification_result.sb_valid_secrets_count += 1 + else: + file_verification_result.sb_false_secrets_count += 1 + + found = False + # duplicates: [31860, 31861], [93022, 93024] + # others: bad secret values that not match with the original files + # easy to cover: [ + # 42815 (tokenimprover for links and querystrings), + # 94321 (rsa key without end header) + # ] + # currently unable to find: [94925, 24676(indented-md-yaml), 11549(html-inside-py)] + # potentially false positives: [32426] + # error files: + # - coinapi-coinapi-sdk_eb8cf18f1d5437e96e27df8a0d450cf65ae… + if id in [87344, 40571, 40526, 33888, 5956, 31860, 31861, 93022, 93024]: + found = True + file_verification_result.report[id] = SecretPointer( + found=found, + secret_id=id, + line_number=secret.start_line, + line_offset=secret.start_column, + detection=secret.secret, + context=None, + is_valid=secret.label, + ) + continue + + for finding in findings.get(current_file, []): + if finding.detection.replace(' ', '').replace('\n', '').replace('.', '') != secret.secret.replace( + ' ', '' + ).replace('\n', '').replace('.', ''): + continue + + if finding.start_line_number != secret.start_line: + continue + + found = True + file_verification_result.report[id] = SecretPointer( + found=found, + secret_id=secret.id, + line_number=secret.start_line, + line_offset=secret.start_column, + detection=secret.secret, + is_valid=secret.label, + rule_id=secret.comment, + ) + findings[current_file].remove(finding) + break + + if found is False: + file_verification_result.report[id] = SecretPointer( + secret_id=id, + found=found, + line_number=secret.start_line, + is_valid=secret.label, + detection=secret.secret, + internal_score=f'e: {secret.entropy}', + rule_id=secret.comment, + context=None, + ) + + if secret.label is True: + file_verification_result.all_found = False + file_verification_result.not_found_valid_secret_ids.append(id) + else: + file_verification_result.not_found_false_secret_ids.append(id) + + else: + if secret.label is True: + file_verification_result.found_valid_secret_ids.append(secret.id) + else: + file_verification_result.found_false_secret_ids.append(secret.id) + + # EXTRA LEFT + for finding in findings.get(current_file, []): + id = finding.get_id() + file_verification_result.report[id] = SecretPointer( + found=True, + secret_id=id, + line_number=finding.start_line_number, + context=None, + detection=finding.detection, + internal_score=finding.internal_score, + is_extra=True, + is_valid=True, + rule_id=finding.rules[0].id, + ) + file_verification_result.extra_secrets_count += 1 + + # self.write_verification_file(current_file, file_verification_result) + self.write_verification_db(current_file, file_verification_result) + + self.db.close() + + def dictify_findings_list(self, findings: List[Finding]) -> Dict[str, List[Finding]]: + final = {} + for finding in findings: + if finding.file.relative_path not in final: + final[finding.file.relative_path] = list() + + final[finding.file.relative_path].append(finding) + return final diff --git a/benchmarker/models/verification.py b/benchmarker/models/verification.py new file mode 100644 index 0000000..547d3a0 --- /dev/null +++ b/benchmarker/models/verification.py @@ -0,0 +1,46 @@ +from datetime import datetime +from typing import Dict, List, Optional + +from pydantic import BaseModel, Field, RootModel + + +class SecretPointer(BaseModel): + found: bool + secret_id: Optional[int] = None + line_number: Optional[int] = None + line_offset: Optional[int] = None + is_valid: bool + detection: Optional[str] = None + internal_score: Optional[str] = None + context: Optional[str] = None + rule_id: Optional[str] = None + is_extra: Optional[bool] = Field(default=False) + + +class FileVerificationResult(BaseModel): + checked: bool = Field(default=False) + all_found: bool = Field(default=True) + file_identifier: str + + sb_secrets_count: int = Field(default=0) + sb_valid_secrets_count: int = Field(default=0) + sb_false_secrets_count: int = Field(default=0) + + not_found_valid_secret_ids: List[int] = Field(default_factory=list) + not_found_false_secret_ids: List[int] = Field(default_factory=list) + found_valid_secret_ids: List[int] = Field(default_factory=list) + found_false_secret_ids: List[int] = Field(default_factory=list) + + extra_secrets_count: int = 0 + updated_ts: Optional[datetime] = None + report: Dict[int, SecretPointer] = Field(default_factory=dict) + + def __hash__(self) -> int: + return hash(self.file_identifier) + + def __eq__(self, value: object) -> bool: + return value.file_identifier == self.file_identifier + + +class VerificationsReportFile(RootModel): + root: Dict[str, FileVerificationResult] diff --git a/benchmarker/per_bucket.py b/benchmarker/per_bucket.py new file mode 100644 index 0000000..efc5b2d --- /dev/null +++ b/benchmarker/per_bucket.py @@ -0,0 +1,26 @@ +import sys + +from benchmarker.models.secretbench import TestingScope + + +def run(): + + dataset_location = sys.argv[1] + target_dir = sys.argv[2] + bucket_dir = sys.argv[3] + verification_dir = sys.argv[4] + + scope = TestingScope() + scope.init( + bucket_dir=bucket_dir, + target_dir=target_dir, + dataset_location=dataset_location, + verification_dir=verification_dir, + ) + scope.start_per_bucket() + + print() + + +if __name__ == '__main__': + run() diff --git a/benchmarker/run_bucket_benchmark.sh b/benchmarker/run_bucket_benchmark.sh new file mode 100755 index 0000000..092b354 --- /dev/null +++ b/benchmarker/run_bucket_benchmark.sh @@ -0,0 +1 @@ +PYTHONPATH=/app python3 /app/benchmarker/per_bucket.py /secretbench/secretbench.csv /secretbench/bench/buckets bucket_$1 /secretbench/bench/verifications diff --git a/deepsecrets/cli.py b/deepsecrets/cli.py index b70c405..c7dd0a7 100644 --- a/deepsecrets/cli.py +++ b/deepsecrets/cli.py @@ -1,7 +1,6 @@ import argparse from datetime import datetime import json -import logging from argparse import RawTextHelpFormatter from typing import Dict, List from jschema_to_python.to_json import to_json @@ -16,6 +15,7 @@ from deepsecrets.core.rulesets.false_findings import FalseFindingsBuilder from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder +from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder from deepsecrets.core.utils.fs import get_abspath, get_path_inside_package from deepsecrets.core.utils.log import logger from deepsecrets.scan_modes.cli import CliScanMode @@ -99,7 +99,7 @@ def _build_argparser(self) -> None: help='Paths to your Regex Rulesets.\n' "- Set 'disable' to turn off regex checks\n" '- Ignore this argument to use the built-in ruleset.\n' - "- Using your own rulesets disables the default one. Add 'built-in' to the args list to enable it\n" + "- Using your own rulesets disables the default one. Add 'built-in' to the args list to merge rulesets\n" 'eq. --regex-rules built-in /root/my_regex_rules.json\n', default=['built-in'], ) @@ -117,7 +117,19 @@ def _build_argparser(self) -> None: type=str, help='Controls semantic checks (enabled by default)\n' "- Set 'disable' to turn off semantic checks (not recommended)\n" - 'eq. --semantic-analysis disable', + 'eq. --semantic-analysis disable\n' + 'Uses "--variable-scoring-rules" under the hood', + default=['built-in'], + ) + + parser.add_argument( + '--variable-scoring-rules', + nargs='*', + type=str, + help='Controls rules for assessing variables as dangerous based on names, values, langs and filenames\n' + '- Ignore this argument to use the built-in (mature and robust) ruleset\n' + "- Using your own rulesets disables the default one. Add 'built-in' to the args list to merge rulesets\n" + 'eq. --variable-scoring-rules built-in /root/my_var_scoring_rules.json\n', default=['built-in'], ) @@ -189,8 +201,8 @@ def _build_argparser(self) -> None: default='json', type=str, choices=['json', 'sarif', 'dojo-sarif'], - help='"json": internal format (default)\n' - '"sarif": SARIF format (specification accurate)\n' + help='"json": internal format (default, will be deprecated soon)\n' + '"sarif": SARIF format (specification accurate, will become default soon)\n' '"dojo-sarif": SARIF format (compatible with DefectDojo\'s parser)\n', ) @@ -201,23 +213,32 @@ def _build_argparser(self) -> None: 'Use this flag if you want to render found secrets in plaintext but be extremely careful.', ) + parser.add_argument('--benchmarking-mode', help=argparse.SUPPRESS, action='store_true') + parser.add_argument('--oneshot', help=argparse.SUPPRESS, type=str, default=None) + self.argparser = parser def parse_arguments(self) -> None: user_args = self.argparser.parse_args(args=self.args[1:]) if user_args.verbose: - config.set_logging_level(logging.DEBUG) + pass + # config.set_logging_level(logging.DEBUG) if user_args.disable_masking: config.set_disable_masking(True) + if user_args.benchmarking_mode: + config._set_benchmarking_mode(True) + self.say_hello() config.set_workdir(user_args.target_dir) + config.set_oneshot_path(user_args.oneshot) config.set_max_file_size(user_args.max_file_size) config.set_process_count(user_args.process_count) config.set_mp_context(user_args.multiprocessing_context) + config.set_verbose(user_args.verbose) config.output = Output(type=user_args.outformat, path=user_args.outfile) if user_args.reflect_findings_in_return_code: @@ -240,6 +261,13 @@ def parse_arguments(self) -> None: if conf_semantic_analysis is not None and conf_semantic_analysis != DISABLED: config.engines.append(SemanticEngine) + VARIABLE_SCORING_RULESET = get_path_inside_package('rules/variable_scoring_rules.json') + if user_args.variable_scoring_rules is not None: + rules = [ + rule.replace('built-in', VARIABLE_SCORING_RULESET) for rule in user_args.variable_scoring_rules + ] + config.add_ruleset(VariableScoringRulesetBuilder, rules) + conf_hashed_ruleset = user_args.hashed_values if conf_hashed_ruleset is not None and conf_hashed_ruleset != DISABLED: config.engines.append(RegexEngine) @@ -301,9 +329,9 @@ def start(self) -> int: # pragma: nocover align='center', ) ) + return ReturnCodes.ERROR console.print('\n\n') - return -1 console.rule( f'Planning a scan against {config.workdir_path} using {config.process_count} process(es)', characters='=' @@ -333,6 +361,14 @@ def start(self) -> int: # pragma: nocover errors: Dict[str, List[str]] findings, errors = mode.run() + + ''' + for finding in findings: + if finding._mapped_on_file is False: + continue + finding.file = None + ''' + progress_bar.stop() finish_time = datetime.now() report_path = get_abspath(config.output.path) @@ -354,7 +390,7 @@ def start(self) -> int: # pragma: nocover errors_line_color = '[bold red]' if len(errors.keys()) > 0 else '[bold green]' table.add_row( Align(f'{errors_line_color}File Errors', vertical='middle'), - f'{errors_line_color}{str(len(errors.keys()))}', + f'{errors_line_color}{mode.stats.failed_files}', ) table.add_row() @@ -363,9 +399,12 @@ def start(self) -> int: # pragma: nocover Align(f'{findings_line_color}Potential Findings', vertical='middle'), f'{findings_line_color}{str(len(findings))}', ) - table.add_row(Align('Report Location', vertical='middle'), report_path) + table.add_row(Align(f'Report Location ({config.output.type})', vertical='middle'), report_path) console.print(Align(table, align='center')) + if config._benchmarking_mode is True: + return findings, errors, mode._oneshot_file + with open(report_path, 'w+') as f: if config.output.type == 'json': @@ -378,7 +417,7 @@ def start(self) -> int: # pragma: nocover f, ) - if config.output.type == 'dojo-sarif': + if config.output.type in ['sarif', 'dojo-sarif']: f.write( to_json( DojoSarifResponseBuilder() diff --git a/deepsecrets/config.py b/deepsecrets/config.py index 21272c9..1f30fd5 100644 --- a/deepsecrets/config.py +++ b/deepsecrets/config.py @@ -26,6 +26,7 @@ class Output(BaseModel): class Config: logging_level: int workdir_path: str + oneshot_path: str max_file_size: int = 0 # 0 means no limit mp_context: str = 'spawn' engines: List[Type] = [] @@ -35,6 +36,9 @@ class Config: process_count: int return_code_if_findings: bool disable_masking: bool + verbose: bool = False + + _benchmarking_mode: bool def __init__(self) -> None: self.engines = [] @@ -43,13 +47,22 @@ def __init__(self) -> None: self.return_code_if_findings = False self.disable_masking = False + self._benchmarking_mode = False + self.oneshot_path = None + # equals to CPU count self.process_count = FALLBACK_PROCESS_COUNT self.logging_level = logging.INFO + def set_verbose(self, verbose: bool): + self.verbose = verbose + def set_logging_level(self, level: int): self.logging_level = level + def _set_benchmarking_mode(self, mode: bool): + self._benchmarking_mode = mode + def set_disable_masking(self, state: bool): self.disable_masking = state @@ -58,6 +71,11 @@ def _set_path(self, path: str, field: str) -> None: raise FileNotFoundException(f'{field} path does not exist ({path})') setattr(self, field, get_abspath(path)) + def set_oneshot_path(self, path: str): + self.oneshot_path = path + if self.oneshot_path is not None: + self.workdir_path = '' + def set_workdir(self, path: str) -> None: self._set_path(path, 'workdir_path') diff --git a/deepsecrets/core/engines/semantic.py b/deepsecrets/core/engines/semantic.py index 36c8a64..dbd7e54 100644 --- a/deepsecrets/core/engines/semantic.py +++ b/deepsecrets/core/engines/semantic.py @@ -1,14 +1,12 @@ -import regex as re from typing import List +from deepsecrets.core.helpers.variable_evaluator import EvaluationResult, VariableEvaluator from deepsecrets.core.utils.log import logger from deepsecrets.core.engines.iengine import IEngine from deepsecrets.core.helpers.content_analyzer import ContentAnalyzer -from deepsecrets.core.helpers.entropy import EntropyHelper from deepsecrets.core.model.finding import Finding from deepsecrets.core.model.rules.rule import Rule -from deepsecrets.core.model.token import Token -from deepsecrets.core.utils.string import StringUtils +from deepsecrets.core.model.token import SemanticType, Token filenames_ignorelist = [ 'package-lock.json', @@ -21,44 +19,16 @@ '%env', ] -useless_values = [ - 'null', - 'bearer', - 'restore_password', -] - -var_name_showstoppers = [ - 'public', - 'path', - 'location', - 'field', - 'data', - 'cache', - 'prefix', - 'threshold', - 'name', - 'algo', - 'algorithm', - 'change', - 'mock', - 'fake', - 'dummy', -] - class SemanticEngine(IEngine): name = 'semantic' - entropy_threshold = 4.15 - dangerous_variable_regex = re.compile( - r'(secret|passw|\bpass\b|\btoken\b|\baccess\b|\bpwd\b|rivateke|cesstoke|authkey|cred|\bsecret\b|\bkey\b).{0,15}', - re.IGNORECASE, - ) - useless_value_regex = re.compile(r'^[^A-Za-z0-9]*$|^%.*%$|^\[.*\]$|^{.*}$', re.IGNORECASE) - subengine: IEngine - - def __init__(self, subengine: IEngine, **kwargs) -> None: + subengine: IEngine = None + variable_evaluator: VariableEvaluator + + def __init__(self, subengine: IEngine = None, **kwargs) -> None: super().__init__(**kwargs) self.subengine = subengine + self.variable_evaluator = VariableEvaluator(self.ruleset) # token is a STRING with potential 'semantic' extension def search(self, token: Token) -> List[Finding]: @@ -71,7 +41,15 @@ def search(self, token: Token) -> List[Finding]: if fname in token.file.path: return findings - if token.semantic is not None and token.semantic.creds_probability == 9: + if self.subengine is not None: # pragma: nocover + content_findings = ContentAnalyzer(self.subengine).analyze(token) + if content_findings is not None: + findings.extend(content_findings) + + if token.semantic is None: + return findings + + if token.semantic.creds_probability == 9: findings.append( Finding( detection=token.content, @@ -81,79 +59,55 @@ def search(self, token: Token) -> List[Finding]: ) ) - try: - dangerous_variable = self._if_dangerous_variable(token) - - if self.subengine is not None: # pragma: nocover - content_findings = ContentAnalyzer(self.subengine).analyze(token) - if content_findings is not None: - findings.extend(content_findings) - - if not dangerous_variable: - return findings - - if len(token.content) == 1: - return findings - - if len(token.content.split(' ')) > 1: - return findings - - if token.content in useless_values: - return findings - - if len(re.findall(self.useless_value_regex, token.content)) > 0: - return findings - - entropy = EntropyHelper.get_for_string(token.content) - if self._is_high_entropy(entropy): - findings.append( - Finding( - detection=token.content, - start_offset=0, - end_offset=len(token.content), - rules=[Rule(id='S105', name='Entropy+Var naming', confidence=-1)], + if token.semantic.type == SemanticType.VARIABLE: + try: + + if len(token.content) == 1: + return findings + + if len(token.content.split(' ')) > 1: + return findings + + evaluation_result: EvaluationResult = self.variable_evaluator.evaluate(token.semantic.payload) + dangerous_variable = evaluation_result.is_dangerous + + if not dangerous_variable: + return findings + + if evaluation_result.entropy_score > 0: + findings.append( + Finding( + detection=token.content, + start_offset=0, + end_offset=len(token.content), + rules=[ + Rule( + id='S105', + name='Entropy+Var naming', + confidence=evaluation_result.export_confidence, + ) + ], + internal_score=evaluation_result.summary(), + ) ) - ) - else: - for fss in false_starting_sequences: - if token.content.startswith(fss): - return findings - - findings.append( - Finding( - detection=token.content, - start_offset=0, - end_offset=len(token.content), - rules=[Rule(id='S106', name='Var naming', confidence=-1)], + else: + findings.append( + Finding( + detection=token.content, + start_offset=0, + end_offset=len(token.content), + rules=[ + Rule( + id='S106', + name='Var naming', + confidence=evaluation_result.export_confidence, + ) + ], + internal_score=evaluation_result.summary(), + ) ) - ) - except Exception as e: - logger.error('Problem during Entropy check on token') + except Exception as e: + logger.error(f'Problem during variable evaluation {e}') return findings - - def _is_high_entropy(self, entropy: float) -> bool: - return True if entropy > self.entropy_threshold else False - - def _if_dangerous_variable(self, token: Token) -> bool: - if token.semantic is None: - return False - - if token.semantic.creds_probability == 9: - return True - - cleaned_up_varname, name_parts = self.normalize_punctuation(token.semantic.name) - badvar = self.dangerous_variable_regex.findall(cleaned_up_varname) - if len(badvar) == 0: - return False - - if any(part in var_name_showstoppers for part in name_parts): - return False - - return True - - def normalize_punctuation(self, string: str): - normalized = string.replace(' ', '_').replace('-', ' ').replace('_', ' ') - parts = StringUtils.camel_case_divide(normalized).split(' ') - return normalized.lower(), parts diff --git a/deepsecrets/core/helpers/variable_evaluator.py b/deepsecrets/core/helpers/variable_evaluator.py new file mode 100644 index 0000000..62ecf05 --- /dev/null +++ b/deepsecrets/core/helpers/variable_evaluator.py @@ -0,0 +1,154 @@ +from dataclasses import dataclass, field +from typing import List, Union +from deepsecrets.core.helpers.entropy import EntropyHelper +from deepsecrets.core.model.rules.variable_scoring import VariableScoringRule +from deepsecrets.core.model.semantic import Context, Variable +from nostril import nonsense + +# func = generate_nonsense_detector(min_score=8.1) + + +@dataclass +class EvaluationResult: + is_dangerous: bool + matched_rules: List[str] = field(default_factory=list) + entropy: float = 0.0 + + naming_and_content_score: float = 0.0 + entropy_score: float = 0.0 + nonsence_value_score: float = 0.0 + + total_score: float = 0.0 + + export_confidence: int = 0 + + # < 3: 0 + # 3-4: 0 -> 35 + + def summary(self) -> str: + return f'conf: {self.export_confidence}; n+c:{self.naming_and_content_score}; e:{self.entropy_score}; gib:{self.nonsence_value_score}' + + +HOPELESS_THRESHOLD = -100 +DANGER_THRESHOLD = 0 + + +class VariableEvaluator: + + rules: List[VariableScoringRule] + + def __init__(self, rules: List[VariableScoringRule]) -> None: + self.rules = rules + + def calculate_entropy_score(self, entropy: float) -> float: + if entropy < 3: + return 0 + + if 3 <= entropy < 4: + return (entropy - 3) * 35 + + return 40 + + def _is_nonsense(self, token: str) -> float: + if len(token) <= 6: + return 0 + + is_nonsense = True + try: + is_nonsense = nonsense(token) + except ValueError: + pass + + return 1 if is_nonsense is True else 0 + + def calculate_nonsense_value_score(self, parts: List[str], normalized: str) -> float: + if len(normalized) > 300: + return 1 # obviously + + len_checks = 0 + applicable_parts = [t for t in parts if len(t) > 6] + scores = 0 + + for part in applicable_parts: + try: + scores += self._is_nonsense(part) + len_checks += 1 + except Exception: + pass + + normalized_string_score = 0 + if len(normalized) > 6: + normalized_string_score = self._is_nonsense(normalized) + + if len_checks == 0: + return 0 + return ((scores / len_checks) + normalized_string_score) / 2 + + def evaluate(self, variable: Union[Variable | Context]) -> EvaluationResult: + context = variable.context if isinstance(variable, Variable) else variable + + naming_and_content_score = 0 + matched_rules = [] + for rule in self.rules: + fired = rule.match_by_context(context) + if fired: + naming_and_content_score += rule.score + matched_rules.append(rule.id) + + if naming_and_content_score <= HOPELESS_THRESHOLD: + return EvaluationResult( + total_score=naming_and_content_score, + is_dangerous=False, + matched_rules=matched_rules, + ) + + entropy = EntropyHelper.get_for_string(context.value) + entropy_score = self.calculate_entropy_score(entropy) + + nonsense_value_score = self.calculate_nonsense_value_score(context.value_parts, context.value_normalized) + + total_score = naming_and_content_score + entropy_score + + result = EvaluationResult( + naming_and_content_score=naming_and_content_score, + entropy=entropy, + entropy_score=entropy_score, + total_score=total_score, + matched_rules=matched_rules, + nonsence_value_score=nonsense_value_score, + is_dangerous=naming_and_content_score > DANGER_THRESHOLD, + ) + + result.export_confidence = self.confidence_from_evaluation_result(result) + return result + + def confidence_from_evaluation_result(self, result: EvaluationResult): + entropy_part = 0 + var_part = result.naming_and_content_score / 25 * 10 + + if result.entropy_score > 0: + if result.naming_and_content_score <= 20: + # var_naming: 0 -> 5 + # entropy: 0 -> 5 + var_part = result.naming_and_content_score / 25 * 5 + entropy_part = result.entropy_score / 40 * 5 + else: + var_part = 5 + entropy_part = result.entropy_score / 40 * 5 + + entropy_part = entropy_part * min(result.nonsence_value_score + 0.5, 1) + # ep non + + if entropy_part > 5: + entropy_part = 5 + + if entropy_part < 0: + entropy_part = 0 + + if var_part > 10: + var_part = 10 + + if var_part < 0: + var_part = 0 + + return round(var_part + entropy_part) diff --git a/deepsecrets/core/model/file.py b/deepsecrets/core/model/file.py index 381c529..cde3e37 100644 --- a/deepsecrets/core/model/file.py +++ b/deepsecrets/core/model/file.py @@ -1,5 +1,5 @@ import regex as re -from typing import Dict, Optional, Tuple +from typing import Dict, List, Optional, Tuple from deepsecrets.core.utils.log import logger from deepsecrets.core.utils.fs import get_abspath @@ -10,8 +10,8 @@ class File: path: str content: str = '' length: int - line_offsets: Dict[int, Tuple[int, int]] = {} - line_contents_cache: Dict[int, str] = {} + line_offsets: Dict[int, Tuple[int, int]] + line_contents_cache: Dict[int, str] empty: bool name: str extension: Optional[str] @@ -22,9 +22,11 @@ def __init__( relative_path: Optional[str] = None, content: Optional[str] = None, offsets: Optional[Dict] = None, + extension: Optional[str] = None, ) -> None: self.line_offsets = {} self.line_contents_cache = {} + self.path = None if path is not None: self.path = get_abspath(path) @@ -42,8 +44,8 @@ def __init__( self.length = len(self.content) - self.name = self._get_name() - self.extension = self._get_extension() + self.name = self._get_name() if self.path is not None else 'ephemeral' + self.extension = extension if extension is not None else self._get_extension() self.empty = True if self.length == 0 else False if offsets is not None: @@ -57,6 +59,9 @@ def _get_name(self) -> str: return by_slash[-1].split('.')[0] def _get_extension(self) -> Optional[str]: + if self.path is None: + return None + by_dot = self.path.split('.') if len(by_dot) == 1: return None @@ -72,8 +77,12 @@ def _calc_offsets(self) -> None: if len(self.line_offsets) == 0 and self.length > 0: self.line_offsets[1] = (0, self.length) + last_line_number = len(self.line_offsets) + if self.line_offsets[last_line_number][1] != self.length - 1: + self.line_offsets[last_line_number + 1] = (self.line_offsets[last_line_number][1], self.length - 1) + def _get_contents(self) -> str: - with open(self.path) as f: + with open(self.path, errors='replace') as f: raw = f.read() if raw[-1] != '\n': raw += '\n' @@ -110,6 +119,12 @@ def get_span_for_string(self, str: str, between: Optional[Tuple[int, int]] = Non if between is None: between = (0, self.length) + if between[0] < 0: + between[0] = 0 + + if between[1] > self.length: + between[1] = self.length + search_window = self.content[between[0] : between[1]] pattern = re.escape(str) @@ -122,7 +137,7 @@ def get_span_for_string(self, str: str, between: Optional[Tuple[int, int]] = Non def get_column_number(self, position: int) -> int: line_number = self.get_line_number(position=position) - return position - self.line_offsets[line_number][0] + return position - self.line_offsets[line_number][0] + 1 def is_one_liner(self) -> bool: return len(self.line_offsets) == 1 @@ -134,5 +149,17 @@ def get_line_length(self, line_number: int) -> int: def get_line_start_offset(self, line_number: int) -> int: return self.line_offsets.get(line_number)[0] + def get_offset(self, line: int, column: int) -> int: + return self.get_line_start_offset(line) + column + + def check_boundaries(self, boundaries: List[int]): + if boundaries[0] < 0: + boundaries[0] = 0 + + if boundaries[1] >= self.length: + boundaries[1] = self.length - 1 + + return boundaries + def __repr__(self) -> str: # pragma: no cover return self.path diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index 4926a59..c160379 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -23,6 +23,7 @@ class Finding(BaseModel): end_offset: int reason: str = Field(default='') final_rule: Optional[Rule] = Field(default=None) + internal_score: Optional[str] = Field(default_factory=str) _mapped_on_file: bool = PrivateAttr(default=False) model_config = ConfigDict(arbitrary_types_allowed=True) @@ -75,6 +76,9 @@ def __hash__(self) -> int: # pragma: nocover return hash(f'{self.file.path}{self.detection}{self.start_offset}{self.end_offset}') + def get_id(self) -> int: + return 99000 + self.__hash__() + def __eq__(self, other: Any) -> bool: if not isinstance(other, Finding): return False diff --git a/deepsecrets/core/model/internal/processing.py b/deepsecrets/core/model/internal/processing.py index 43e4519..3d48a69 100644 --- a/deepsecrets/core/model/internal/processing.py +++ b/deepsecrets/core/model/internal/processing.py @@ -1,5 +1,6 @@ from dataclasses import dataclass -from typing import List +from typing import List, Optional +from deepsecrets.core.model.file import File from deepsecrets.core.model.finding import Finding @@ -8,3 +9,6 @@ class PerFileAnalysisResult: internal_task_id: int findings: List[Finding] errors: List[str] + + # ONLY FOR BENCHMARKING MODE + _file: Optional[File] = None diff --git a/deepsecrets/core/model/response/base.py b/deepsecrets/core/model/response/base.py index ca87efc..a248b0d 100644 --- a/deepsecrets/core/model/response/base.py +++ b/deepsecrets/core/model/response/base.py @@ -37,7 +37,7 @@ def _get_context_boundaries(self, finding: Finding, start_column: int, end_colum line_partial = False if line_length <= MAX_LINE_LENGTH_FOR_CONTEXT: - return boundaries + return boundaries, line_partial line_partial = True boundaries[0] = int(start_column - (MAX_LINE_LENGTH_FOR_CONTEXT / 2)) diff --git a/deepsecrets/core/model/response/builtin.py b/deepsecrets/core/model/response/builtin.py index 2c7d074..ccf5ffe 100644 --- a/deepsecrets/core/model/response/builtin.py +++ b/deepsecrets/core/model/response/builtin.py @@ -50,7 +50,9 @@ def build(self) -> Dict[str, List[Dict]]: resp_finding = FindingApiModel.from_finding(finding) if self.masking_enabled: - resp_finding.line = resp_finding.line.replace(resp_finding.string, '*' * len(resp_finding.string)) + if resp_finding.line is not None: + resp_finding.line = resp_finding.line.replace(resp_finding.string, '*' * len(resp_finding.string)) + resp_finding.string = '*' * len(resp_finding.string) resp[finding.file.path].append(resp_finding.model_dump()) diff --git a/deepsecrets/core/model/response/dojo_sarif.py b/deepsecrets/core/model/response/dojo_sarif.py index ca1f9b3..a9bb618 100644 --- a/deepsecrets/core/model/response/dojo_sarif.py +++ b/deepsecrets/core/model/response/dojo_sarif.py @@ -4,46 +4,33 @@ Tool, ToolComponent, ReportingDescriptor, - ArtifactContent, Result, - Region, Message, - ArtifactLocation, - PhysicalLocation, Location, + PhysicalLocation, + ArtifactLocation, + ArtifactContent, + Region, ) from deepsecrets.config import SCANNER_NAME, SCANNER_URL, SCANNER_VERSION -from deepsecrets.core.model import Finding -from typing import Dict +from typing import List +from deepsecrets.core.model.finding import Finding from deepsecrets.core.model.response.base import BaseResponseBuilder +from deepsecrets.core.model.rules.rule import Rule +from deepsecrets.core.modes.iscan_mode import ScanMode -class DojoSarifResponseBuilder(BaseResponseBuilder): +SRC_PATH_BASE_ID = 'SRCROOT' - def _get_levels(self, finding: Finding): - if finding.final_rule.confidence > 5: - precision = 'high' - security_severity = 'High' - level = 'error' - elif finding.final_rule.confidence > 0: - precision = 'medium' - security_severity = 'High' - level = 'error' - else: - precision = 'low' - security_severity = 'Medium' - level = 'warning' - return { - 'precision': precision, - 'security_severity': security_severity, - 'level': level, - } +class DojoSarifResponseBuilder(BaseResponseBuilder): - def build(self) -> SarifLog: # type: ignore + report: SarifLog - report = SarifLog( + def __init__(self) -> None: + super().__init__() + self.report = SarifLog( schema_uri='https://json.schemastore.org/sarif-2.1.0.json', version='2.1.0', runs=[ @@ -61,33 +48,81 @@ def build(self) -> SarifLog: # type: ignore ], ) - sarif_rules: Dict[str, ReportingDescriptor] = {} + def with_current_mode(self, mode: ScanMode): + super().with_current_mode(mode) + self.report.runs[0].original_uri_base_ids = ( + { + SRC_PATH_BASE_ID: { + 'uri': self.mode.config.workdir_path, + }, + }, + ) + return self - for finding in self.findings: + def _get_levels(self, rule: Rule): + precision = 'very-high' + security_severity = 'High' + level = 'error' - finding.choose_final_rule() - levels = self._get_levels(finding=finding) - - rule = ReportingDescriptor( - id=finding.final_rule.id, - short_description={'text': finding.final_rule.name}, - full_description={'text': finding.final_rule.name}, - help={'text': finding.final_rule.name}, - properties={ - 'security_severity': levels.get('security_severity'), - 'precision': levels.get('precision'), - }, - default_configuration={'level': levels.get('level')}, - ) + if rule.confidence >= 9: + precision = 'very-high' + security_severity = 'High' + level = 'error' + elif 9 > rule.confidence >= 6: + precision = 'high' + security_severity = 'High' + level = 'error' + elif 6 > rule.confidence >= 3: + precision = 'medium' + security_severity = 'High' + level = 'error' + elif 3 > rule.confidence >= 0: + precision = 'low' + security_severity = 'High' + level = 'error' - sarif_rules[finding.final_rule.id] = rule + return { + 'precision': precision, + 'security_severity': security_severity, + 'level': level, + } + + def _get_list_of_all_rules(self) -> List[ReportingDescriptor]: + sarif_rules = [] + for _, ruleset in self.mode.rulesets.items(): + for rule in ruleset: + sarif_rules.append(self._get_rule(rule)) + + return sarif_rules + + def _get_rule(self, rule: Rule) -> ReportingDescriptor: + levels = self._get_levels(rule) + return ReportingDescriptor( + id=rule.id, + short_description={'text': rule.name}, + full_description={'text': rule.name}, + help={'text': rule.name}, + properties={ + 'security-severity': levels.get('security_severity'), + 'precision': levels.get('precision'), + }, + default_configuration={'level': levels.get('level')}, + ) + def build(self) -> SarifLog: # type: ignore + + rules: set[Rule] = set() # self._get_list_of_rules() + + for finding in self.findings: + finding.choose_final_rule() region = self.get_region(finding=finding, masking=self.masking_enabled) context_region = self.get_context_region(finding=finding, masking=self.masking_enabled) + rules.add(finding.final_rule) + result = Result( rule_id=finding.final_rule.id, - message=Message(text=f'Secret in code ({finding.final_rule.name})'), + message=Message(text=f'Secret in code: ({finding.final_rule.name})'), locations=[ Location( physical_location=PhysicalLocation( @@ -99,10 +134,10 @@ def build(self) -> SarifLog: # type: ignore ], ) - report.runs[0].results.append(result) + self.report.runs[0].results.append(result) - report.runs[0].tool.driver.rules = [rule for rule in sarif_rules.values()] - return report + self.report.runs[0].tool.driver.rules = [self._get_rule(rule) for rule in rules] + return self.report def get_context_region(self, finding: Finding, masking: bool = True): @@ -124,7 +159,7 @@ def get_context_region(self, finding: Finding, masking: bool = True): snippet=ArtifactContent(text=snippet), ) - def get_region(self, finding: 'Finding', masking: bool = True): + def get_region(self, finding: Finding, masking: bool = True): start_column = finding.file.get_column_number(position=finding.start_offset) end_column = finding.file.get_column_number(position=finding.end_offset) diff --git a/deepsecrets/core/model/rules/rule.py b/deepsecrets/core/model/rules/rule.py index 9cdccfd..28822f7 100644 --- a/deepsecrets/core/model/rules/rule.py +++ b/deepsecrets/core/model/rules/rule.py @@ -1,5 +1,5 @@ import regex as re -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -7,6 +7,8 @@ class Rule(BaseModel): id: str name: Optional[str] = None + description: Optional[str] = None + enabled: bool = Field(default=True) confidence: int = Field(default=9) applicable_file_patterns: List[re.Pattern] = Field(default=[]) @@ -27,3 +29,12 @@ def fill_confidence(cls, values: Dict) -> Dict: def __hash__(self) -> int: # pragma: nocover return hash(self.id) + + def __eq__(self, other: Any): + if not isinstance(other, Rule): + return False + + if self.id == other.id: + return True + + return False diff --git a/deepsecrets/core/model/rules/semantic.py b/deepsecrets/core/model/rules/semantic.py deleted file mode 100644 index e7bf3a5..0000000 --- a/deepsecrets/core/model/rules/semantic.py +++ /dev/null @@ -1,5 +0,0 @@ -from deepsecrets.core.model.rules.rule import Rule - - -class SemanticRule(Rule): - pass diff --git a/deepsecrets/core/model/rules/variable_scoring.py b/deepsecrets/core/model/rules/variable_scoring.py new file mode 100644 index 0000000..9746eac --- /dev/null +++ b/deepsecrets/core/model/rules/variable_scoring.py @@ -0,0 +1,35 @@ +from enum import Enum +from deepsecrets.core.model.rules.regex import RegexRule +from deepsecrets.core.model.semantic import Context +import regex as re + + +class Target(str, Enum): + NAME_SPACED = "NAME_SPACED" # "api access key" + NAME_NORMALIZED = "NAME_NORMALIZED" # "apiaccesskey" (Best for fuzzy) + VALUE = "VALUE" + FILEPATH = "FILEPATH" # The file path + + +target_to_fields = { + Target.FILEPATH: 'filepath', + Target.NAME_SPACED: 'name_spaced', + Target.VALUE: 'value', + Target.NAME_NORMALIZED: 'name_normalized', +} + + +class VariableScoringRule(RegexRule): + score: int + target: Target + + def _get_content_for_matching(self, context: Context): + field = target_to_fields.get(self.target) + return getattr(context, field) + + def match_by_context(self, context: Context) -> bool: + content = self._get_content_for_matching(context) + match = re.search(self.pattern, content) + if match is not None: + return True + return False diff --git a/deepsecrets/core/model/semantic.py b/deepsecrets/core/model/semantic.py index 87618ab..da90091 100644 --- a/deepsecrets/core/model/semantic.py +++ b/deepsecrets/core/model/semantic.py @@ -1,14 +1,83 @@ +from dataclasses import dataclass, field from typing import List + from deepsecrets.core.model.token import Token +from deepsecrets.core.utils.string import StringUtils +import regex as re + + +number_pattern = re.compile(r'\b\d+\b') +hex_color = re.compile(r'#(?:[0-9a-fA-F]{3}){1,2}\b') + + +@dataclass +class Context: + name: str + value: str + filepath: str + + name_parts: List[str] = field(default_factory=list, repr=False) + name_normalized: str = field(default_factory=str, repr=False) + name_spaced: str = field(default_factory=str, repr=False) + + value_parts: List[str] = field(default_factory=list, repr=False) + value_normalized: str = field(default_factory=str, repr=False) + value_spaced: str = field(default_factory=str, repr=False) + + def __post_init__(self): + self.name_spaced, self.name_normalized, self.name_parts = self.normalize_punctuation(self.name) + self.value_spaced, self.value_normalized, self.value_parts = self.normalize_punctuation( + self.value, split_camel_case=False + ) + + def normalize_punctuation(self, string: str, split_camel_case=True): + normalized = string.replace(' ', '_') + normalized = hex_color.sub('', normalized) + normalized = number_pattern.sub('', normalized) + normalized = ( + normalized.replace('-', ' ') + .replace('_', ' ') + .replace('.', ' ') + .replace(':', ' ') + .replace('=', ' ') + .replace(';', ' ') + .replace('#', ' ') + .replace('/', ' ') + .replace('@', ' ') + ) + + parts = [] + if split_camel_case: + normalized = StringUtils.camel_case_divide(normalized) + + parts = normalized.split(' ') + return ' '.join(parts), normalized.lower().replace(' ', ''), parts class Variable: name: Token value: Token + _context: Context = None span: List[int] found_by: 'VariableDetector' + @property + def context(self): + if self._context is None: + self._context = Context( + name=self.name.content, + value=self.value.content, + filepath=self.name.file.path, + ) + return self._context + + +class Region: + span: List[int] + found_by: 'RegionDetector' + from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import ( # noqa: E402 VariableDetector, + RegionDetector, ) diff --git a/deepsecrets/core/model/token.py b/deepsecrets/core/model/token.py index 00ff41b..83a6ef1 100644 --- a/deepsecrets/core/model/token.py +++ b/deepsecrets/core/model/token.py @@ -1,7 +1,7 @@ from __future__ import annotations -from enum import Enum -from typing import List, Optional, Type +from enum import Enum, auto +from typing import Any, List, Optional, Type from deepsecrets.core.model.file import File from deepsecrets.core.model.rules.hashing import HashingAlgorithm @@ -9,18 +9,22 @@ class SemanticType(Enum): - VAR = 1 + VARIABLE = auto() class Semantic: type: SemanticType - name: str + payload: Any = None creds_probability: int - def __init__(self, type: SemanticType, name: str, creds_probability: int = 0) -> None: + def __init__(self, type: SemanticType, creds_probability: int = 0, payload: Any = None) -> None: self.type = type - self.name = name self.creds_probability = creds_probability + self.payload = payload + + @property + def name(self): + return self.payload.context.name class Token: @@ -63,7 +67,7 @@ def __repr__(self) -> str: # pragma: no cover if self.semantic is None and self.type is not None: return f'{self.content} | {self.type[0]}\n' - out = f'======== VAR: {self.semantic.name} = {self.content}' # type: ignore + out = f'======== VAR: {self.semantic.payload.context.name} = {self.content}' # type: ignore if self.type is not None: out += f' | {self.type[0]}\n' diff --git a/deepsecrets/core/model/tokenized_region.py b/deepsecrets/core/model/tokenized_region.py new file mode 100644 index 0000000..f362d6c --- /dev/null +++ b/deepsecrets/core/model/tokenized_region.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from typing import List + +from deepsecrets.core.model.token import Token +from deepsecrets.core.tokenizers.helpers.semantic.language import Language + + +@dataclass +class TokenizedRegion: + language: Language + tokens: List[Token] + stream: str diff --git a/deepsecrets/core/modes/iscan_mode.py b/deepsecrets/core/modes/iscan_mode.py index 01c55da..332d27d 100644 --- a/deepsecrets/core/modes/iscan_mode.py +++ b/deepsecrets/core/modes/iscan_mode.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from multiprocessing.pool import AsyncResult import regex as re @@ -12,6 +12,7 @@ from deepsecrets import PROFILER_ON, console from deepsecrets.config import Config +from deepsecrets.core.model.file import File from deepsecrets.core.model.finding import Finding from deepsecrets.core.model.internal.processing import PerFileAnalysisResult from deepsecrets.core.model.rules.exlcuded_path import ExcludePathRule @@ -31,6 +32,7 @@ class FileJob: internal_id: int pb_task_id: Optional[int] name: str + result_holder: AsyncResult @dataclass @@ -40,6 +42,11 @@ class Stats: failed_files: int = 0 tokens_processed: int = 0 total_findings: int = 0 + finished_ids: set[int] = field(default_factory=set) + + def new_finished(self, tid): + self.finished_ids.add(tid) + self.finished = len(self.finished_ids) class ScanMode: @@ -59,6 +66,9 @@ class ScanMode: _mp_manager = None + # ONLY IN BENCHMARKING MODE + _oneshot_file: Optional[File] = None + def __init__(self, config: Config, pool_engine: Optional[Any] = None) -> None: console.print('[*] Looking for applicable files...', end='') console.line() @@ -82,6 +92,18 @@ def __init__(self, config: Config, pool_engine: Optional[Any] = None) -> None: def set_progress_bar(self, progress_bar: ProgressBar): self.progress_bar = progress_bar + def stop_progress_bar(self, overall_progress): + if not self.progress_bar: + return + + self.refresh_jobs_progress_bars() + self.refresh_overall_progress_bar(overall_progress) + for task_id in self.progress_bar.task_ids: + if task_id == overall_progress: + continue + self.progress_bar.remove_task(task_id=task_id) + self.progress_bar.stop() + def _get_process_count_for_runner(self) -> int: limit = self.config.process_count @@ -94,8 +116,11 @@ def refresh_jobs_progress_bars(self): if self.active_task_reporter is None: return - tasks_to_remove = [] + tasks_to_remove = set() + for internal_task_id, current_state in self.active_task_reporter.items(): + if current_state is None: + continue job = self.file_jobs.get(internal_task_id) if job is None: raise Exception() @@ -118,7 +143,7 @@ def refresh_jobs_progress_bars(self): findings = current_state.get('findings', 0) if finished is True: - tasks_to_remove.append(internal_task_id) + tasks_to_remove.add(internal_task_id) if failure is True: self.stats.failed_files += 1 @@ -134,20 +159,21 @@ def refresh_jobs_progress_bars(self): continue total = current_state.get('total_tokens') + completed = current_state.get('percentage') if job.pb_task_id is not None: self.progress_bar.update( job.pb_task_id, - completed=processed, - total=total, + completed=completed, + total=100, visible=True, findings=findings, size=f'| {size}', ) for to_remove in tasks_to_remove: - self.stats.finished += 1 self.active_task_reporter.pop(to_remove) + self.stats.new_finished(to_remove) def refresh_overall_progress_bar(self, pb_task_id): if pb_task_id is None: @@ -159,6 +185,7 @@ def refresh_overall_progress_bar(self, pb_task_id): total=self.stats.total_files, findings=f'F: {self.stats.total_findings}', errors=f'ERR: {self.stats.failed_files}', + size=f'{self.stats.finished}/{self.stats.total_files}', ) def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: @@ -171,11 +198,11 @@ def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: return final, errors overall_progress_task = self.progress_bar.add_task( - "[green bold]OVERALL PROGRESS\n", + "[green bold]OVERALL\nPROGRESS\n", visible=True, findings='F: 0', errors='ERR: 0', - size='', + size=f'0/{self.stats.total_files}', ) if PROFILER_ON: @@ -190,29 +217,27 @@ def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: tid = 0 for file in self.filepaths: tid += 1 - # runnable = partial(pool_wrapper, bundle, self._per_file_analyzer, self.task_reporter) result = pool.apply_async( pool_wrapper, (bundle, self._per_file_analyzer, tid, self.active_task_reporter, file), ) self.file_results.append(result) - self.file_jobs[tid] = FileJob( - name=file, - internal_id=tid, - pb_task_id=None, - ) + self.file_jobs[tid] = FileJob(name=file, internal_id=tid, pb_task_id=None, result_holder=result) pool.close() self.stats.total_files = len(self.file_jobs.keys()) while self.stats.finished < self.stats.total_files: self.refresh_jobs_progress_bars() self.refresh_overall_progress_bar(overall_progress_task) + # self.refresh_overall_debug_progress_bar(overall_debug) + self.stop_progress_bar(overall_progress_task) + console.print('[*] Collecting results..') pool.join() - self.progress_bar.stop() - for job_result in self.file_results: analysis_result: PerFileAnalysisResult = job_result.get() + self._oneshot_file = analysis_result._file + job = self.file_jobs.get(analysis_result.internal_task_id) errors[job.name] = analysis_result.errors @@ -241,14 +266,18 @@ def _get_files_list(self) -> List[str]: excl_paths_builder.with_rules_from_file(path) self.path_exclusion_rules = excl_paths_builder.rules - with Live(console=console, refresh_per_second=5) as live: + if self.config.oneshot_path is not None: + flist.append(get_abspath(self.config.oneshot_path)) + return flist + + with Live(console=console, refresh_per_second=5) as live: total_files = 0 skipped = 0 for fpath, _, files in os.walk(get_abspath(self.config.workdir_path)): for filename in files: - live.update(Text(text=f'Found {total_files} files, {skipped} will be skipped')) total_files += 1 + live.update(Text(text=f'Found {total_files} files, {skipped} will be skipped')) full_path = os.path.join(fpath, filename) rel_path = full_path.replace(f'{self.config.workdir_path}/', '') if not self._path_included(rel_path): diff --git a/deepsecrets/core/rulesets/ibuilder.py b/deepsecrets/core/rulesets/ibuilder.py index 2d89199..255ec8e 100644 --- a/deepsecrets/core/rulesets/ibuilder.py +++ b/deepsecrets/core/rulesets/ibuilder.py @@ -17,7 +17,11 @@ def with_rules_from_file(self, file: str) -> object: with open(file) as f: rules_raw = json.load(f) - self.rules.extend([self.rule_model(**rule) for rule in rules_raw]) + for rule in rules_raw: + parsed_model: Rule = self.rule_model(**rule) + if parsed_model.enabled is False: + continue + self.rules.append(parsed_model) return self @property diff --git a/deepsecrets/core/rulesets/variable_scoring.py b/deepsecrets/core/rulesets/variable_scoring.py new file mode 100644 index 0000000..4957a91 --- /dev/null +++ b/deepsecrets/core/rulesets/variable_scoring.py @@ -0,0 +1,7 @@ +from deepsecrets.core.model.rules.variable_scoring import VariableScoringRule +from deepsecrets.core.rulesets.ibuilder import IRulesetBuilder + + +class VariableScoringRulesetBuilder(IRulesetBuilder): + rule_model = VariableScoringRule + ruleset_name = 'variable_scoring' diff --git a/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py b/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py new file mode 100644 index 0000000..1a8365c --- /dev/null +++ b/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py @@ -0,0 +1,125 @@ +from typing import List, Sequence, Set + +from ordered_set import OrderedSet + +from deepsecrets.core.model.semantic import Variable +from deepsecrets.core.model.token import Semantic, SemanticType, Token +from deepsecrets.core.model.tokenized_region import TokenizedRegion +from deepsecrets.core.tokenizers.helpers.semantic.language import Language +from deepsecrets.core.tokenizers.helpers.semantic.var_detection.rules import ( + VariableDetectionRules, + VariableSuppressionRules, +) +from deepsecrets.core.tokenizers.helpers.type_stream import types_to_filter_before, types_to_filter_after + +empty_tokens = ['\n', '\t', "'", "''", '"', '""'] + + +class DeepAnalyzer: + + regions: List[TokenizedRegion] + deep_inspection: bool + post_filter: bool + + def __init__(self, regions: List[TokenizedRegion], post_filter: bool, deep_inspection: bool = True) -> None: + self.regions = regions + self.deep_inspection = deep_inspection + self.post_filter = post_filter + + def get_final_tokens(self): + tokens = [] + + if self.deep_inspection is True: # type: ignore + self.run() + + [tokens.extend(region.tokens) for region in self.regions] + return tokens + + def run(self): + for region in self.regions: + updated_tokens = [] + tokens_to_be_excluded = self.analyze_token_sequence(region.language, region.tokens, region.stream) + updated_tokens.extend( + self.final_cleanup(region.tokens, tokens_to_be_excluded) if self.post_filter else list(region.tokens) + ) + region.tokens = updated_tokens + + def analyze_token_sequence(self, language: Language, tokens: List[Token], stream: str) -> Set[Token]: + tokens_all = OrderedSet(tokens) + if language is None: + return tokens_all + + exclude_after = set() + + true_var_detections: List[Variable] = [] + suppression_regions: List[List[int]] = [] + + detection_rules = VariableDetectionRules.for_language(language) + suppression_rules = VariableSuppressionRules.for_language(language) + + for rule in detection_rules: + true_var_detections.extend(rule.match(tokens, stream)) + + for rule in suppression_rules: + suppression_regions.extend(rule.match(tokens, stream)) + + suppression_regions = self._collapse_suppression_regions(suppression_regions) + + for var in true_var_detections: + suppressed = self._if_suppressed(var, suppression_regions) + if suppressed: + exclude_after.update([var.name, var.value]) + continue + + var.value.semantic = Semantic( + type=SemanticType.VARIABLE, + # name=var.name.content, + payload=var, + creds_probability=var.found_by.creds_probability, + ) + exclude_after.add(var.name) + + return exclude_after + + def _if_suppressed(self, var: Variable, regions): + for reg in regions: + if var.span[0] >= reg[0] and var.span[1] <= reg[1]: + return True + return False + + def _collapse_suppression_regions(self, suppression_regions): + regions = [] + if len(suppression_regions) == 0: + return regions + + for i, reg in enumerate(suppression_regions): + if i == 0: + regions.append(suppression_regions[0]) + continue + + if reg[0] == regions[-1][1]: + regions[-1][1] = reg[1] + else: + regions.append(reg) + + return regions + + def final_cleanup(self, tokens_all: Sequence[Token], tokens_to_be_excluded: Sequence[Token]) -> List[Token]: + if not isinstance(tokens_all, OrderedSet): + tokens_all = OrderedSet(tokens_all) + + tokens_all = tokens_all - tokens_to_be_excluded + final = [] + for token in tokens_all: + if any(type in token.type for type in types_to_filter_before): # type: ignore + continue + + if any(type in token.type for type in types_to_filter_after): # type: ignore + continue + + if token.content.replace(' ', '') in empty_tokens: + continue + + final.append(token) + + return final diff --git a/deepsecrets/core/tokenizers/helpers/semantic/language.py b/deepsecrets/core/tokenizers/helpers/semantic/language.py index e1e154e..1671d7d 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/language.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/language.py @@ -5,7 +5,7 @@ class Language(MultiValueEnum): PYTHON = 'py' GOLANG = 'go' PHP = 'php' - JS = 'js','jsx' + JS = 'js', 'jsx' TOML = 'toml' JSON = 'json' YAML = 'yaml' @@ -16,6 +16,7 @@ class Language(MultiValueEnum): JAVA = 'java' KOTLIN = 'kt' SWIFT = 'swift' + MARKDOWN = 'md' ANY = 'any' UNKNOWN = 'unknown' diff --git a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py index 07453cd..054b302 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py @@ -79,14 +79,13 @@ def regexify_values(cls, values: Dict) -> List[re.Pattern]: return patterns -class VariableDetector(BaseModel): +class RegionDetector(BaseModel): language: Optional[Language] = None stream_pattern: re.Pattern - # re_flags: Optional[re.RegexFlag] = None + match_rules: Dict[int, Match] match_semantics: Dict[int, str] creds_probability: int = 0 - model_config = ConfigDict(arbitrary_types_allowed=True) def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: @@ -96,13 +95,13 @@ def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: if not self._verify(match, tokens): continue - var = Variable() + reg = Region() for i, name in self.match_semantics.items(): - setattr(var, name, tokens[match.span(i)[0]]) - var.found_by = self - var.span = [match.span(0)[0], match.span(0)[1]] + setattr(reg, name, [match.span(i)[0], match.span(i)[1]]) + reg.found_by = self + reg.span = [match.span(0)[0], match.span(0)[1]] - true_detections.append(var) + true_detections.append(reg) return true_detections @@ -117,6 +116,27 @@ def _verify(self, match: re.Match, tokens: List[Token]) -> bool: return True +class VariableDetector(RegionDetector): + creds_probability: int = 0 + + def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: + true_detections = [] + + for match in re.finditer(self.stream_pattern, token_stream, overlapped=True): + if not self._verify(match, tokens): + continue + + var = Variable() + for i, name in self.match_semantics.items(): + setattr(var, name, tokens[match.span(i)[0]]) + var.found_by = self + var.span = [match.span(0)[0], match.span(0)[1]] + + true_detections.append(var) + + return true_detections + + class VariableSuppressor(VariableDetector): def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: @@ -128,4 +148,4 @@ def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: return spans -from deepsecrets.core.model.semantic import Variable +from deepsecrets.core.model.semantic import Region, Variable diff --git a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py index 0ca56ed..efc2610 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py @@ -9,6 +9,18 @@ ) from pygments.token import Token as PygmentsToken +''' +VariableDetector( + language=Language.YAML, + stream_pattern=re.compile('(n)(p)(L)'), + match_rules={ + 1: Match(types=[PygmentsToken.Name.Tag]), + 2: Match(values=[':']), + }, + match_semantics={1: 'name', 3: 'value'}, +), +''' + class VariableDetectionRules: rules = [ @@ -43,8 +55,8 @@ class VariableDetectionRules: # GOLANG VariableDetector( language=Language.GOLANG, - stream_pattern=re.compile('(n)(p)(L)(?:p|\n)?'), - match_rules={2: Match(values=[':', '='])}, + stream_pattern=re.compile('(n)(o|p)(L)(?:p|\n)?'), + match_rules={2: Match(values=[':', '=', ':='])}, match_semantics={1: 'name', 3: 'value'}, ), VariableDetector( @@ -167,6 +179,16 @@ class VariableDetectionRules: }, match_semantics={3: 'name', 5: 'value'}, ), + VariableDetector( + language=Language.MARKDOWN, + stream_pattern=re.compile('(n)(p)(.)(p)(L)'), + match_rules={ + 1: Match(values=[re.compile('^put$')]), + 2: Match(values=[re.compile('^\\($')]), + 4: Match(values=[re.compile('^,$')]), + }, + match_semantics={3: 'name', 5: 'value'}, + ), ] @classmethod @@ -177,8 +199,9 @@ def for_language(cls, language: Language) -> List[VariableDetector]: class VariableSuppressionRules(VariableDetectionRules): rules = [ VariableSuppressor( + # For cases like language=Language.JS, - stream_pattern=re.compile('(p)(n).+?(p)(u|L|\n)'), + stream_pattern=re.compile('(p)(n).+?(p)(u|L|\n|$)'), match_rules={ 1: Match( values=[ @@ -198,7 +221,7 @@ class VariableSuppressionRules(VariableDetectionRules): ), VariableSuppressor( language=Language.JS, - stream_pattern=re.compile('(n)(o)L.{0,4}(?:u|\n)(n)(o)(?:L|u)'), + stream_pattern=re.compile('(n)(o)L.{0,4}(?:u|\n)?(n)(o)(?:L|u|n)'), match_rules={ 1: Match( values=[ @@ -212,7 +235,9 @@ class VariableSuppressionRules(VariableDetectionRules): ), 3: Match( values=[ - re.compile('^value$'), + re.compile('^.*value.*$', flags=re.IGNORECASE), + re.compile('^.*name.*$', flags=re.IGNORECASE), + re.compile('^.*title.*$', flags=re.IGNORECASE), ] ), 4: Match( diff --git a/deepsecrets/core/tokenizers/helpers/spot_improvements.py b/deepsecrets/core/tokenizers/helpers/single_token_improver.py similarity index 74% rename from deepsecrets/core/tokenizers/helpers/spot_improvements.py rename to deepsecrets/core/tokenizers/helpers/single_token_improver.py index 2026ddb..02996f7 100644 --- a/deepsecrets/core/tokenizers/helpers/spot_improvements.py +++ b/deepsecrets/core/tokenizers/helpers/single_token_improver.py @@ -5,10 +5,11 @@ from deepsecrets.core.model.token import Token from deepsecrets.core.tokenizers.helpers.semantic.language import Language +from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import Match, RegionDetector from deepsecrets.core.tokenizers.helpers.type_stream import token_to_typestream_item -class SpotImprovements: +class SingleTokenImprover: language: Language acc: dict[Language, List[Callable]] @@ -16,7 +17,7 @@ def __init__(self, lang: Language) -> None: self.language = lang self.acc = {Language.SHELL: [self._curl_argstring_breakdown]} - def improve_token(self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token) -> List[Token]: + def improve(self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token) -> List[Token]: tokens = [] for improvement in self.acc.get(self.language, []): tokens.extend(improvement(so_far_tokens, so_far_type_stream, current_token)) @@ -30,17 +31,17 @@ def _curl_argstring_breakdown( self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token ) -> List[Token]: projected_typestream = so_far_type_stream + token_to_typestream_item(current_token) - rule = {'pattern': re.compile('(L)(L)$'), 'checks': {1: re.compile('^-u$')}} - match: re.Match = rule['pattern'].search(projected_typestream) + + rule = RegionDetector( + stream_pattern=re.compile('(L)(L)$'), + match_rules={1: Match(values=[re.compile('^-u$')])}, + match_semantics={}, + ) + + match = rule.match(so_far_tokens, projected_typestream) if not match: return [current_token] - for group_i, pattern in rule['checks'].items(): - span = match.span(group_i) - group_token: Token = so_far_tokens[span[0]] - if not pattern.search(group_token.content): - return [current_token] - new_parts = current_token.content.split(':') if new_parts[0] == '' or new_parts[1] == '': return [current_token] diff --git a/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py b/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py new file mode 100644 index 0000000..ba9b521 --- /dev/null +++ b/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py @@ -0,0 +1,129 @@ +import regex as re +from typing import List +from deepsecrets.core.model.file import File +from deepsecrets.core.model.semantic import Region +from deepsecrets.core.model.token import Token +from deepsecrets.core.model.tokenized_region import TokenizedRegion +from deepsecrets.core.tokenizers.helpers.semantic.language import Language +from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import Match, RegionDetector +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from pygments.token import Token as PygmentsToken + +''' + +''' + + +class SubfileDetectionRules: + hint_to_lang = {'console': 'txt'} + + rules = [ + # Detecting code snippets inside named backticks + # TODO: look at the text after the backtick, eq. ```java + RegionDetector( + language=Language.MARKDOWN, + stream_pattern=re.compile('b(b)\\s(o+)\\s*b'), + match_rules={}, + match_semantics={1: 'language', 2: 'code'}, + ), + RegionDetector( + language=Language.MARKDOWN, + stream_pattern=re.compile('b(b)\n(\\X*?)b'), + match_rules={}, + match_semantics={1: 'language', 2: 'code'}, + ), + RegionDetector( + language=Language.YAML, + stream_pattern=re.compile('(p)(p)([\n|n]*)\n(n)', flags=re.MULTILINE | re.S), + match_rules={ + 1: Match(values=[re.compile('^:$')]), + 2: Match( + values=[re.compile('^|$')], + types=[PygmentsToken.Punctuation.Indicator], + ), + 4: Match(types=[PygmentsToken.Name.Tag]), + }, + match_semantics={3: 'code'}, + ), + ] + + @classmethod + def for_language(cls, language: Language) -> List[RegionDetector]: + return list(filter(lambda x: x.language in [language, Language.ANY], cls.rules)) + + +class SubFileRegionsHelper: + + language: Language + tokens: List[Token] + stream: str + file: File + regions: List[TokenizedRegion] + + def __init__(self, file: File, language: Language, tokens: List[Token], stream: str) -> None: + self.language = language + self.tokens = tokens + self.stream = stream + self.file = file + self.regions = [] + + def extract_subcontent(self, match: Region): + + start_offset = self.tokens[match.code[0]].span[0] + end_offset = self.tokens[match.code[1]].span[0] + language_hint = None + if hasattr(match, 'language') is True: + language_hint = self.tokens[match.language[0]].content + language_hint = SubfileDetectionRules.hint_to_lang.get(language_hint, language_hint) + + new_content = self.file.content[start_offset:end_offset] + quazi_file = File(path=None, content=new_content, extension=language_hint) + nizer = LexerTokenizer(deep_token_inspection=False) + nizer.tokenize(quazi_file, post_filter=False) + return nizer.regions + + def find(self): + detection_rules = SubfileDetectionRules.for_language(self.language) + if len(detection_rules) == 0: + self.add_region(self.language, self.tokens, self.stream) + return self.regions + + for rule in detection_rules: + matches = rule.match(self.tokens, self.stream) + current_index = 0 + for match in matches: + start_index = match.code[0] + end_index = match.code[1] + self.add_region( + self.language, + self.tokens[current_index:start_index], + self.stream[current_index:start_index], + ) + + regions = self.extract_subcontent(match) + if len(regions) == 0: + continue + + for region in regions: + start_offset = self.tokens[start_index].span[0] + for token in region.tokens: + token.span = (start_offset + token.span[0], start_offset + token.span[1]) + token.file = self.file + + self.add_region(region.language, region.tokens, region.stream) + current_index = end_index + + if len(self.regions) == 0: + self.add_region(self.language, self.tokens, self.stream) + return self.regions + + return self.regions + + def add_region(self, lang, tokens, stream): + self.regions.append( + TokenizedRegion( + language=lang, + tokens=tokens, + stream=stream, + ) + ) diff --git a/deepsecrets/core/tokenizers/helpers/type_stream.py b/deepsecrets/core/tokenizers/helpers/type_stream.py index 0110996..e1aac02 100644 --- a/deepsecrets/core/tokenizers/helpers/type_stream.py +++ b/deepsecrets/core/tokenizers/helpers/type_stream.py @@ -19,6 +19,10 @@ PygmentsToken.Name.Builtin.Pseudo, ] +types_not_to_filter_before = [ + PygmentsToken.Generic.Output, +] + types_to_filter_after = [ PygmentsToken.Punctuation, @@ -48,7 +52,8 @@ PygmentsToken.String.Single: 'L', PygmentsToken.String.Double: 'L', PygmentsToken.Text: 'L', - PygmentsToken.Literal.String.Backtick: 'p', # technically it's a punc + PygmentsToken.Literal.String.Backtick: 'b', # technically it's a punc + PygmentsToken.Generic.Output: 'o', } @@ -56,7 +61,7 @@ def token_to_typestream_item(token: Token) -> str: if token.content == '\n': return '\n' - if any(type in token.type for type in types_to_filter_before): # type: ignore + if any(type in token.type for type in types_to_filter_before) and not any(type in token.type for type in types_not_to_filter_before): # type: ignore return 'u' return acc.get(token.type[0], '?') # type: ignore diff --git a/deepsecrets/core/tokenizers/itokenizer.py b/deepsecrets/core/tokenizers/itokenizer.py index b268e70..cb9c9c4 100644 --- a/deepsecrets/core/tokenizers/itokenizer.py +++ b/deepsecrets/core/tokenizers/itokenizer.py @@ -4,21 +4,46 @@ from deepsecrets.core.model.file import File from deepsecrets.core.model.token import Token +from deepsecrets.core.utils.lifecycle_hooks import FileLifecycleHooks class Tokenizer: tokens: List[Token] settings: NamedTuple + lifecycle: FileLifecycleHooks + + last_offset_reported: float = 0 def __init__(self, **kwargs) -> None: self.tokens = [] Settings = namedtuple('Settings', kwargs.keys()) # type: ignore self.settings = Settings._make(kwargs.values()) # type: ignore + self.lifecycle = None + + def add_lifecycle_hooks(self, lifecycle): + self.lifecycle = lifecycle + + def on_new_offset_processed(self, new_offset: float): + if self.lifecycle is None: + return + if new_offset - self.last_offset_reported < 0.01: + return + + self.last_offset_reported = new_offset + + self.lifecycle.on_tokenization_progress( + name=self.__class__.__name__, + new_offset=round(new_offset, 2), + ) @abstractmethod def tokenize(self, file: File) -> List[Token]: pass + @abstractmethod + def get_variables(self): + return [] + def __hash__(self) -> int: # pragma: nocover return hash(type(self)) diff --git a/deepsecrets/core/tokenizers/lexer.py b/deepsecrets/core/tokenizers/lexer.py index cb874a9..6195149 100644 --- a/deepsecrets/core/tokenizers/lexer.py +++ b/deepsecrets/core/tokenizers/lexer.py @@ -1,37 +1,29 @@ -from typing import List, Optional, Sequence, Set, Type, Union +from typing import List, Optional, Type, Union + +from deepsecrets.core.model.tokenized_region import TokenizedRegion +from deepsecrets.core.tokenizers.helpers.semantic.deep_analyzer import DeepAnalyzer from deepsecrets.core.utils.log import logger -from ordered_set import OrderedSet -from pygments import highlight -from pygments.formatters import RawTokenFormatter -from pygments.lexers.special import Lexer, RawTokenLexer +from pygments.lexers.special import Lexer from pygments.token import Token as PygmentsToken from deepsecrets.core.model.file import File -from deepsecrets.core.model.semantic import Variable -from deepsecrets.core.model.token import Semantic, SemanticType, Token +from deepsecrets.core.model.token import SemanticType, Token from deepsecrets.core.tokenizers.helpers.semantic.language import Language -from deepsecrets.core.tokenizers.helpers.semantic.var_detection.rules import ( - VariableDetectionRules, - VariableSuppressionRules, -) -from deepsecrets.core.tokenizers.helpers.spot_improvements import SpotImprovements +from deepsecrets.core.tokenizers.helpers.single_token_improver import SingleTokenImprover from deepsecrets.core.tokenizers.helpers.type_stream import ( token_to_typestream_item, - types_to_filter_after, - types_to_filter_before, ) from deepsecrets.core.tokenizers.itokenizer import Tokenizer from deepsecrets.core.utils.lexer_finder import LexerFinder -empty_tokens = ['\n', '\t', "'", "''", '"', '""'] - class LexerTokenizer(Tokenizer): token_stream: str lexer: Lexer - language: Language + language: Language = None + regions: List[TokenizedRegion] = [] def _get_types_for_token(self, token: PygmentsToken) -> List[Type]: # type: ignore types = [] @@ -77,7 +69,6 @@ def tokenize(self, file: File, post_filter=True) -> List[Token]: self.lexer = self._find_lexer_for_file(file) if not self.lexer: return self.tokens - try: self.language: Language = Language.from_text(self.lexer.filenames[0]) except (ValueError, IndexError): @@ -85,18 +76,18 @@ def tokenize(self, file: File, post_filter=True) -> List[Token]: except Exception as e: logger.exception(e) - result = highlight(file.content, self.lexer, RawTokenFormatter()) - raw_tokens = list(RawTokenLexer().get_tokens(result)) - token_improver = SpotImprovements(lang=self.language) + raw_tokens = list(self.lexer.get_tokens_unprocessed(file.content)) + single_token_improver = SingleTokenImprover(lang=self.language) current_position = 0 - - for i, raw_token in enumerate(raw_tokens): - content: str = raw_token[1] - types: List[Type] = self._get_types_for_token(raw_token[0]) + # TODO: Token.Error creates millions of bullshit + for offset, types, content in raw_tokens: + types: List[Type] = self._get_types_for_token(types) start = current_position end = start + len(content) current_position = end + if current_position >= file.length: + continue try: content = self.sanitize(content) @@ -107,84 +98,35 @@ def tokenize(self, file: File, post_filter=True) -> List[Token]: token = Token(file=file, content=content, span=span) token.set_type(types) - improved_tokens = token_improver.improve_token(self.tokens, self.token_stream, token) + improved_tokens = single_token_improver.improve(self.tokens, self.token_stream, token) self.tokens.extend(improved_tokens) self.add_to_token_stream(improved_tokens) except Exception as e: str(e) - tokens_to_be_excluded = [] - if self.settings.deep_token_inspection is True: # type: ignore - tokens_to_be_excluded = self.deep_analyze() + self.on_new_offset_processed(new_offset=offset / file.length) + + self.regions: List[TokenizedRegion] = SubFileRegionsHelper( + file=file, + language=self.language, + tokens=self.tokens, + stream=self.token_stream, + ).find() - return self.final_cleanup(self.tokens, tokens_to_be_excluded) if post_filter else list(self.tokens) + self.tokens = DeepAnalyzer( + regions=self.regions, + deep_inspection=self.settings.deep_token_inspection, + post_filter=post_filter, + ).get_final_tokens() + return self.tokens def add_to_token_stream(self, tokens: List[Token]) -> None: for token in tokens: self.token_stream += token_to_typestream_item(token=token) - def final_cleanup(self, tokens_all: Sequence[Token], tokens_to_be_excluded: Sequence[Token]) -> List[Token]: - if not isinstance(tokens_all, OrderedSet): - tokens_all = OrderedSet(tokens_all) - - tokens_all = tokens_all - tokens_to_be_excluded - final = [] - for token in tokens_all: - if any(type in token.type for type in types_to_filter_before): # type: ignore - continue - - if any(type in token.type for type in types_to_filter_after): # type: ignore - continue - - if token.content.replace(' ', '') in empty_tokens: - continue - - final.append(token) - - return final - - def deep_analyze(self) -> Set[Token]: - tokens_all = OrderedSet(self.tokens) - if self.language is None: - return tokens_all - - exclude_after = set() - - true_detections: List[Variable] = [] - suppression_regions: List[List[int]] = [] - - detection_rules = VariableDetectionRules.for_language(self.language) - suppression_rules = VariableSuppressionRules.for_language(self.language) - - for rule in detection_rules: - true_detections.extend(rule.match(self.tokens, self.token_stream)) - - for rule in suppression_rules: - suppression_regions.extend(rule.match(self.tokens, self.token_stream)) - - suppression_regions = self._collapse_suppression_regions(suppression_regions) - - for var in true_detections: - suppressed = self._if_suppressed(var, suppression_regions) - if suppressed: - exclude_after.update([var.name, var.value]) - continue - - var.value.semantic = Semantic( - type=SemanticType.VAR, - name=var.name.content, - creds_probability=var.found_by.creds_probability, - ) - exclude_after.add(var.name) - - return exclude_after - - def _if_suppressed(self, var: Variable, regions): - for reg in regions: - if var.span[0] >= reg[0] and var.span[1] <= reg[1]: - return True - return False + def print_token_type_stream(self) -> None: + print(self.token_stream) def get_variables(self, tokens: Optional[List[Token]] = None) -> List[Token]: tokens = tokens if tokens is not None else self.tokens @@ -196,29 +138,12 @@ def get_variables(self, tokens: Optional[List[Token]] = None) -> List[Token]: if token.semantic is None: continue - if token.semantic.type != SemanticType.VAR: + if token.semantic.type != SemanticType.VARIABLE: continue vars.append(token) return vars - def print_token_type_stream(self) -> None: - print(self.token_stream) - - def _collapse_suppression_regions(self, suppression_regions): - regions = [] - if len(suppression_regions) == 0: - return regions - - for i, reg in enumerate(suppression_regions): - if i == 0: - regions.append(suppression_regions[0]) - continue - - if reg[0] == regions[-1][1]: - regions[-1][1] = reg[1] - else: - regions.append(reg) - return regions +from deepsecrets.core.tokenizers.helpers.subfile_regions_helper import SubFileRegionsHelper diff --git a/deepsecrets/core/utils/file_analyzer.py b/deepsecrets/core/utils/file_analyzer.py index 0c2bf2c..edef077 100644 --- a/deepsecrets/core/utils/file_analyzer.py +++ b/deepsecrets/core/utils/file_analyzer.py @@ -31,7 +31,7 @@ def __init__(self, file: File): self.engine_tokenizers = [] self.file = file self.tokens = {} - self.progress = FileProgress(tokenizers_total=len(self.engine_tokenizers)) + self.progress = FileProgress() self.lifecycle = FileLifecycleHooks(reporter=None, task_id=None, progress=self.progress) self.task_reporter = None self.task_id = None @@ -46,7 +46,7 @@ def attach_global_task_reporter(self, task_reporter, task_id): def add_engine(self, engine: IEngine, tokenizers: List[Tokenizer]) -> None: for tokenizer in tokenizers: self.engine_tokenizers.append(EngineWithTokenizer(engine=engine, tokenizer=tokenizer)) - self.progress.tokenizers_total += 1 + self.progress.add_tokenizer(tokenizer.__class__.__name__) def process(self) -> List[Finding]: results: List[Finding] = [] @@ -67,31 +67,38 @@ def _run_engine(self, et: EngineWithTokenizer) -> List[Finding]: processed_values: Dict[int, bool] = {} if et.tokenizer not in self.tokens: + et.tokenizer.add_lifecycle_hooks(self.lifecycle) self.tokens[et.tokenizer] = et.tokenizer.tokenize(self.file) - self.progress.on_tokenization_finished(token_count=len(self.tokens[et.tokenizer])) + self.lifecycle.on_tokenization_finished( + name=et.tokenizer.__class__.__name__, + token_count=len( + self.tokens[et.tokenizer], + ), + ) tokens: List[Token] = self.tokens[et.tokenizer] for token in tokens: - self.lifecycle.on_token_processing_start(token) + self.lifecycle.on_token_processing_start( + name=et.tokenizer.__class__.__name__, + ) is_known_content = processed_values.get(token.val_hash()) if is_known_content is not None and is_known_content is False: continue processed_values[token.val_hash()] = False + findings: List[Finding] = et.engine.search(token) try: - findings: List[Finding] = et.engine.search(token) for finding in findings: finding.map_on_file(file=self.file, relative_start=token.span[0]) results.append(finding) processed_values[token.val_hash()] = True - self.lifecycle.on_token_processing_end(len(findings)) - except Exception as e: logger.exception(f'Unable to process token: {e}') continue + self.lifecycle.on_token_processing_end(len(findings)) return results diff --git a/deepsecrets/core/utils/finding_merger.py b/deepsecrets/core/utils/finding_merger.py index 0fe0793..943d846 100644 --- a/deepsecrets/core/utils/finding_merger.py +++ b/deepsecrets/core/utils/finding_merger.py @@ -9,10 +9,13 @@ class FindingMerger: def __init__(self, full_list: List[Finding]) -> None: self.all = full_list - def merge(self) -> List[Finding]: + def merge(self, choose_final_rule=False) -> List[Finding]: interm_dict: Dict[int, Finding] = {} for elem in self.all: + if choose_final_rule: + elem.choose_final_rule() + hash = elem.__hash__() if hash not in interm_dict: interm_dict[hash] = elem diff --git a/deepsecrets/core/utils/guess_filetype.py b/deepsecrets/core/utils/guess_filetype.py index 9ffe7f5..1351fc6 100644 --- a/deepsecrets/core/utils/guess_filetype.py +++ b/deepsecrets/core/utils/guess_filetype.py @@ -15,6 +15,7 @@ def __init__(self) -> None: 'pp': self._is_puppet, 'ini': self._is_ini, 'yaml': self._is_yaml, + 'rst': self._is_rst, # 'properties': self._dot_properties, } @@ -22,8 +23,9 @@ def guess(self, content: str) -> Optional[str]: for ext, probe in self.probes.items(): if probe(content): return ext - + # TODO: Guesslang + # TODO: HOCON parser ''' ml_guesser = Guess() guess = ml_guesser.language_name(content) @@ -35,13 +37,13 @@ def guess(self, content: str) -> Optional[str]: return ext ''' return None - + def _is_json(self, content: str): try: json.loads(content) except Exception: return False - + return True def _is_toml(self, content: str): @@ -49,7 +51,7 @@ def _is_toml(self, content: str): tomllib.loads(content) except Exception: return False - + return True def _is_yaml(self, content: str): @@ -57,20 +59,26 @@ def _is_yaml(self, content: str): _ = yaml.safe_load(content) except yaml.YAMLError: return False - + return True - + def _is_puppet(self, content: str): try: _, _ = parse(content) except Exception: return False - + return True def _is_ini(self, content): try: _ = ConfigParser().read_string(content) - except Exception as e: + except Exception: return False - return True \ No newline at end of file + return True + + def _is_rst(self, content: str): + features = ['.. code-block::'] + for feature in features: + if feature in content: + return True diff --git a/deepsecrets/core/utils/lexer_finder.py b/deepsecrets/core/utils/lexer_finder.py index 6d4a05a..2e79970 100644 --- a/deepsecrets/core/utils/lexer_finder.py +++ b/deepsecrets/core/utils/lexer_finder.py @@ -1,9 +1,13 @@ from typing import Dict, List, Optional +from pygments import highlight + from deepsecrets.core.model.file import File from deepsecrets.core.utils.guess_filetype import FileTypeGuesser from pygments.lexers import load_lexer_from_file, get_lexer_for_filename, get_lexer_by_name from pygments.util import ClassNotFound +from pygments.formatters import RawTokenFormatter +from pygments.lexers.special import RawTokenLexer from jsx import lexer as lexer_mod @@ -51,6 +55,15 @@ def find(self, file: File): except ClassNotFound as e: pass + # Extremely unreliable + ''' + try: + lexer = guess_lexer(file.content) + return lexer + except ClassNotFound as e: + pass + ''' + return lexer def _determine_extension(self): @@ -60,8 +73,27 @@ def _determine_extension(self): return self.file.extension + def _if_hocon_coffeescript_hack(self): + lexer = get_lexer_by_name('coffeescript') + try: + result = highlight(self.file.content, lexer, RawTokenFormatter()) + raw_tokens = list(RawTokenLexer().get_tokens(result)) + except Exception: + return None + + if len(raw_tokens) == 0: + return None + + return 'coffeescript' + def _try_guess_extension(self) -> Optional[str]: - return FileTypeGuesser().guess(self.file.content) + guess = FileTypeGuesser().guess(self.file.content) + if guess is not None: + return guess + + # HOCON-files are well lexed by the coffeescript pygments lexer. + # so let's try it first + return self._if_hocon_coffeescript_hack() def _determine_distinguishing_feature(self): applicable_strategies = self.probes.get(self.extension, []) diff --git a/deepsecrets/core/utils/lifecycle_hooks.py b/deepsecrets/core/utils/lifecycle_hooks.py index fb8229a..6af5aa4 100644 --- a/deepsecrets/core/utils/lifecycle_hooks.py +++ b/deepsecrets/core/utils/lifecycle_hooks.py @@ -1,5 +1,4 @@ from typing import Optional -from deepsecrets.core.model.token import Token from deepsecrets.core.utils.progress import FileProgress, Progress from multiprocessing.managers import DictProxy @@ -40,10 +39,21 @@ class JobLifecycleHooks(LifecycleHooks): class FileLifecycleHooks(LifecycleHooks): progress: FileProgress - def on_token_processing_start(self, token: Token): - self.progress.on_token_processing_start() + def on_new_tokenizer_added(self, name: str): + self.progress.add_tokenizer(name) self._report() + def on_token_processing_start(self, name: str): + self.progress.on_token_processing_start(name=name) + self._report() + + def on_tokenization_finished(self, name: str, token_count: int): + self.progress.on_tokenization_finished(name=name, token_count=token_count) + def on_token_processing_end(self, findings_count: int): self.progress.add_findings_count(findings_count) self._report() + + def on_tokenization_progress(self, name: str, new_offset: int): + self.progress.on_tokenization_progress(name, new_offset) + self._report() diff --git a/deepsecrets/core/utils/progress.py b/deepsecrets/core/utils/progress.py index 3fb648f..a870183 100644 --- a/deepsecrets/core/utils/progress.py +++ b/deepsecrets/core/utils/progress.py @@ -1,4 +1,5 @@ -from typing import Optional +from datetime import datetime +from typing import Dict, Optional class Progress: @@ -6,11 +7,13 @@ class Progress: started: bool finished: bool failure: bool + latest_report: datetime def __init__(self) -> None: self.started = False self.finished = False self.failure = False + self.latest_report = None def on_start(self): self.started = True @@ -31,49 +34,87 @@ def report(self, child_report: Optional[dict] = None): 'failure': self.failure, 'finished': self.finished, } + self.latest_report = datetime.now() return merged +REPORT_THROTTLING_PERIOD_SECONDS = 1 + + class FileProgress(Progress): total_tokens: int processed_count: int findings: int - file_size: str + file_size: int + file_size_str: str + + tokenizers: Dict[str, Dict] + + def add_tokenizer(self, name: str): + self.tokenizers[name] = { + 'tokenization_done': False, + 'tokenization_progress_percent': 0, + 'tokens_count': 0, + 'tokens_processed': 0, + } - tokenizers_total: int - tokenizers_done: int + def on_tokenization_progress(self, name: str, new_offset: int): + self.tokenizers[name]['tokenization_progress_percent'] = new_offset - def __init__(self, tokenizers_total: int, tokenizers_done: int = 0): + def __init__(self): super().__init__() self.total_tokens = 0 self.processed_count = 0 self.findings = 0 + self.tokenizers = {} - self.tokenizers_total = tokenizers_total - self.tokenizers_done = tokenizers_done - - def on_tokenization_finished(self, token_count: int): + def on_tokenization_finished(self, name: str, token_count: int): + self.tokenizers[name]['tokenization_done'] = True + self.tokenizers[name]['tokens_count'] = token_count self.total_tokens += token_count - self.tokenizers_done += 1 - def on_token_processing_start(self): + def on_token_processing_start(self, name: str): self.processed_count += 1 + self.tokenizers[name]['tokens_processed'] += 1 def add_findings_count(self, count: int): self.findings += count def set_file_size(self, file_size: int): - self.file_size = f'{round(file_size / 1024)} Kb' + self.file_size = file_size + self.file_size_str = f'{round(file_size / 1024)} Kb' def report(self, child_report: Optional[dict] = None): - if self.tokenizers_total > 0 and self.tokenizers_done > 0: - total_tokens = self.total_tokens / (self.tokenizers_done / self.tokenizers_total) - else: - total_tokens = self.total_tokens - - return super().report() | { - 'total_tokens': total_tokens, + # percentages + # Lexer: 95% | FullContent: 5% + # Tokenization: 80%. Tokenization: 5% + # Search: 20% Search: 95% + + percentage = 0 + for name, tokenizer_info in self.tokenizers.items(): + tokens_count = tokenizer_info['tokens_count'] + tokenization_done = tokenizer_info['tokenization_done'] + tokenization_progress_percent = tokenizer_info.get('tokenization_progress_percent', 0) + tokens_processed = tokenizer_info['tokens_processed'] + + if name == 'LexerTokenizer': + if tokenization_done is True: + percentage += 76 + percentage += 14 * (tokens_processed / tokens_count) if tokens_count > 0 else 0 + else: + percentage += 76 * tokenization_progress_percent + + if name == 'FullContentTokenizer': + if tokenization_done is True: + percentage += 0.25 + percentage += 4.75 * (tokens_processed / tokens_count) if tokens_count > 0 else 0 + else: + percentage += 0.25 * tokenization_progress_percent + + return { + 'total_tokens': self.total_tokens, + 'percentage': percentage, 'processed': self.processed_count, 'findings': self.findings, - 'file_size': self.file_size, - } + 'file_size': self.file_size_str, + } | super().report() diff --git a/deepsecrets/core/utils/string.py b/deepsecrets/core/utils/string.py index 059817b..a9b0576 100644 --- a/deepsecrets/core/utils/string.py +++ b/deepsecrets/core/utils/string.py @@ -3,11 +3,25 @@ class StringUtils: @staticmethod def camel_case_divide(string: str) -> str: final = '' - for i, _ in enumerate(string): - final += string[i].lower() + word_start_index = 0 + + for i, char in enumerate(string): + final += char.lower() + if i == len(string) - 1: - continue + break + + if string[i].islower() and string[i + 1].isupper(): + + current_word_len = (i - word_start_index) + 1 + if current_word_len < 2: + continue + + remaining_len = len(string) - (i + 1) + if remaining_len < 2: + continue - if string[i].islower() and string[i+1].isupper(): final += ' ' - return final \ No newline at end of file + word_start_index = i + 1 + + return final diff --git a/deepsecrets/rules/regexes.json b/deepsecrets/rules/regexes.json index 0d95924..e09ab87 100644 --- a/deepsecrets/rules/regexes.json +++ b/deepsecrets/rules/regexes.json @@ -9,31 +9,31 @@ "id": "S1", "name": "RSA private key", "confidence": 9, - "pattern": "-----BEGIN RSA PRIVATE KEY-----[\\S\\s]{15,}?-----END RSA PRIVATE KEY-----" + "pattern": "-----BEGIN[\\S\\s].*RSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*RSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" }, { "id": "S2", "name": "SSH (OPENSSH) private key", "confidence": 9, - "pattern": "-----BEGIN OPENSSH PRIVATE KEY-----[\\S\\s]{15,}?-----END OPENSSH PRIVATE KEY-----" + "pattern": "-----BEGIN[\\S\\s].*OPENSSH[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*OPENSSH[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" }, { "id": "S3", "name": "SSH (DSA) private key", "confidence": 9, - "pattern": "-----BEGIN DSA PRIVATE KEY-----[\\S\\s]{15,}?-----END DSA PRIVATE KEY-----" + "pattern": "-----BEGIN[\\S\\s].*DSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*DSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" }, { "id": "S4", "name": "SSH (EC) private key", "confidence": 9, - "pattern": "-----BEGIN EC PRIVATE KEY-----[\\S\\s]{15,}?-----END EC PRIVATE KEY-----" + "pattern": "-----BEGIN[\\S\\s].*EC[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*EC[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" }, { "id": "S5", "name": "PGP private key block", "confidence": 9, - "pattern": "-----BEGIN PGP PRIVATE KEY BLOCK-----" + "pattern": "-----BEGIN[\\S\\s].*PGP[\\S\\s].*PRIVATE[\\S\\s].*KEY[\\S\\s].*BLOCK-----" }, { "id": "S7", @@ -80,7 +80,7 @@ "target_group": 2, "match_rules": { "2": { - "pattern": "^[^\\$|{|%|<].+[^\\$|}|%|>]$" + "pattern": "^[^\\$|{|%|<|][^ ]+[^\\$|}|%|>]$" } } }, @@ -112,7 +112,7 @@ "id": "S26", "name": "Custom private key", "confidence": 9, - "pattern": "-----BEGIN PRIVATE KEY-----[\\S\\s]{15,}?-----END PRIVATE KEY-----" + "pattern": "-----BEGIN[\\S\\s]{,10}?PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" }, { "id": "S28", @@ -172,5 +172,12 @@ "name": "AWS Access Key ID", "confidence": 9, "pattern": "\\b(A3T[A-Z0-9]|AKIA|AGPA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\\b" + }, + { + "id": "S36", + "description": "https://docs.stripe.com/keys", + "name": "Stripe Secret", + "confidence": 9, + "pattern": "\\b(sk|rk)_(test|live)_[0-9a-zA-Z]{24,35}\\b" } -] \ No newline at end of file +] diff --git a/deepsecrets/rules/variable_scoring_rules.json b/deepsecrets/rules/variable_scoring_rules.json new file mode 100644 index 0000000..25ec9cf --- /dev/null +++ b/deepsecrets/rules/variable_scoring_rules.json @@ -0,0 +1,95 @@ +[ + { + "id": "SEM_VAR_FALSE_STARTING_SEQ", + "name": "Ignore variable values with specific starting", + "pattern": "^(\\$\\{|%env|true|false|\\(|<)", + "target": "VALUE", + "score": -1000 + }, + { + "id": "SEM_VAR_DUMMY_VALUES", + "name": "Ignore variable values with specific flags in values", + "description": "For me in future: The last part of the regex matches only special symbols", + "pattern": "^(null|bearer|undefined|none|todo|.*change.*|.*restore.*|[^A-Za-z0-9]*|%.*%|\\[.*\\]|{.*})$", + "target": "VALUE", + "score": -50 + }, + { + "id": "SEM_VAR_FILE_PATHS", + "enabled": false, + "description": "Slightly reduce score for test/mock/minified files", + "target": "FILEPATH", + "pattern": "(.*\\.min\\.js$|/test/|/tests/|/spec/|/mock/|_test\\.)", + "score": -5 + }, + { + "id": "SEM_VAR_NAME_SLICE_REDFLAGS", + "description": "Reduce score for common non-secret words inside the variable name PARTS", + "target": "NAME_SPACED", + "pattern": "\\b(item|limit|result|window|open|public|str|path|location|field|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template)\\b", + "score": -20 + }, + { + "id": "SEM_VAR_FULLNAME_REDFLAGS", + "description": "Reduce score for common non-secret words inside the variable name", + "target": "NAME_NORMALIZED", + "pattern": "(item|limit|result|public|path|location|input|field|data|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template)", + "score": -15 + }, + { + "id": "SEM_VAR_HIGH_CONFIDENCE_SLICES_1", + "description": "Critical confidence: api key, auth token, etc", + "target": "NAME_SPACED", + "pattern": "\\b(csrf|api|access|refresh|auth|oauth|site|app)\\s+(key|token|secret)\\b", + "score": 25 + }, + { + "id": "SEM_VAR_HIGH_CONFIDENCE_FULLNAME_1", + "description": "Critical confidence: api key, auth token, etc", + "target": "NAME_NORMALIZED", + "pattern": "(csrf|site|api|access|refresh|auth|oauth|site|app)(key|token|secret)", + "score": 16 + }, + { + "id": "SEM_VAR_HIGH_CONFIDENCE_SLICES_2", + "description": "Critical confidence: password, pwd, private key", + "target": "NAME_SPACED", + "pattern": "(oauth|sitekey|passw|pwd|secret)|private\\s+key\\b", + "score": 15 + }, + { + "id": "SEM_VAR_HIGH_CONFIDENCE_FULLNAME_2", + "description": "Critical confidence: password, pwd, private key", + "target": "NAME_NORMALIZED", + "pattern": "(authke|oauth|passw|pwd|secret|rivateke|cesstoke)", + "score": 10 + }, + { + "id": "SEM_VAR_TOKEN_AS_PART_OF_NAME", + "description": "", + "target": "NAME_SPACED", + "pattern": "(\\btoken\\b)", + "score": 8 + }, + { + "id": "SEM_VAR_TOKEN_AS_NAME", + "description": "", + "target": "NAME_NORMALIZED", + "pattern": "(^token$)", + "score": 4 + }, + { + "id": "SEM_VAR_KEY_AS_PART_OF_NAME", + "description": "Key in a variable name slice", + "target": "NAME_SPACED", + "pattern": "\\bkey\\b", + "score": 5 + }, + { + "id": "SEM_VAR_KEY_AS_NAME", + "description": "Only key", + "target": "NAME_NORMALIZED", + "pattern": "^key$", + "score": 2 + } +] \ No newline at end of file diff --git a/deepsecrets/scan_modes/cli.py b/deepsecrets/scan_modes/cli.py index aa1a741..8574d26 100644 --- a/deepsecrets/scan_modes/cli.py +++ b/deepsecrets/scan_modes/cli.py @@ -13,6 +13,7 @@ from deepsecrets.core.model.internal.processing import PerFileAnalysisResult from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder +from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder from deepsecrets.core.tokenizers.full_content import FullContentTokenizer from deepsecrets.core.tokenizers.lexer import LexerTokenizer from deepsecrets.core.utils.lifecycle_hooks import JobLifecycleHooks @@ -45,6 +46,7 @@ def analyzer_bundle(self) -> DotWiz: bundle = super().analyzer_bundle() bundle.update( workdir=self.config.workdir_path, + benchmarking_mode=self.config._benchmarking_mode, engines=self.engines_enabled, rulesets=self.rulesets, ) @@ -54,6 +56,9 @@ def analyzer_bundle(self) -> DotWiz: def _per_file_analyzer(bundle: Any, file: Any, task_id: Optional[int] = None, task_reporter: Optional[Any] = None) -> PerFileAnalysisResult: # type: ignore def __finalize(result: PerFileAnalysisResult): + if bundle.benchmarking_mode is True: + result._file = file + result.errors = get_error_list() return result @@ -108,7 +113,9 @@ def __finalize(result: PerFileAnalysisResult): file_analyzer.add_engine(hashed_secret_engine, [lex]) if eng == SemanticEngine.name: - semantic_engine = SemanticEngine(regex_engine) + semantic_engine = SemanticEngine( + regex_engine, ruleset=bundle.rulesets.get(VariableScoringRulesetBuilder.ruleset_name, []) + ) file_analyzer.add_engine(semantic_engine, [lex]) try: @@ -119,5 +126,7 @@ def __finalize(result: PerFileAnalysisResult): if PROFILER_ON: pass - lifecycle.on_finish(task_reporter[task_id]) + if task_reporter is not None: + lifecycle.on_finish(task_reporter.get('task_id')) + return __finalize(result) diff --git a/poetry.lock b/poetry.lock index 4aea9aa..d3f5c88 100644 --- a/poetry.lock +++ b/poetry.lock @@ -105,12 +105,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {dev = "platform_system == \"Windows\"", test = "sys_platform == \"win32\""} +markers = {main = "sys_platform == \"win32\"", dev = "platform_system == \"Windows\"", test = "sys_platform == \"win32\""} [[package]] name = "coverage" @@ -253,7 +253,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["test"] +groups = ["main", "test"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -266,13 +266,28 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "humanize" +version = "4.13.0" +description = "Python humanize utilities" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "humanize-4.13.0-py3-none-any.whl", hash = "sha256:b810820b31891813b1673e8fec7f1ed3312061eab2f26e3fa192c393d11ed25f"}, + {file = "humanize-4.13.0.tar.gz", hash = "sha256:78f79e68f76f0b04d711c4e55d32bebef5be387148862cb1ef83d2b58e7935a0"}, +] + +[package.extras] +tests = ["freezegun", "pytest", "pytest-cov"] + [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["test"] +groups = ["main", "test"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -517,6 +532,28 @@ files = [ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] +[[package]] +name = "nostril" +version = "1.2.0" +description = "Nonsense String Evaluator" +optional = false +python-versions = ">=3" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +humanize = ">=0.5.1" +plac = ">=0.9.1" +pytest = ">=3.0.5" +tabulate = ">=0.7.7" + +[package.source] +type = "git" +url = "https://github.com/casics/nostril.git" +reference = "HEAD" +resolved_reference = "fbc0c91249283a9fbc9036206391ce1138826fd3" + [[package]] name = "ordered-set" version = "4.1.0" @@ -538,7 +575,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -571,6 +608,18 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "plac" +version = "1.4.5" +description = "The smartest command line arguments parser in the world" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "plac-1.4.5-py2.py3-none-any.whl", hash = "sha256:87187786b4e446688b1cf5112e18fed8a23ab3b316c25fe91266a10bd1736b16"}, + {file = "plac-1.4.5.tar.gz", hash = "sha256:5f05bf85235c017fcd76c73c8101d4ff8e96beb3dc58b9a37de49cac7de82d14"}, +] + [[package]] name = "platformdirs" version = "4.4.0" @@ -594,7 +643,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["test"] +groups = ["main", "test"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -830,7 +879,7 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["test"] +groups = ["main", "test"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1147,13 +1196,28 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + [[package]] name = "tomli" version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -1198,7 +1262,7 @@ files = [ {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] -markers = {dev = "python_version < \"3.11\"", test = "python_full_version <= \"3.11.0a6\""} +markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.11\"", test = "python_full_version <= \"3.11.0a6\""} [[package]] name = "typing-extensions" @@ -1231,4 +1295,4 @@ typing-extensions = ">=4.12.0" [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0.0" -content-hash = "aa1f1d4cc44f5c67732445578008b6d75bca2afecfa3d96fd4358011df62a476" +content-hash = "7b1d6d8c73a100a5277f8855a89fcc75f65555d5f9d56d5af6ccd20d5fcd167a" diff --git a/pyproject.toml b/pyproject.toml index 2f432c7..b28e4b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "sarif-om == 1.0.4", "jschema-to-python == 1.2.3", "rich (==14.2.0)", + "nostril @ git+https://github.com/casics/nostril.git", ] [project.urls] diff --git a/tests/case_helpers.py b/tests/case_helpers.py new file mode 100644 index 0000000..343adc1 --- /dev/null +++ b/tests/case_helpers.py @@ -0,0 +1,36 @@ +from typing import List +from deepsecrets.core.engines.regex import RegexEngine +from deepsecrets.core.engines.semantic import SemanticEngine +from deepsecrets.core.model.file import File +from deepsecrets.core.model.finding import Finding +from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.tokenizers.itokenizer import Tokenizer +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from deepsecrets.core.utils.file_analyzer import FileAnalyzer +from deepsecrets.core.utils.finding_merger import FindingMerger +from deepsecrets.core.utils.fs import get_path_inside_package + + +def run(file, engine, tokenizer): + fa = FileAnalyzer(file) + fa.add_engine(engine, [tokenizer]) + findings: List[Finding] = fa.process() + findings = FindingMerger(findings).merge(choose_final_rule=True) + return findings, tokenizer.tokens, tokenizer.get_variables() + + +def semantic_case(file: File): + builder = VariableScoringRulesetBuilder() + builder.with_rules_from_file(get_path_inside_package('rules/variable_scoring_rules.json')) + engine = SemanticEngine(ruleset=builder.rules) + tokenizer = LexerTokenizer(deep_token_inspection=True) + return run(file, engine, tokenizer) + + +def regex_case(tokenizer: Tokenizer, engine: RegexEngine, file: File): + return run(file, engine, tokenizer) + + +def variable_detection_case(tokenizer: LexerTokenizer, file: File, post_filter=False): + tokenizer.tokenize(file, post_filter=post_filter) + return tokenizer.get_variables(), tokenizer.lexer, tokenizer.tokens diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index e8ba872..e00e54e 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -13,6 +13,8 @@ def args_1(): '/app/tests/fixtures/false_findings.json', '--outfile', './fdsafad.json', + '--max-file-size', + '500', '--verbose', '--reflect-findings-in-return-code', ] @@ -43,10 +45,11 @@ def test_1_cli(args_1): config = tool.get_current_config() assert config is not None - assert len(config.rulesets) == 2 + assert len(config.rulesets) == 3 assert len(config.engines) == 2 assert len(config.global_exclusion_paths) == 1 + assert config.max_file_size == 500 assert config.output.path == './fdsafad.json' assert config.workdir_path == '/app/tests/fixtures/' assert config.output.type == 'json' @@ -63,4 +66,5 @@ def test_2_cli(args_2): assert config is not None assert len(config.global_exclusion_paths) == 2 + assert config.max_file_size == 0 assert config.output.type == 'dojo-sarif' diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..2195e8e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,78 @@ +import pytest + +from deepsecrets.core.engines.hashed_secret import HashedSecretEngine +from deepsecrets.core.engines.regex import RegexEngine +from deepsecrets.core.model.file import File +from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder +from deepsecrets.core.rulesets.regex import RegexRulesetBuilder +from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.tokenizers.full_content import FullContentTokenizer +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from deepsecrets.core.tokenizers.per_line import PerLineTokenizer +from deepsecrets.core.utils.fs import get_path_inside_package + + +def pytest_configure(config): + config.addinivalue_line('markers', "fixture_file_path: pass file path to retrieve a File object") + + +@pytest.fixture +def hashed_secrets_fixture_file_location(): + return 'tests/fixtures/hashed_secrets.json' + + +@pytest.fixture +def regex_ruleset_location(): + return 'rules/regexes.json' + + +@pytest.fixture +def variable_scoring_ruleset_location(): + return 'rules/variable_scoring_rules.json' + + +@pytest.fixture +def file(request): + # Get the marker named 'load_file' from the test + marker = request.node.get_closest_marker('fixture_file_path') + if marker is None: + return None + + file_path = f'tests/fixtures/{marker.args[0]}' + return File(path=file_path, relative_path=file_path) + + +@pytest.fixture +def variable_scoring_rules(variable_scoring_ruleset_location): + builder = VariableScoringRulesetBuilder() + builder.with_rules_from_file(get_path_inside_package(variable_scoring_ruleset_location)) + return builder.rules + + +@pytest.fixture +def hashed_secrets_engine(hashed_secrets_fixture_file_location): + builder = HashedSecretsRulesetBuilder() + builder.with_rules_from_file(hashed_secrets_fixture_file_location) + return HashedSecretEngine(ruleset=builder.rules) + + +@pytest.fixture +def regex_engine(regex_ruleset_location): + builder = RegexRulesetBuilder() + builder.with_rules_from_file(get_path_inside_package(regex_ruleset_location)) + return RegexEngine(ruleset=builder.rules) + + +@pytest.fixture +def lexer_tokenizer(): + yield LexerTokenizer(deep_token_inspection=True) + + +@pytest.fixture +def full_content_tokenizer(): + yield FullContentTokenizer() + + +@pytest.fixture +def per_line_tokenizer(): + yield PerLineTokenizer() diff --git a/tests/core/cohesive/test_specific_cases.py b/tests/core/cohesive/test_specific_cases.py new file mode 100644 index 0000000..3fbf534 --- /dev/null +++ b/tests/core/cohesive/test_specific_cases.py @@ -0,0 +1,44 @@ +import pytest + +from deepsecrets.core.engines.regex import RegexEngine +from deepsecrets.core.model.file import File +from deepsecrets.core.tokenizers.full_content import FullContentTokenizer +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import regex_case, semantic_case, variable_detection_case + + +@pytest.mark.fixture_file_path('cases/inline_yaml_inside_yaml_inside_markdown.md') +def test_6(file: File): + + findings, tokens, variables = semantic_case(file) + assert len(variables) == 7 + assert len(findings) == 1 + + +@pytest.mark.fixture_file_path('cases/inline_yaml_inside_yaml.yaml') +def test_7(file: File, lexer_tokenizer: LexerTokenizer): + vars, _, tokens = variable_detection_case(lexer_tokenizer, file) + assert len(vars) == 7 + + +@pytest.mark.fixture_file_path('cases/code_in_markdown_with_lang_labels.md') +def test_8(file: File, lexer_tokenizer: LexerTokenizer): + vars, _, tokens = variable_detection_case(lexer_tokenizer, file) + assert 1 == 1 + + +@pytest.mark.fixture_file_path('cases/tricky_secrets.min.js') +def test_9(file: File): + + findings, tokens, variables = semantic_case(file) + assert len(findings) == 4 + + +@pytest.mark.fixture_file_path('1.pem') +def test_10(file: File, full_content_tokenizer: FullContentTokenizer, regex_engine: RegexEngine): + findings, tokens, variables = regex_case( + tokenizer=full_content_tokenizer, + engine=regex_engine, + file=file, + ) + assert 1 == 1 diff --git a/tests/core/engines/hashed_secret/test_hs.py b/tests/core/engines/hashed_secret/test_hs.py index 49ee1dc..aa51587 100644 --- a/tests/core/engines/hashed_secret/test_hs.py +++ b/tests/core/engines/hashed_secret/test_hs.py @@ -3,42 +3,28 @@ import pytest from deepsecrets.core.engines.hashed_secret import HashedSecretEngine -from deepsecrets.core.engines.regex import RegexEngine from deepsecrets.core.model.file import File from deepsecrets.core.model.finding import Finding from deepsecrets.core.model.rules.hashed_secret import HashedSecretRule from deepsecrets.core.model.token import Token -from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.tokenizers.lexer import LexerTokenizer -@pytest.fixture(scope='module') -def file(): - path = 'tests/fixtures/1.py' - return File(path=path, relative_path=path) +def test_ruleset_init_success(hashed_secrets_engine: HashedSecretEngine): + rules = hashed_secrets_engine.ruleset - -@pytest.fixture(scope='module') -def engine(): - builder = HashedSecretsRulesetBuilder() - builder.with_rules_from_file('tests/fixtures/hashed_secrets.json') - return HashedSecretEngine(ruleset=builder.rules) + assert rules[0] == rules[0] + assert rules[1] == rules[1] + assert rules[1] != rules[0] -def test_1(file: File, engine: RegexEngine): +@pytest.mark.fixture_file_path('1.py') +def test_engine_works(file: File, hashed_secrets_engine: HashedSecretEngine): findings: List[Finding] = [] tokens: List[Token] = LexerTokenizer(deep_token_inspection=True).tokenize(file) for token in tokens: - findings.extend(engine.search(token)) + findings.extend(hashed_secrets_engine.search(token)) assert len(findings) == 1 assert isinstance(findings[0].rules[0], HashedSecretRule) assert findings[0].rules[0].hashed_val == '8c535f99d6d0fa55b64af0fae6e3b6829eda413b' - - -def test_2(engine: HashedSecretEngine): - rules = engine.ruleset - - assert rules[0] == rules[0] - assert rules[1] == rules[1] - assert rules[1] != rules[0] diff --git a/tests/core/engines/regex/test_regex.py b/tests/core/engines/regex/test_regex.py index 2910be9..ebd8276 100644 --- a/tests/core/engines/regex/test_regex.py +++ b/tests/core/engines/regex/test_regex.py @@ -1,105 +1,54 @@ -from typing import List - import pytest from deepsecrets.core.engines.regex import RegexEngine from deepsecrets.core.model.file import File -from deepsecrets.core.model.finding import Finding, FindingMerger, FindingResponse -from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.core.tokenizers.full_content import FullContentTokenizer -from deepsecrets.core.tokenizers.lexer import LexerTokenizer -from deepsecrets.core.utils.fs import get_path_inside_package - - -@pytest.fixture(scope='module') -def file(): - path = 'tests/fixtures/regex_checks.txt' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_extless(): - path = 'tests/fixtures/extless/radius' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_go_7(): - path = 'tests/fixtures/7.go' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def regex_engine(): - builder = RegexRulesetBuilder() - builder.with_rules_from_file(get_path_inside_package('rules/regexes.json')) - return RegexEngine(ruleset=builder.rules) +from tests.case_helpers import regex_case +@pytest.mark.fixture_file_path('regex_checks.txt') def test_1(file: File, regex_engine: RegexEngine): - findings: List[Finding] = [] - tokens = FullContentTokenizer().tokenize(file) - for token in tokens: - token_findings = regex_engine.search(token) - for finding in token_findings: - finding.map_on_file(file=file, relative_start=token.span[0]) - findings.append(finding) - - for finding in findings: - finding.map_on_file(file=file, relative_start=finding.start_offset) - finding.choose_final_rule() - - assert len(findings) == 10 - assert findings[0].rules[0].id == 'S0' - assert findings[1].rules[0].id == 'S0' - assert findings[2].rules[0].id == 'S1' - assert findings[3].rules[0].id == 'S2' - assert findings[4].rules[0].id == 'S3' - assert findings[5].rules[0].id == 'S4' - assert findings[6].rules[0].id == 'S5' - assert findings[7].rules[0].id == 'S19' - assert findings[8].rules[0].id == 'S19' - - assert findings[9].rules[0].id == 'S19' + findings, tokens, variables = regex_case( + tokenizer=FullContentTokenizer(), + engine=regex_engine, + file=file, + ) + + assert len(findings) == 11 + assert findings[0].final_rule.id == 'S0' + assert findings[1].final_rule.id == 'S0' + assert findings[2].final_rule.id == 'S1' + assert findings[3].final_rule.id == 'S2' + assert findings[4].final_rule.id == 'S3' + assert findings[5].final_rule.id == 'S4' + assert findings[6].final_rule.id == 'S5' + assert findings[7].final_rule.id == 'S19' + assert findings[8].final_rule.id == 'S19' + + assert findings[9].final_rule.id == 'S19' assert findings[9].detection == 'sneakypass' - findings = FindingMerger(findings).merge() - assert len(findings) == 10 - - response = FindingResponse.from_list(findings) + assert findings[10].final_rule.id == 'S19' -def test_extless(file_extless: File, regex_engine: RegexEngine): - findings: List[Finding] = [] - tokens = FullContentTokenizer().tokenize(file_extless) - tokens_lex = LexerTokenizer(deep_token_inspection=True).tokenize(file_extless) - - for token in tokens: - token_findings = regex_engine.search(token) - for finding in token_findings: - finding.map_on_file(file=file_extless, relative_start=token.span[0]) - findings.append(finding) - - for finding in findings: - finding.map_on_file(file=file_extless, relative_start=finding.start_offset) - finding.choose_final_rule() +@pytest.mark.fixture_file_path('extless/radius') +def test_extless(file: File, regex_engine: RegexEngine): + findings, tokens, variables = regex_case( + tokenizer=FullContentTokenizer(), + engine=regex_engine, + file=file, + ) assert len(findings) == 1 assert findings[0].rules[0].id == 'S28' -def test_go_7(file_go_7: File, regex_engine: RegexEngine): - findings: List[Finding] = [] - tokens = FullContentTokenizer().tokenize(file_go_7) - - for token in tokens: - token_findings = regex_engine.search(token) - for finding in token_findings: - finding.map_on_file(file=file_go_7, relative_start=token.span[0]) - findings.append(finding) - - for finding in findings: - finding.map_on_file(file=file_go_7, relative_start=finding.start_offset) - finding.choose_final_rule() +@pytest.mark.fixture_file_path('7.go') +def test_go_7(file: File, regex_engine: RegexEngine): + findings, tokens, variables = regex_case( + tokenizer=FullContentTokenizer(), + engine=regex_engine, + file=file, + ) assert len(findings) == 0 diff --git a/tests/core/engines/semantic/test_ruleset.py b/tests/core/engines/semantic/test_ruleset.py new file mode 100644 index 0000000..402c347 --- /dev/null +++ b/tests/core/engines/semantic/test_ruleset.py @@ -0,0 +1,8 @@ +from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.utils.fs import get_path_inside_package + + +def test_builder(): + builder = VariableScoringRulesetBuilder() + builder.with_rules_from_file(get_path_inside_package('rules/variable_scoring_rules.json')) + assert len(builder.rules) != 0 diff --git a/tests/core/engines/semantic/test_semantic.py b/tests/core/engines/semantic/test_semantic.py index 17c5ce3..f2744b2 100644 --- a/tests/core/engines/semantic/test_semantic.py +++ b/tests/core/engines/semantic/test_semantic.py @@ -1,149 +1,104 @@ -from typing import List import pytest -from deepsecrets.core.engines.semantic import SemanticEngine from deepsecrets.core.model.file import File -from deepsecrets.core.model.finding import Finding, FindingMerger from deepsecrets.core.model.token import SemanticType -from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import semantic_case -@pytest.fixture(scope='module') -def file() -> File: - path = 'tests/fixtures/4.py' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_json_2() -> File: - path = 'tests/fixtures/2.json' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_toml_1() -> File: - path = 'tests/fixtures/1.toml' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_toml_2() -> File: - path = 'tests/fixtures/2.toml' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_sh_2() -> File: - path = 'tests/fixtures/2.sh' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_html_1() -> File: - path = 'tests/fixtures/1.html' - return File(path=path, relative_path=path) - - -def test_1_semantic_engine(file: File): - tokens = LexerTokenizer(deep_token_inspection=True).tokenize(file) +@pytest.mark.fixture_file_path('4.py') +def test_python_1(file: File): + findings, tokens, variables = semantic_case(file) assert len(tokens) == 13 - assert tokens[3].semantic.type == SemanticType.VAR + assert tokens[3].semantic.type == SemanticType.VARIABLE assert tokens[3].semantic.name == 'pass' - engine = SemanticEngine(subengine=None) - findings = engine.search(tokens[3]) - assert len(findings) == 1 - assert findings[0].rules[0].name == 'Var naming' + assert len(findings) == 0 -def test_2_semantic_engine(file_json_2: File): - tokens = LexerTokenizer(deep_token_inspection=True).tokenize(file_json_2) +@pytest.mark.fixture_file_path('2.json') +def test_json_2(file: File): + findings, tokens, variables = semantic_case(file) assert len(tokens) == 6 - assert tokens[0].semantic.type == SemanticType.VAR + assert tokens[0].semantic.type == SemanticType.VARIABLE assert tokens[0].semantic.name == 'access_Token' - assert tokens[1].semantic.type == SemanticType.VAR + assert tokens[1].semantic.type == SemanticType.VARIABLE assert tokens[1].semantic.name == 'accessToken' - engine = SemanticEngine(subengine=None) - - findings = [] - for token in tokens: - findings.extend(engine.search(token)) - assert len(findings) == 3 assert findings[0].rules[0].name == 'Entropy+Var naming' assert findings[1].rules[0].name == 'Entropy+Var naming' assert findings[2].rules[0].name == 'Var naming' -def test_3_semantic_engine(file_toml_1: File): - tokens = LexerTokenizer(deep_token_inspection=True).tokenize(file_toml_1) +@pytest.mark.fixture_file_path('1.toml') +def test_toml_1(file: File): + findings, tokens, variables = semantic_case(file) assert len(tokens) == 51 - assert tokens[50].semantic.type == SemanticType.VAR + assert tokens[50].semantic.type == SemanticType.VARIABLE assert tokens[50].semantic.name == 'MATTERMOST_BOT_TOKEN' - engine = SemanticEngine(subengine=None) - - findings = [] - for token in tokens: - findings.extend(engine.search(token)) - assert len(findings) == 2 - assert findings[0].rules[0].name == 'Var naming' - assert findings[1].rules[0].name == 'Var naming' + assert findings[0].rules[0].name == 'Entropy+Var naming' + assert findings[1].rules[0].name == 'Entropy+Var naming' -def test_4_semantic_engine(file_toml_2: File): - tokens = LexerTokenizer(deep_token_inspection=True).tokenize(file_toml_2) +@pytest.mark.fixture_file_path('2.toml') +def test_toml_2(file: File): + findings, tokens, _ = semantic_case(file) assert len(tokens) == 13 + assert len(findings) == 4 - engine = SemanticEngine(subengine=None) - - findings = [] - findings.extend(engine.search(tokens[4])) - findings.extend(engine.search(tokens[10])) - findings.extend(engine.search(tokens[12])) +@pytest.mark.fixture_file_path('2.sh') +def test_sh_2(file: File): + findings, tokens, _ = semantic_case(file) + assert len(tokens) == 16 assert len(findings) == 1 - assert findings[0].rules[0].name == 'Var naming' + assert findings[0].final_rule.name == 'Dangerous condition' -def test_5_semantic_engine(file_sh_2: File): - tokens = LexerTokenizer(deep_token_inspection=True).tokenize(file_sh_2) - assert len(tokens) == 16 +@pytest.mark.fixture_file_path('1.html') +def test_html_1(file: File): + findings, _, _ = semantic_case(file) + assert len(findings) == 0 - engine = SemanticEngine(subengine=None) - findings: List[Finding] = [] - for token in tokens: - findings.extend(engine.search(token)) +@pytest.mark.fixture_file_path('5_1.min.js') +def test_minjs_5_1(file: File): + findings, tokens, vars = semantic_case(file) + assert len(findings) == 2 - for finding in findings: - finding.map_on_file(file=file_sh_2, relative_start=finding.start_offset) - finding.choose_final_rule() - findings = FindingMerger(findings).merge() +@pytest.mark.fixture_file_path('3.html') +def test_html_3(file: File): + findings, _, _ = semantic_case(file) assert len(findings) == 1 - assert findings[0].final_rule.name == 'Dangerous condition' -def test_6_semantic_engine(file_html_1: File): - tokens = LexerTokenizer(deep_token_inspection=True).tokenize(file_html_1) - # assert len(tokens) == 16 +@pytest.mark.fixture_file_path('edge_cases/code_in_markdown.md') +def test_ec_code_in_markdown(file: File): + findings, tokens, vars = semantic_case(file) + assert len(findings) == 2 - engine = SemanticEngine(subengine=None) - findings: List[Finding] = [] - for token in tokens: - findings.extend(engine.search(token)) +@pytest.mark.fixture_file_path('8.go') +def test_go_8(file: File): + findings, tokens, vars = semantic_case(file) + assert len(findings) == 2 - for finding in findings: - finding.map_on_file(file=file_html_1, relative_start=finding.start_offset) - finding.choose_final_rule() - findings = FindingMerger(findings).merge() - assert len(findings) == 0 +@pytest.mark.fixture_file_path('1.go') +def test_go_1(file: File): + findings, tokens, vars = semantic_case(file) + assert len(findings) == 2 + + +@pytest.mark.fixture_file_path('3.conf') +def test_conf_3(file: File): + # TODO: HOCON + findings, tokens, vars = semantic_case(file) + assert len(findings) == 1 diff --git a/tests/core/helpers/test_content_analyzer.py b/tests/core/helpers/test_content_analyzer.py index d35e767..f51a2be 100644 --- a/tests/core/helpers/test_content_analyzer.py +++ b/tests/core/helpers/test_content_analyzer.py @@ -14,7 +14,7 @@ def file() -> File: return File(path=path, relative_path=path, content=BASE_64_STR) -def test_semantic_engine(file: File): +def test_content_analyzer_engine(file: File): tokens = FullContentTokenizer().tokenize(file) assert len(tokens) == 1 diff --git a/tests/core/helpers/test_entropy.py b/tests/core/helpers/test_entropy.py index 3539530..c2fc597 100644 --- a/tests/core/helpers/test_entropy.py +++ b/tests/core/helpers/test_entropy.py @@ -22,4 +22,4 @@ def test_password_entropy(): assert 3.85 <= entropy <= 3.86 -# Oops, it seems like the password has less entropy that a statement +# Oops, it seems like the password has less entropy than a statement diff --git a/tests/core/helpers/test_variable_evaluator.py b/tests/core/helpers/test_variable_evaluator.py new file mode 100644 index 0000000..510cb2b --- /dev/null +++ b/tests/core/helpers/test_variable_evaluator.py @@ -0,0 +1,95 @@ +from deepsecrets.core.helpers.variable_evaluator import EvaluationResult, VariableEvaluator +from deepsecrets.core.model.semantic import Context + + +def test_1(variable_scoring_rules): + ctx = Context(name='SERVICE_OAUTH', value='vhpn6mbsvhpn6mbsvhpn6mbsvhpn6mbs', filepath='good') + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + + assert result.is_dangerous is True and result.nonsence_value_score >= 0.5 + + +def test_2(variable_scoring_rules): + # Cisco_cisco_key:"shape=mxgraph.cisco.misc.key;fillColor=#036897;strokeColor=#ffffff" + ctx = Context( + name='Cisco_cisco_key', + value='shape=mxgraph.cisco.misc.key;fillColor=#036897;strokeColor=#ffffff', + filepath='some.min.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True and result.nonsence_value_score < 0.5 + + +def test_3(variable_scoring_rules): + # data-fp-apikey="AFcwrT3qvREad1lGpKXXWz" + ctx = Context( + name='data-fp-apikey', + value='AFcwrT3qvREad1lGpKXXWz', + filepath='nice.html', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True and result.nonsence_value_score > 0.5 + + +def test_4(variable_scoring_rules): + # result_key: GameSessions + ctx = Context( + name='result_key', + value='GameSessions', + filepath='one.min.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False and result.nonsence_value_score < 0.5 + + +def test_5(variable_scoring_rules): + # limit_key: VpcEndpointConnections + ctx = Context( + name='limit_key', + value='VpcEndpointConnections', + filepath='one.min.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False + assert result.nonsence_value_score < 0.5 + + +def test_6(variable_scoring_rules): + # output_token: PolicyDescriptions + ctx = Context( + name='output_token', + value='PolicyDescriptions', + filepath='one.min.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False and result.nonsence_value_score < 0.5 + + +def test_7(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='input_token', + value='MaxRecords', + filepath='one.min.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False and result.nonsence_value_score < 0.5 + + +def test_8(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='key', + value='eek05SKgALpQQg20ASrCzm1ZF7o', + filepath='2.conf', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True and result.nonsence_value_score >= 0.5 diff --git a/tests/core/model/test_file.py b/tests/core/model/test_file.py index 45b51fa..c2317b5 100644 --- a/tests/core/model/test_file.py +++ b/tests/core/model/test_file.py @@ -5,106 +5,102 @@ LINE_BREAK = '\n' -@pytest.fixture(scope='module') -def model() -> File: - path = 'tests/fixtures/4.go' - return File(path=path, relative_path=path) +@pytest.mark.fixture_file_path('4.go') +def test_basic_info(file: File): + assert file.path == '/app/tests/fixtures/4.go' + assert file.relative_path == 'tests/fixtures/4.go' + assert file.extension == 'go' + assert file.length == 395 + assert len(file.line_offsets) == 15 -def test_basic_info(model): - assert model.path == '/app/tests/fixtures/4.go' - assert model.relative_path == 'tests/fixtures/4.go' - assert model.extension == 'go' - assert model.length == 395 - assert len(model.line_offsets) == 15 +@pytest.mark.fixture_file_path('4.go') +def test_line_offsets(file): + assert file.line_offsets[1] == (0, 48) + assert file.content[48] == LINE_BREAK + assert file.line_offsets[2] == (49, 152) + assert file.content[152] == LINE_BREAK -def test_line_offsets(model): - assert model.line_offsets[1] == (0, 48) - assert model.content[48] == LINE_BREAK + assert file.line_offsets[3] == (153, 154) + assert file.content[154] == LINE_BREAK - assert model.line_offsets[2] == (49, 152) - assert model.content[152] == LINE_BREAK + assert file.line_offsets[4] == (155, 194) + assert file.content[194] == LINE_BREAK - assert model.line_offsets[3] == (153, 154) - assert model.content[154] == LINE_BREAK + assert file.line_offsets[5] == (195, 240) + assert file.content[240] == LINE_BREAK - assert model.line_offsets[4] == (155, 194) - assert model.content[194] == LINE_BREAK + assert file.line_offsets[6] == (241, 293) + assert file.content[293] == LINE_BREAK - assert model.line_offsets[5] == (195, 240) - assert model.content[240] == LINE_BREAK + assert file.line_offsets[7] == (294, 294) + assert file.content[294] == LINE_BREAK - assert model.line_offsets[6] == (241, 293) - assert model.content[293] == LINE_BREAK + assert file.line_offsets[8] == (295, 311) + assert file.content[311] == LINE_BREAK - assert model.line_offsets[7] == (294, 294) - assert model.content[294] == LINE_BREAK + assert file.line_offsets[9] == (312, 325) + assert file.content[325] == LINE_BREAK - assert model.line_offsets[8] == (295, 311) - assert model.content[311] == LINE_BREAK + assert file.line_offsets[10] == (326, 328) + assert file.content[328] == LINE_BREAK - assert model.line_offsets[9] == (312, 325) - assert model.content[325] == LINE_BREAK + assert file.line_offsets[11] == (329, 358) + assert file.content[358] == LINE_BREAK - assert model.line_offsets[10] == (326, 328) - assert model.content[328] == LINE_BREAK + assert file.line_offsets[12] == (359, 375) + assert file.content[375] == LINE_BREAK - assert model.line_offsets[11] == (329, 358) - assert model.content[358] == LINE_BREAK + assert file.line_offsets[13] == (376, 389) + assert file.content[389] == LINE_BREAK - assert model.line_offsets[12] == (359, 375) - assert model.content[375] == LINE_BREAK + assert file.line_offsets[14] == (390, 392) + assert file.content[392] == LINE_BREAK - assert model.line_offsets[13] == (376, 389) - assert model.content[389] == LINE_BREAK + assert file.line_offsets[15] == (393, 394) + assert file.content[394] == LINE_BREAK - assert model.line_offsets[14] == (390, 392) - assert model.content[392] == LINE_BREAK + assert file.content[-1] == file.content[file.length - 1] == file.content[394] == '\n' - assert model.line_offsets[15] == (393, 394) - assert model.content[394] == LINE_BREAK - assert ( - model.content[-1] - == model.content[model.length - 1] - == model.content[394] - == '\n' - ) - - -def test_caching(model: File): +@pytest.mark.fixture_file_path('4.go') +def test_caching(file: File): LINUM = 4 - line_contents = model.get_line_contents(LINUM) + line_contents = file.get_line_contents(LINUM) assert line_contents == '''\ttest2 := os.Getenv(`TEST_TEST`, "lol")''' - assert model.line_contents_cache[LINUM] == line_contents + assert file.line_contents_cache[LINUM] == line_contents -def test_get_full_line_for_position(model: File): +@pytest.mark.fixture_file_path('4.go') +def test_get_full_line_for_position(file: File): POSITION = 94 projected_line_number = 2 - line_contents = model.get_full_line_for_position(POSITION) + line_contents = file.get_full_line_for_position(POSITION) assert ( line_contents == '\tos.Setenv("RABBITMQ_URL", "amqp://fake_user:TESTSECRET1234@rabbitmq-esp01.miami.example.com:5672/esp")' ) - assert projected_line_number in model.line_contents_cache.keys() + assert projected_line_number in file.line_contents_cache.keys() -def test_get_line_number(model: File): +@pytest.mark.fixture_file_path('4.go') +def test_get_line_number(file: File): POSITION = 94 projected_line_number = 2 - line_number = model.get_line_number(POSITION) + line_number = file.get_line_number(POSITION) assert line_number == projected_line_number -def test_1_span_for_string(model: File): +@pytest.mark.fixture_file_path('4.go') +def test_1_span_for_string(file: File): looking_for = 'rabbitmq-esp01' - span = model.get_span_for_string(looking_for) + span = file.get_span_for_string(looking_for) assert span == (109, 123) -def test_2_span_for_string(model: File): +@pytest.mark.fixture_file_path('4.go') +def test_2_span_for_string(file: File): looking_for = 'rabbitmq-esp01' - span = model.get_span_for_string(looking_for, between=(130, 150)) + span = file.get_span_for_string(looking_for, between=(130, 150)) assert span is None diff --git a/tests/core/model/test_finding.py b/tests/core/model/test_finding.py index f49f7e9..edbfdc8 100644 --- a/tests/core/model/test_finding.py +++ b/tests/core/model/test_finding.py @@ -12,18 +12,12 @@ FINDING_SPAN_INSIDE_TOKEN = (18, 32) -@pytest.fixture(scope='module') -def file() -> File: - path = 'tests/fixtures/4.go' - return File(path=path, relative_path=path) - - @pytest.fixture(scope='module') def rule() -> Rule: return Rule(id='test') -@pytest.fixture(scope='module') +@pytest.mark.fixture_file_path('4.go') def token(file: File) -> Token: return Token( file=file, @@ -32,19 +26,21 @@ def token(file: File) -> Token: ) -def test_1_finding(file: File, token: Token, rule: Rule): - assert file.content[token.span[0] : token.span[1]] == TEST_TOKEN_CONTENTS +@pytest.mark.fixture_file_path('4.go') +def test_1_finding(file: File, rule: Rule): + _token = token(file) + assert file.content[_token.span[0] : _token.span[1]] == TEST_TOKEN_CONTENTS new_finding = Finding( file=file, rules=[rule], start_offset=FINDING_SPAN_INSIDE_TOKEN[0], end_offset=FINDING_SPAN_INSIDE_TOKEN[1], - detection=token.content[FINDING_SPAN_INSIDE_TOKEN[0] : FINDING_SPAN_INSIDE_TOKEN[1]], + detection=_token.content[FINDING_SPAN_INSIDE_TOKEN[0] : FINDING_SPAN_INSIDE_TOKEN[1]], ) assert new_finding.detection == FINDING_CONTENT - new_finding.map_on_file(relative_start=token.span[0]) + new_finding.map_on_file(relative_start=_token.span[0]) assert new_finding.start_offset == TOKEN_SPAN[0] + FINDING_SPAN_INSIDE_TOKEN[0] assert new_finding.end_offset == TOKEN_SPAN[0] + FINDING_SPAN_INSIDE_TOKEN[1] diff --git a/tests/core/model/test_token.py b/tests/core/model/test_token.py index 059fb42..6094f90 100644 --- a/tests/core/model/test_token.py +++ b/tests/core/model/test_token.py @@ -4,18 +4,11 @@ from deepsecrets.core.model.semantic import Variable from deepsecrets.core.model.token import Semantic, SemanticType, Token -TEST_TOKEN_CONTENTS = ( - '"amqp://fake_user:TESTSECRET1234@rabbitmq-esp01.miami.example.com:5672/esp"' -) +TEST_TOKEN_CONTENTS = '"amqp://fake_user:TESTSECRET1234@rabbitmq-esp01.miami.example.com:5672/esp"' TOKEN_SPAN = (76, 151) -@pytest.fixture(scope='module') -def file() -> File: - path = 'tests/fixtures/4.go' - return File(path=path, relative_path=path) - - +@pytest.mark.fixture_file_path('4.go') def test_token(file: File): token = Token( file=file, @@ -29,6 +22,7 @@ def test_token(file: File): assert len(token.type) == 0 +@pytest.mark.fixture_file_path('4.go') def test_semantic_token(file: File): token = Token( file=file, @@ -41,7 +35,7 @@ def test_semantic_token(file: File): variable.name = token variable.value = token - token.semantic = Semantic(type=SemanticType.VAR, name=variable.name.content) + token.semantic = Semantic(type=SemanticType.VARIABLE, payload=variable) assert token.span == TOKEN_SPAN assert token.length == 75 diff --git a/tests/core/model/test_variable_context.py b/tests/core/model/test_variable_context.py new file mode 100644 index 0000000..f0a5bbc --- /dev/null +++ b/tests/core/model/test_variable_context.py @@ -0,0 +1,57 @@ +from deepsecrets.core.model.semantic import Context + + +def test_1_context(): + ctx = Context(name='basicCamelCaseExample', value='', filepath='') + assert ctx.name == 'basicCamelCaseExample' + assert ctx.name_normalized == 'basiccamelcaseexample' + assert ctx.name_parts == ['basic', 'camel', 'case', 'example'] + assert ctx.name_spaced == 'basic camel case example' + + +def test_2_context(): + ctx = Context(name='BasicCamelCaseExample', value='', filepath='') + assert ctx.name == 'BasicCamelCaseExample' + assert ctx.name_normalized == 'basiccamelcaseexample' + assert ctx.name_parts == ['basic', 'camel', 'case', 'example'] + assert ctx.name_spaced == 'basic camel case example' + + +def test_3_context(): + ctx = Context(name='Basic_CamelCaseExample', value='', filepath='') + assert ctx.name == 'Basic_CamelCaseExample' + assert ctx.name_normalized == 'basiccamelcaseexample' + assert ctx.name_parts == ['basic', 'camel', 'case', 'example'] + assert ctx.name_spaced == 'basic camel case example' + + +def test_4_context(): + ctx = Context(name='extra_basicCamelCaseExample', value='', filepath='') + assert ctx.name == 'extra_basicCamelCaseExample' + assert ctx.name_normalized == 'extrabasiccamelcaseexample' + assert ctx.name_parts == ['extra', 'basic', 'camel', 'case', 'example'] + assert ctx.name_spaced == 'extra basic camel case example' + + +def test_5_context(): + ctx = Context(name='Another-extra_basicCamelCaseExample', value='', filepath='') + assert ctx.name == 'Another-extra_basicCamelCaseExample' + assert ctx.name_normalized == 'anotherextrabasiccamelcaseexample' + assert ctx.name_parts == ['another', 'extra', 'basic', 'camel', 'case', 'example'] + assert ctx.name_spaced == 'another extra basic camel case example' + + +def test_6_context(): + ctx = Context(name='another extra_basicCamelCaseExample', value='', filepath='') + assert ctx.name == 'another extra_basicCamelCaseExample' + assert ctx.name_normalized == 'anotherextrabasiccamelcaseexample' + assert ctx.name_parts == ['another', 'extra', 'basic', 'camel', 'case', 'example'] + assert ctx.name_spaced == 'another extra basic camel case example' + + +def test_7_context(): + ctx = Context(name='data-sitekey', value='', filepath='') + assert ctx.name == 'data-sitekey' + assert ctx.name_normalized == 'datasitekey' + assert ctx.name_parts == ['data', 'sitekey'] + assert ctx.name_spaced == 'data sitekey' diff --git a/tests/core/tokenizers/lexer/variable_detection/test_conf.py b/tests/core/tokenizers/lexer/variable_detection/test_conf.py index 5aaadfa..7e02a59 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_conf.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_conf.py @@ -1,113 +1,52 @@ import pytest -from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_toml_1(): - path = 'tests/fixtures/1.toml' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_json_1(): - path = 'tests/fixtures/1.json' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_json_2_broken(): - path = 'tests/fixtures/2.json' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_yaml_1(): - path = 'tests/fixtures/1.yaml' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_yml_1(): - path = 'tests/fixtures/1.yml' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_ini_1(): - path = 'tests/fixtures/1.ini' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_pp_1(): - path = 'tests/fixtures/1.pp' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_conf_2(): - path = 'tests/fixtures/2.conf' - return File(path=path, relative_path=path) - - -def test_1(file_toml_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_toml_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.toml') +def test_1(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 50 -def test_2(file_json_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_json_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.json') +def test_2(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 1 -def test_3(file_yaml_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_yaml_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.yaml') +def test_3(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 4 -def test_4(file_ini_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_ini_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.ini') +def test_4(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 9 -def test_5(file_pp_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_pp_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.pp') +def test_5(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 37 -def test_6(file_json_2_broken): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_json_2_broken, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('2.json') +def test_6(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 6 -def test_7(file_yml_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_yml_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.yml') +def test_7(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 1 -def test_8(file_conf_2): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_conf_2, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('2.conf') +def test_8(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 6 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_cs.py b/tests/core/tokenizers/lexer/variable_detection/test_cs.py index 13ea30a..5ce0152 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_cs.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_cs.py @@ -1,18 +1,10 @@ import pytest -from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_cs_1(): - path = 'tests/fixtures/1.cs' - return File(path=path, relative_path=path) - - -def test_1(file_cs_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_cs_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.cs') +def test_1(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 9 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_go.py b/tests/core/tokenizers/lexer/variable_detection/test_go.py index 4494375..b4bcf06 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_go.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_go.py @@ -1,95 +1,46 @@ import pytest -from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_go_1(): - path = 'tests/fixtures/1.go' - return File(path=path, relative_path=path) +@pytest.mark.fixture_file_path('1.go') +def test_1(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 66 -@pytest.fixture(scope='module') -def file_go_2(): - path = 'tests/fixtures/2.go' - return File(path=path, relative_path=path) +@pytest.mark.fixture_file_path('2.go') +def test_2(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 87 -@pytest.fixture(scope='module') -def file_go_3(): - path = 'tests/fixtures/3.go' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_go_4(): - path = 'tests/fixtures/4.go' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_go_5(): - path = 'tests/fixtures/5.go' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_go_6(): - path = 'tests/fixtures/6.go' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_go_7(): - path = 'tests/fixtures/7.go' - return File(path=path, relative_path=path) - - -def test_1(file_go_1): - lex = LexerTokenizer(deep_token_inspection=True) - tokens = lex.tokenize(file_go_1, post_filter=False) - variables = lex.get_variables(tokens) - assert len(variables) == 65 - - -def test_2(file_go_2): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_go_2, post_filter=False) - variables = lex.get_variables() - assert len(variables) == 86 - - -def test_3(file_go_3): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_go_3, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('3.go') +def test_3(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 2 -def test_4(file_go_4): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_go_4, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('4.go') +def test_4(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 2 -def test_5(file_go_5): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_go_5, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('5.go') +def test_5(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 1 -def test_6(file_go_6): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_go_6, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('6.go') +def test_6(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 4 -def test_7(file_go_7): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_go_7, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('7.go') +def test_7(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 1 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_html.py b/tests/core/tokenizers/lexer/variable_detection/test_html.py index 820e80a..105b9fd 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_html.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_html.py @@ -2,17 +2,10 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_html_1(): - path = 'tests/fixtures/1.html' - return File(path=path, relative_path=path) - - -def test_1(file_html_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_html_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.html') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 32 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_java.py b/tests/core/tokenizers/lexer/variable_detection/test_java.py index 4697350..c0be648 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_java.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_java.py @@ -2,17 +2,16 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_java_1(): - path = 'tests/fixtures/1.java' - return File(path=path, relative_path=path) - +@pytest.mark.fixture_file_path('1.java') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 2 -def test_1(file_java_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_java_1, post_filter=False) - variables = lex.get_variables() - assert len(variables) == 2 +@pytest.mark.fixture_file_path('2.java') +def test_2(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 1 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_js.py b/tests/core/tokenizers/lexer/variable_detection/test_js.py index 3add189..f39303d 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_js.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_js.py @@ -2,6 +2,7 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case @pytest.fixture(scope='module') @@ -9,67 +10,73 @@ def file_js_3(): path = 'tests/fixtures/3.js' return File(path=path, relative_path=path) + @pytest.fixture(scope='module') def file_jsx_1(): path = 'tests/fixtures/1.jsx' return File(path=path, relative_path=path) + @pytest.fixture(scope='module') def file_jsx_2(): path = 'tests/fixtures/2.jsx' return File(path=path, relative_path=path) + @pytest.fixture(scope='module') def file_jsx_3(): path = 'tests/fixtures/3.jsx' return File(path=path, relative_path=path) + @pytest.fixture(scope='module') def file_js_4(): path = 'tests/fixtures/4.js' return File(path=path, relative_path=path) +@pytest.fixture(scope='module') +def file_minjs_5_1(): + path = 'tests/fixtures/5_1.min.js' + return File(path=path, relative_path=path) -def test_1(file_js_3): - lex = LexerTokenizer(deep_token_inspection=True) - tokens = lex.tokenize(file_js_3, post_filter=True) - assert lex.lexer.name == 'JSX' - variables = lex.get_variables(tokens) +@pytest.mark.fixture_file_path('3.js') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, lexer, _ = variable_detection_case(lexer_tokenizer, file) + assert lexer.name == 'JSX' assert len(variables) == 2 -def test_2_jsx(file_jsx_1): - lex = LexerTokenizer(deep_token_inspection=True) - tokens = lex.tokenize(file_jsx_1, post_filter=True) - assert lex.lexer.name == 'JSX' - - variables = lex.get_variables(tokens) +@pytest.mark.fixture_file_path('1.jsx') +def test_2_jsx(file: File, lexer_tokenizer: LexerTokenizer): + variables, lexer, _ = variable_detection_case(lexer_tokenizer, file) + assert lexer.name == 'JSX' assert len(variables) == 1 -def test_3_jsx(file_jsx_2): - lex = LexerTokenizer(deep_token_inspection=True) - tokens = lex.tokenize(file_jsx_2, post_filter=True) - assert lex.lexer.name == 'JSX' - - variables = lex.get_variables(tokens) +@pytest.mark.fixture_file_path('2.jsx') +def test_3_jsx(file: File, lexer_tokenizer: LexerTokenizer): + variables, lexer, _ = variable_detection_case(lexer_tokenizer, file) + assert lexer.name == 'JSX' assert len(variables) == 0 -def test_4_jsx(file_jsx_3): - lex = LexerTokenizer(deep_token_inspection=True) - tokens = lex.tokenize(file_jsx_3, post_filter=True) - assert lex.lexer.name == 'JSX' - - variables = lex.get_variables(tokens) +@pytest.mark.fixture_file_path('3.jsx') +def test_4_jsx(file: File, lexer_tokenizer: LexerTokenizer): + variables, lexer, _ = variable_detection_case(lexer_tokenizer, file, post_filter=False) + assert lexer.name == 'JSX' assert len(variables) == 0 -def test_5_js(file_js_4): - lex = LexerTokenizer(deep_token_inspection=True) - tokens = lex.tokenize(file_js_4, post_filter=True) +@pytest.mark.fixture_file_path('4.js') +def test_5_js(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 0 + - variables = lex.get_variables(tokens) - assert len(variables) == 0 \ No newline at end of file +@pytest.mark.fixture_file_path('5_1.min.js') +def test_minjs_5_1(file, lexer_tokenizer): + tokens = lexer_tokenizer.tokenize(file, post_filter=True) + variables = lexer_tokenizer.get_variables(tokens) + assert len(variables) == 17 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py b/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py new file mode 100644 index 0000000..f14c2f3 --- /dev/null +++ b/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py @@ -0,0 +1,11 @@ +import pytest + +from deepsecrets.core.model.file import File +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case + + +@pytest.mark.fixture_file_path('1_markdown.txt') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 3 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_php.py b/tests/core/tokenizers/lexer/variable_detection/test_php.py index a1ad3ac..76fc366 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_php.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_php.py @@ -2,6 +2,7 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case @pytest.fixture(scope='module') @@ -10,9 +11,7 @@ def file_php_1(): return File(path=path, relative_path=path) -def test_1(file_php_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_php_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.php') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 12 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_py.py b/tests/core/tokenizers/lexer/variable_detection/test_py.py index 2b2df33..dc2c105 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_py.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_py.py @@ -2,56 +2,24 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_py_1(): - path = 'tests/fixtures/1.py' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_py_2(): - path = 'tests/fixtures/2.py' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_py_3(): - path = 'tests/fixtures/3.py' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_py_4(): - path = 'tests/fixtures/4.py' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_py_6(): - path = 'tests/fixtures/6.py' - return File(path=path, relative_path=path) - - -def test_1(file_py_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_py_1, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.py') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 5 -def test_2(file_py_2): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_py_2, post_filter=False) - variables = lex.get_variables() +@pytest.mark.fixture_file_path('2.py') +def test_2(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 93 -def test_3(file_py_3): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_py_3, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('3.py') +def test_3(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 3 assert variables[1].semantic.name == 'password' assert variables[1].content == 'TESTSECRET1234' @@ -60,17 +28,13 @@ def test_3(file_py_3): assert variables[2].content == '2TESTSECRET1234' -def test_4(file_py_4): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_py_4, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('4.py') +def test_4(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 11 -def test_5(file_py_6): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_py_6, post_filter=False) - - variables = lex.get_variables() - assert len(variables) == 1 +@pytest.mark.fixture_file_path('5.py') +def test_5(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 2 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_sh.py b/tests/core/tokenizers/lexer/variable_detection/test_sh.py index 425fc43..e4253c5 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_sh.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_sh.py @@ -2,17 +2,10 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_sh_1(): - path = 'tests/fixtures/1.sh' - return File(path=path, relative_path=path) - - -def test_1(file_sh_1): - lex = LexerTokenizer(deep_token_inspection=True) - lex.tokenize(file_sh_1, post_filter=False) - - variables = lex.get_variables() +@pytest.mark.fixture_file_path('1.sh') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 7 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_swift.py b/tests/core/tokenizers/lexer/variable_detection/test_swift.py index 1439a1e..bd731d3 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_swift.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_swift.py @@ -2,19 +2,10 @@ from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_swift_1(): - path = 'tests/fixtures/1.swift' - return File(path=path, relative_path=path) - - -def test_suppress(file_swift_1): - lex = LexerTokenizer(deep_token_inspection=True) - vars = lex.tokenize(file_swift_1, post_filter=True) - - assert len(vars) == 0 - - - +@pytest.mark.fixture_file_path('1.swift') +def test_suppress(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 0 diff --git a/tests/core/tokenizers/test_full_content.py b/tests/core/tokenizers/test_full_content.py index 78e2880..7448084 100644 --- a/tests/core/tokenizers/test_full_content.py +++ b/tests/core/tokenizers/test_full_content.py @@ -4,14 +4,8 @@ from deepsecrets.core.tokenizers.full_content import FullContentTokenizer -@pytest.fixture(scope='module') -def file_toml_1(): - path = 'tests/fixtures/1.toml' - return File(path=path, relative_path=path) - - -def test_full_content(file_toml_1: File): - tokenizer = FullContentTokenizer() - tokens = tokenizer.tokenize(file=file_toml_1) +@pytest.mark.fixture_file_path('1.toml') +def test_full_content(file: File, full_content_tokenizer: FullContentTokenizer): + tokens = full_content_tokenizer.tokenize(file=file) assert len(tokens) == 1 - assert tokens[0].content == file_toml_1.content + assert tokens[0].content == file.content diff --git a/tests/core/tokenizers/test_per_line.py b/tests/core/tokenizers/test_per_line.py index d902b7b..c2e8d71 100644 --- a/tests/core/tokenizers/test_per_line.py +++ b/tests/core/tokenizers/test_per_line.py @@ -4,13 +4,7 @@ from deepsecrets.core.tokenizers import PerLineTokenizer -@pytest.fixture(scope='module') -def file_toml_1(): - path = 'tests/fixtures/1.toml' - return File(path=path, relative_path=path) - - -def test_per_line(file_toml_1: File): - tokenizer = PerLineTokenizer() - tokens = tokenizer.tokenize(file=file_toml_1) +@pytest.mark.fixture_file_path('1.toml') +def test_per_line(file: File, per_line_tokenizer: PerLineTokenizer): + tokens = per_line_tokenizer.tokenize(file=file) assert len(tokens) == 76 diff --git a/tests/core/utils/test_file_analyzer.py b/tests/core/utils/test_file_analyzer.py index 2ece2e5..5a13f41 100644 --- a/tests/core/utils/test_file_analyzer.py +++ b/tests/core/utils/test_file_analyzer.py @@ -1,20 +1,14 @@ import pytest from deepsecrets.core.engines.semantic import SemanticEngine -from deepsecrets.core.model.file import File from deepsecrets.core.tokenizers.lexer import LexerTokenizer from deepsecrets.core.utils.file_analyzer import FileAnalyzer -@pytest.fixture(scope='module') -def file_toml_1(): - path = 'tests/fixtures/1.toml' - return File(path=path, relative_path=path) +@pytest.mark.fixture_file_path('1.toml') +def test_file_analyzer(file): + file_analyzer = FileAnalyzer(file) - -def test_file_analyzer(file_toml_1): - file_analyzer = FileAnalyzer(file_toml_1) - lex = LexerTokenizer(deep_token_inspection=True) semantic_engine = SemanticEngine(subengine=None) file_analyzer.add_engine(engine=semantic_engine, tokenizers=[lex]) diff --git a/tests/core/utils/test_lexer_finder.py b/tests/core/utils/test_lexer_finder.py index fda71f9..d7523fb 100644 --- a/tests/core/utils/test_lexer_finder.py +++ b/tests/core/utils/test_lexer_finder.py @@ -3,49 +3,25 @@ from deepsecrets.core.utils.lexer_finder import LexerFinder -@pytest.fixture(scope='module') -def file_extless_json(): - path = 'tests/fixtures/extless/json' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_extless_yaml(): - path = 'tests/fixtures/extless/yaml' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_extless_ini(): - path = 'tests/fixtures/extless/ini' - return File(path=path, relative_path=path) - -@pytest.fixture(scope='module') -def file_js_react(): - path = 'tests/fixtures/3.js' - return File(path=path, relative_path=path) - - - -def test_extless_json(file_extless_json): - lf = LexerFinder() - lexer = lf.find(file_extless_json) - +@pytest.mark.fixture_file_path('extless/json') +def test_extless_json(file: File): + lexer = LexerFinder().find(file) assert lexer.name == 'JSON' -def test_extless_ini(file_extless_ini): - lf = LexerFinder() - lexer = lf.find(file_extless_ini) +@pytest.mark.fixture_file_path('extless/ini') +def test_extless_ini(file: File): + lexer = LexerFinder().find(file) assert lexer.name == 'INI' -def test_extless_yaml(file_extless_yaml): - lf = LexerFinder() - lexer = lf.find(file_extless_yaml) - +@pytest.mark.fixture_file_path('extless/yaml') +def test_extless_yaml(file: File): + lexer = LexerFinder().find(file) assert lexer.name == 'YAML' -def test_js_react(file_js_react): - lf = LexerFinder() - lexer = lf.find(file_js_react) - assert lexer.name == 'JSX' \ No newline at end of file +@pytest.mark.fixture_file_path('3.js') +def test_js_react(file: File): + lexer = LexerFinder().find(file) + assert lexer.name == 'JSX' diff --git a/tests/core/utils/test_string.py b/tests/core/utils/test_string.py index bd53883..01c1a7a 100644 --- a/tests/core/utils/test_string.py +++ b/tests/core/utils/test_string.py @@ -1,6 +1,7 @@ from deepsecrets.core.utils.string import StringUtils + def test_camel_case_divide(): example = 'someCoolVariableName' result = StringUtils.camel_case_divide(example) - assert result == 'some cool variable name' \ No newline at end of file + assert result == 'some cool variable name' diff --git a/tests/fixtures/1.go b/tests/fixtures/1.go index b2bb118..3e45ba5 100644 --- a/tests/fixtures/1.go +++ b/tests/fixtures/1.go @@ -694,5 +694,7 @@ func head(p string) (head string) { if i <= 0 { return p[1:] } + + csrfToken := "2hefe768g0090hd4368h42880f970e61865f326172d4d2343e645fi5g7i20992" return p[1:i] } \ No newline at end of file diff --git a/tests/fixtures/1.pem b/tests/fixtures/1.pem new file mode 100644 index 0000000..990002e --- /dev/null +++ b/tests/fixtures/1.pem @@ -0,0 +1,10 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAjv1eQ5Zo7PZmVcNDFS6wWO9ITwCO2cWqKp+9YvehR5/mwNRNkqDq +p36uCM6TGMdeGR4XUc7awyD7QvANLztfYtKgBwYFK4EEACOhgYkDgYYABAEsKh+5 +UvpMa5rE19qCWRCm+jGDu8yu+NQzSsp2dnEr+MkA/jkXAgkACtyDybhfOb0DJ0vh +I7n87eV34Daqm9FwwwAC+A9eSFQIcw6yc2zxbwbSQOL8dyKDRLgbHurMZSsGAEE2 +hXDHY77nYne0T51pDIP520QlbRjBern+xwnfRkd+5g== +-----END EC PRIVATE KEY----- diff --git a/tests/fixtures/2.html b/tests/fixtures/2.html new file mode 100644 index 0000000..9b8fc56 --- /dev/null +++ b/tests/fixtures/2.html @@ -0,0 +1 @@ +'mongodb://user:psw@ds135522.somewhere.com:35522/db' \ No newline at end of file diff --git a/tests/fixtures/2.java b/tests/fixtures/2.java new file mode 100644 index 0000000..878480f --- /dev/null +++ b/tests/fixtures/2.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2022 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.security.oidcclientcore.token; + +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.ibm.ws.security.test.common.CommonTestClass; + +import test.common.SharedOutputManager; + +public class TokenRefresherTest extends CommonTestClass { + + private static SharedOutputManager outputMgr = SharedOutputManager.getInstance(); + + private static final String refreshToken = "TJFBsiclSCB2vdDdjevi5maePxftfI3743hxnqEacnXori7xVy"; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + outputMgr.captureStreams(); + } + + @After + public void tearDown() { + outputMgr.resetStreams(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + outputMgr.dumpStreams(); + outputMgr.restoreStreams(); + } + + @Test + public void test_isAccessTokenExpired() throws Exception { + TokenRefresher tokenRefresher = new TokenRefresher(null, null, true, false, refreshToken); + assertTrue(tokenRefresher.isAccessTokenExpired()); + assertTrue(tokenRefresher.isTokenExpired()); + } + + @Test + public void test_isIdTokenExpired() throws Exception { + TokenRefresher tokenRefresher = new TokenRefresher(null, null, true, true, refreshToken); + assertTrue(tokenRefresher.isIdTokenExpired()); + assertTrue(tokenRefresher.isTokenExpired()); + } +} diff --git a/tests/fixtures/3.conf b/tests/fixtures/3.conf new file mode 100644 index 0000000..5c7cf2a --- /dev/null +++ b/tests/fixtures/3.conf @@ -0,0 +1,123 @@ +include "application" + +spray.can.server { + request-timeout = 1s, + + # The time period within which a connection handler must have been + # registered after the bind handler has received a `Connected` event. + # Set to `infinite` to disable. + registration-timeout = 1s +} + +openolitor { + run-proxy-service: true, + mandanten: ["m1"], + m1: { + name: "mandant1", + + # Project specific akka persistence configuration + akka-persistence-sql-async { + url="jdbc:mysql://"${?VCAP_SERVICES.mariadb-object.0.credentials.host}":"${?VCAP_SERVICES.mariadb-object.0.credentials.port}"/"${?VCAP_SERVICES.mariadb-object.0.credentials.database} + user=${?VCAP_SERVICES.mariadb-object.0.credentials.username} + password=${?VCAP_SERVICES.mariadb-object.0.credentials.password} + } + + # Mandant specific db settings + db: { + default: { + url="jdbc:mysql://"${?VCAP_SERVICES.mariadb-object.0.credentials.host}":"${?VCAP_SERVICES.mariadb-object.0.credentials.port}"/"${?VCAP_SERVICES.mariadb-object.0.credentials.database} + user=${?VCAP_SERVICES.mariadb-object.0.credentials.username} + password=${?VCAP_SERVICES.mariadb-object.0.credentials.password} + } + } + + s3 { + aws-endpoint="http://example.com" + aws-access-key-id="5484335407854d4f9gf88h01206if148/FI_S8_30G01107_8388_4628_90ID_EGI06G4E2484" + aws-secret-acccess-key="hhn05VNjDOsTTj20DVuFcp1CI7r" + } + }, + + # DB Seed configuration + db.default.seed { + models = [ + ch.openolitor.core.models.PersonId, + ch.openolitor.stammdaten.models.ProjektId, + ch.openolitor.stammdaten.models.DepotId, + ch.openolitor.stammdaten.models.TourId, + ch.openolitor.stammdaten.models.KundeId, + ch.openolitor.stammdaten.models.AbotypId, + ch.openolitor.stammdaten.models.AboId, + ch.openolitor.stammdaten.models.ProduktId, + ch.openolitor.stammdaten.models.ProduzentId, + ch.openolitor.stammdaten.models.VertriebId, + ch.openolitor.buchhaltung.models.RechnungId] + + mappings { + ch.openolitor.core.models { + PersonId = 40000 + } + ch.openolitor.stammdaten.models { + ProjektId = 1000 + DepotId = 10000 + TourId = 20000 + KundeId = 30000 + AbotypId = 50000 + ProduktId = 60000 + ProduzentId = 70000 + AboId = 100000 + VertriebId = 110000 + } + ch.openolitor.buchhaltung.models { + RechnungId = 200000 + } + } + } + + # Security configuration + security { + second-factor-auth { + require = false + send-email = false + } + # max 20s delay + max-request-delay = 20000 + + cors { + allow-origin = ["http://wwwtest.openolitor.ch"] + } + } +} + +# Default akka configuration +akka { + loglevel = "DEBUG", + stdout-loglevel = "DEBUG", + loggers = ["akka.event.slf4j.Slf4jLogger"] + logging-filter = "akka.event.slf4j.Slf4jLoggingFilter", + persistence { + journal.plugin = "akka-persistence-sql-async.journal" + snapshot-store.plugin = "akka-persistence-sql-async.snapshot-store" + }, + actor { + serializers { + event-serializer = "ch.openolitor.core.eventsourcing.EventStoreSerializer" + }, + serialization-bindings { + "ch.openolitor.core.domain.PersistentEvent" = event-serializer + } + } +} + +# Default akka-persistence configuration +akka-persistence-sql-async { + journal.class = "akka.persistence.journal.sqlasync.MySQLAsyncWriteJournal" + snapshot-store.class = "akka.persistence.snapshot.sqlasync.MySQLSnapshotStore" + + max-pool-size = 4 + wait-queue-capacity = 10000 + + metadata-table-name = "persistence_metadata" + journal-table-name = "persistence_journal" + snapshot-table-name = "persistence_snapshot" +} diff --git a/tests/fixtures/3.html b/tests/fixtures/3.html new file mode 100644 index 0000000..f079477 --- /dev/null +++ b/tests/fixtures/3.html @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/tests/fixtures/8.go b/tests/fixtures/8.go new file mode 100644 index 0000000..85549e5 --- /dev/null +++ b/tests/fixtures/8.go @@ -0,0 +1,552 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hetzner + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +// SDMock is the interface for the Hetzner Cloud mock +type SDMock struct { + t *testing.T + Server *httptest.Server + Mux *http.ServeMux +} + +// NewSDMock returns a new SDMock. +func NewSDMock(t *testing.T) *SDMock { + return &SDMock{ + t: t, + } +} + +// Endpoint returns the URI to the mock server +func (m *SDMock) Endpoint() string { + return m.Server.URL + "/" +} + +// Setup creates the mock server +func (m *SDMock) Setup() { + m.Mux = http.NewServeMux() + m.Server = httptest.NewServer(m.Mux) + m.t.Cleanup(m.Server.Close) +} + +// ShutdownServer creates the mock server +func (m *SDMock) ShutdownServer() { + m.Server.Close() +} + +const hcloudTestToken = "OUN9GDZT1CDHIVuFQHHcOFXzkBA1X3j7zPj4gWonnGF96ibGxbM39qYeYmFNVGim" + +// HandleHcloudServers mocks the cloud servers list endpoint. +func (m *SDMock) HandleHcloudServers() { + m.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", hcloudTestToken) { + w.WriteHeader(http.StatusUnauthorized) + return + } + + w.Header().Add("content-type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "servers": [ + { + "id": 42, + "name": "my-server", + "status": "running", + "created": "2016-01-30T23:50:00+00:00", + "public_net": { + "ipv4": { + "ip": "1.2.3.4", + "blocked": false, + "dns_ptr": "server01.example.com" + }, + "ipv6": { + "ip": "2001:db8::/64", + "blocked": false, + "dns_ptr": [ + { + "ip": "2001:db8::1", + "dns_ptr": "server.example.com" + } + ] + }, + "floating_ips": [ + 478 + ] + }, + "private_net": [ + { + "network": 4711, + "ip": "10.0.0.2", + "alias_ips": [], + "mac_address": "86:00:ff:2a:7d:e1" + } + ], + "server_type": { + "id": 1, + "name": "cx11", + "description": "CX11", + "cores": 1, + "memory": 1, + "disk": 25, + "deprecated": false, + "prices": [ + { + "location": "fsn1", + "price_hourly": { + "net": "1.0000000000", + "gross": "1.1900000000000000" + }, + "price_monthly": { + "net": "1.0000000000", + "gross": "1.1900000000000000" + } + } + ], + "storage_type": "local", + "cpu_type": "shared" + }, + "datacenter": { + "id": 1, + "name": "fsn1-dc8", + "description": "Falkenstein 1 DC 8", + "location": { + "id": 1, + "name": "fsn1", + "description": "Falkenstein DC Park 1", + "country": "DE", + "city": "Falkenstein", + "latitude": 50.47612, + "longitude": 12.370071, + "network_zone": "eu-central" + }, + "server_types": { + "supported": [ + 1, + 2, + 3 + ], + "available": [ + 1, + 2, + 3 + ], + "available_for_migration": [ + 1, + 2, + 3 + ] + } + }, + "image": { + "id": 4711, + "type": "system", + "status": "available", + "name": "ubuntu-20.04", + "description": "Ubuntu 20.04 Standard 64 bit", + "image_size": 2.3, + "disk_size": 10, + "created": "2016-01-30T23:50:00+00:00", + "created_from": { + "id": 1, + "name": "Server" + }, + "bound_to": null, + "os_flavor": "ubuntu", + "os_version": "20.04", + "rapid_deploy": false, + "protection": { + "delete": false + }, + "deprecated": "2018-02-28T00:00:00+00:00", + "labels": {} + }, + "iso": null, + "rescue_enabled": false, + "locked": false, + "backup_window": "22-02", + "outgoing_traffic": 123456, + "ingoing_traffic": 123456, + "included_traffic": 654321, + "protection": { + "delete": false, + "rebuild": false + }, + "labels": { + "my-key": "my-value" + }, + "volumes": [], + "load_balancers": [] + }, + { + "id": 44, + "name": "another-server", + "status": "stopped", + "created": "2016-01-30T23:50:00+00:00", + "public_net": { + "ipv4": { + "ip": "1.2.3.5", + "blocked": false, + "dns_ptr": "server01.example.org" + }, + "ipv6": { + "ip": "2001:db9::/64", + "blocked": false, + "dns_ptr": [ + { + "ip": "2001:db9::1", + "dns_ptr": "server01.example.org" + } + ] + }, + "floating_ips": [] + }, + "private_net": [], + "server_type": { + "id": 2, + "name": "cpx11", + "description": "CPX11", + "cores": 2, + "memory": 1, + "disk": 50, + "deprecated": false, + "prices": [ + { + "location": "fsn1", + "price_hourly": { + "net": "1.0000000000", + "gross": "1.1900000000000000" + }, + "price_monthly": { + "net": "1.0000000000", + "gross": "1.1900000000000000" + } + } + ], + "storage_type": "local", + "cpu_type": "shared" + }, + "datacenter": { + "id": 2, + "name": "fsn1-dc14", + "description": "Falkenstein 1 DC 14", + "location": { + "id": 1, + "name": "fsn1", + "description": "Falkenstein DC Park 1", + "country": "DE", + "city": "Falkenstein", + "latitude": 50.47612, + "longitude": 12.370071, + "network_zone": "eu-central" + }, + "server_types": { + "supported": [ + 1, + 2, + 3 + ], + "available": [ + 1, + 2, + 3 + ], + "available_for_migration": [ + 1, + 2, + 3 + ] + } + }, + "image": { + "id": 4711, + "type": "system", + "status": "available", + "name": "ubuntu-20.04", + "description": "Ubuntu 20.04 Standard 64 bit", + "image_size": 2.3, + "disk_size": 10, + "created": "2016-01-30T23:50:00+00:00", + "created_from": { + "id": 1, + "name": "Server" + }, + "bound_to": null, + "os_flavor": "ubuntu", + "os_version": "20.04", + "rapid_deploy": false, + "protection": { + "delete": false + }, + "deprecated": "2018-02-28T00:00:00+00:00", + "labels": {} + }, + "iso": null, + "rescue_enabled": false, + "locked": false, + "backup_window": "22-02", + "outgoing_traffic": 123456, + "ingoing_traffic": 123456, + "included_traffic": 654321, + "protection": { + "delete": false, + "rebuild": false + }, + "labels": {}, + "volumes": [], + "load_balancers": [] + }, + { + "id": 36, + "name": "deleted-image-server", + "status": "stopped", + "created": "2016-01-30T23:50:00+00:00", + "public_net": { + "ipv4": { + "ip": "1.2.3.6", + "blocked": false, + "dns_ptr": "server01.example.org" + }, + "ipv6": { + "ip": "2001:db7::/64", + "blocked": false, + "dns_ptr": [ + { + "ip": "2001:db7::1", + "dns_ptr": "server01.example.org" + } + ] + }, + "floating_ips": [] + }, + "private_net": [], + "server_type": { + "id": 2, + "name": "cpx11", + "description": "CPX11", + "cores": 2, + "memory": 1, + "disk": 50, + "deprecated": false, + "prices": [ + { + "location": "fsn1", + "price_hourly": { + "net": "1.0000000000", + "gross": "1.1900000000000000" + }, + "price_monthly": { + "net": "1.0000000000", + "gross": "1.1900000000000000" + } + } + ], + "storage_type": "local", + "cpu_type": "shared" + }, + "datacenter": { + "id": 2, + "name": "fsn1-dc14", + "description": "Falkenstein 1 DC 14", + "location": { + "id": 1, + "name": "fsn1", + "description": "Falkenstein DC Park 1", + "country": "DE", + "city": "Falkenstein", + "latitude": 50.47612, + "longitude": 12.370071, + "network_zone": "eu-central" + }, + "server_types": { + "supported": [ + 1, + 2, + 3 + ], + "available": [ + 1, + 2, + 3 + ], + "available_for_migration": [ + 1, + 2, + 3 + ] + } + }, + "image": null, + "iso": null, + "rescue_enabled": false, + "locked": false, + "backup_window": "22-02", + "outgoing_traffic": 123456, + "ingoing_traffic": 123456, + "included_traffic": 654321, + "protection": { + "delete": false, + "rebuild": false + }, + "labels": {}, + "volumes": [], + "load_balancers": [] + } + ], + "meta": { + "pagination": { + "page": 1, + "per_page": 25, + "previous_page": null, + "next_page": null, + "last_page": 1, + "total_entries": 2 + } + } +}`, + ) + }) +} + +// HandleHcloudNetworks mocks the cloud networks list endpoint. +func (m *SDMock) HandleHcloudNetworks() { + m.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", hcloudTestToken) { + w.WriteHeader(http.StatusUnauthorized) + return + } + + w.Header().Add("content-type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "networks": [ + { + "id": 4711, + "name": "mynet", + "ip_range": "10.0.0.0/16", + "subnets": [ + { + "type": "cloud", + "ip_range": "10.0.1.0/24", + "network_zone": "eu-central", + "gateway": "10.0.0.1" + } + ], + "routes": [ + { + "destination": "10.100.1.0/24", + "gateway": "10.0.1.1" + } + ], + "servers": [ + 42 + ], + "load_balancers": [ + 42 + ], + "protection": { + "delete": false + }, + "labels": {}, + "created": "2016-01-30T23:50:00+00:00" + } + ], + "meta": { + "pagination": { + "page": 1, + "per_page": 25, + "previous_page": null, + "next_page": null, + "last_page": 1, + "total_entries": 1 + } + } +}`, + ) + }) +} + +const robotTestUsername = "my-hetzner" +const robotTestPassword = "my-password" + +// HandleRobotServers mocks the robot servers list endpoint. +func (m *SDMock) HandleRobotServers() { + m.Mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if username != robotTestUsername && password != robotTestPassword && !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + + w.Header().Add("content-type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +[ + { + "server":{ + "server_ip":"123.123.123.123", + "server_number":321, + "server_name":"server1", + "product":"DS 3000", + "dc":"NBG1-DC1", + "traffic":"5 TB", + "flatrate":true, + "status":"ready", + "throttled":true, + "cancelled":false, + "paid_until":"2010-09-02", + "ip":[ + "123.123.123.123" + ], + "subnet":[ + { + "ip":"2a01:4f8:111:4221::", + "mask":"64" + } + ] + } + }, + { + "server":{ + "server_ip":"123.123.123.124", + "server_number":421, + "server_name":"server2", + "product":"X5", + "dc":"FSN1-DC10", + "traffic":"2 TB", + "flatrate":true, + "status":"in process", + "throttled":false, + "cancelled":true, + "paid_until":"2010-06-11", + "ip":[ + "123.123.123.124" + ], + "subnet":null + } + } +]`, + ) + }) +} diff --git a/tests/fixtures/cases/bash_in_markdown_1.md b/tests/fixtures/cases/bash_in_markdown_1.md new file mode 100644 index 0000000..0f74bf8 --- /dev/null +++ b/tests/fixtures/cases/bash_in_markdown_1.md @@ -0,0 +1,308 @@ +--- +title: Using mtls-auth plugin +--- + +This guide walks through how to configure the {{site.kic_product_name}} to +verify client certificates using CA certificates and +[mtls-auth](https://docs.konghq.com/hub/kong-inc/mtls-auth/) plugin +for HTTPS requests. + +> Note: You need an Enterprise license to use this feature. + +## Installation + +Please follow the [deployment](/kubernetes-ingress-controller/{{page.kong_version}}/deployment/overview) documentation to install +Kong for Kubernetes Enterprise on your Kubernetes cluster. + +## Testing Connectivity to Kong + +This guide assumes that the `PROXY_IP` environment variable is +set to contain the IP address or URL pointing to Kong. +Please follow one of the +[deployment guides](/kubernetes-ingress-controller/{{page.kong_version}}/deployment/k4k8s-enterprise) to configure +this environment variable. + +If everything is set up correctly, making a request to Kong should return +HTTP 404 Not Found. + +```bash +$ curl -i $PROXY_IP +HTTP/1.1 404 Not Found +Date: Fri, 21 Jun 2019 17:01:07 GMT +Content-Type: application/json; charset=utf-8 +Connection: keep-alive +Content-Length: 48 +Server: kong/1.2.1 + +{"message":"no Route matched with those values"} +``` + +This is expected as Kong does not yet know how to proxy the request. + +## Provision a CA certificate in Kong + +CA certificates in Kong are provisioned by create a `Secret` resource in +Kubernetes. + +The secret resource must have a few properties: +- It must have the `konghq.com/ca-cert: "true"` label. +- It must have a `cert` data property which contains a valid CA certificate + in PEM format. +- It must have an `id` data property which contains a random UUID. +- It must have a `kubernetes.io/ingress.class` annotation whose value matches + the value of the controller's `--ingress-class` argument. By default, that + value is "kong". + +Note that a self-signed CA certificate is being used for the purpose of this +guide. You should use your own CA certificate that is backed by +your PKI infrastructure. + +```bash +$ echo "apiVersion: v1 +kind: Secret +metadata: + name: my-ca-cert + annotations: + kubernetes.io/ingress.class: kong + labels: + konghq.com/ca-cert: 'true' +type: Opaque +stringData: + cert: | + -----BEGIN CERTIFICATE----- + MIICwTCCAamgAwIBAgIUHGUzUWvHJHrREvIZIcORiFUvze4wDQYJKoZIhvcNAQEL + BQAwEDEOMAwGA1UEAwwFSGVsbG8wHhcNMjAwNTA4MjExODA1WhcNMjAwNjA3MjEx + ODA1WjAQMQ4wDAYDVQQDDAVIZWxsbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC + AQoCggEBANCMMBngjuTvqts8ZXtZhqdr181QH/NmytW1KlyqZd6ppXUer+i0OWhP + 1nAyHsBPJljKAFLd8l1EioPFkN78/wJFDJrHOtfniIQPVLdS2cnNQ72dLyQH6smH + JQDV8ePBQ2GdRP6s61+Da8eoaW6nSLtmEUhxvyteboqwmi2CtUtAfuiU1m5sOdpS + z+L4D08CE+SFIT4MGD3gxNdg7lccWCHIfk54VRSdGDKEVwed8OQvxD0TdpHY+ym5 + nJ4JSkhiS9XIodnxR3AZ6rIPRqk+MQ4LGTjX2EbM0/Yg4qvnZ7m4fcpK2goDZIVL + EF8F+ka1RaAYWTsXI1BAkJbb3kdo/yUCAwEAAaMTMBEwDwYDVR0TBAgwBgEB/wIB + ADANBgkqhkiG9w0BAQsFAAOCAQEAVvB/PeVZpeQ7q2IQQQpADtTd8+22Ma3jNZQD + EkWGZEQLkRws4EJNCCIvkApzpx1GqRcLLL9lbV+iCSiIdlR5W9HtK07VZ318gpsG + aTMNrP9/2XWTBzdHWaeZKmRKB04H4z7V2Dl58D+wxjdqNWsMIHeqqPNKGamk/q8k + YFNqNwisRxMhU6qPOpOj5Swl2jLTuVMAeGWBWmPGU2MUoaJb8sc2Vix9KXcyDZIr + eidkzkqSrjNzI0yJ2gdCDRS4/Rw9iV3B3SRMs0mJMLBDrsowhNfLAd8I3NHzLwps + dZFcvZcT/p717K3hlFVdjGnKIgKcG7aYji/XRR87HKnc+cJMCw== + -----END CERTIFICATE----- + id: cce8c384-721f-4f58-85dd-50834e3e733a" | kubectl create -f - +secret/my-ca-cert created +``` + +Please note the ID, you can use this ID one or use a different one but +the ID is important in the next step when we create the plugin. +Each CA certificate that you create needs a unique ID. +Any random UUID will suffice here and it doesn't have an security +implication. + +You can use [uuidgen](https://linux.die.net/man/1/uuidgen) (Linux, OS X) or +[New-Guid](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/new-guid) +(Windows) to generate an ID. + +For example: +```bash +$ uuidgen +907821fc-cd09-4186-afb5-0b06530f2524 +``` + +## Configure mtls-auth plugin + +Next, we are going to create an `mtls-auth` KongPlugin resource which references +CA certificate provisioned in the last step: + +```bash +$ echo " +apiVersion: configuration.konghq.com/v1 +kind: KongPlugin +metadata: + name: mtls-auth +config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: true + revocation_check_mode: SKIP +plugin: mtls-auth +" | kubectl apply -f - +kongplugin.configuration.konghq.com/mtls-auth created +``` + +## Install a dummy service + +Let's deploy an echo service which we wish to protect +using TLS client certificate authentication. + +```bash +$ kubectl apply -f https://bit.ly/echo-service +service/echo created +deployment.apps/echo created +``` + +You can deploy a different service or skip this step if you already +have a service deployed in Kubernetes. + +## Set up Ingress + +Let's expose the echo service outside the Kubernetes cluster +by defining an Ingress. + +```bash +$ echo " +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: demo + annotations: + konghq.com/plugins: mtls-auth + kubernetes.io/ingress.class: kong +spec: + rules: + - http: + paths: + - path: /foo + backend: + serviceName: echo + servicePort: 80 +" | kubectl apply -f - +ingress.extensions/demo created +``` + +## Test the endpoint + +Now, let's test to see if Kong is asking for client certificate +or not when we make the request: + +``` +$ curl -k https://$PROXY_IP +HTTP/2 401 +date: Mon, 11 May 2020 18:15:05 GMT +content-type: application/json; charset=utf-8 +content-length: 50 +x-kong-response-latency: 0 +server: kong/2.0.4.0-enterprise-k8s + +{"message":"No required TLS certificate was sent"} +``` + +As we can see, Kong is restricting the request because it doesn't +have the necessary authentication information. + +Two things to note here: +- `-k` is used because Kong is set up to serve a self-signed certificate + by default. For full mutual authentication in production use cases, + you must configure Kong to serve a certificate that is signed by a trusted CA. +- For some deployments `$PROXY_IP` might contain a port that points to + `http` port of Kong. In others, it might happen that it contains a DNS name + instead of an IP address. If needed, please update the + command to send an `https` request to the `https` port of Kong or + the load balancer in front of it. + + +## Provisioning credential + +Next, in order to authenticate against Kong, create the client +certificate and private key with the following content: + +```bash +$ cat client.crt +-----BEGIN CERTIFICATE----- +MIIEFTCCAv0CAWUwDQYJKoZIhvcNAQELBQAwEDEOMAwGA1UEAwwFSGVsbG8wHhcN +MjAwNTA4MjE0OTE1WhcNMjEwNTA4MjE0OTE1WjCBkDELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAcMBHNvbWUxETAPBgNVBAoMCHNvbWUg +b3JnMRAwDgYDVQQLDAdvcmd1bml0MRswGQYDVQQDDBJleGFtcGxlLmtvbmdocS5j +b20xGzAZBgkqhkiG9w0BCQEWDGZvb0Bzb21lLmNvbTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAM/y80ppzwGYS7zl+A6fx4Xkjwja+ZUK/AoBDazS3TkR +W1tDFZ71koLd60qK2W1d9Wh0/F3iNTcobVefr02mEcLtl+d4zUug+W7RsK/8JSCM +MIDVDYzlTWdd7RJzV1c/0NFZyTRkEVSjGn6eQoC/1aviftiNyfqWtuIDQ5ctSBt8 +2fyvDwu/tBR5VyKu7CLnjZ/ffjNT8WDfbO704XeBBId0+L8i8J7ddYlRhZufdjEw +hKx2Su8PZ9RnJYShTBOpD0xdveh16eb7dpCZiPnp1/MOCyIyo1Iwu570VoMde9SW +sPFLdUMiCXw+A4Gp/e9Am+D/98PiL4JChKsiowbzpDfMrVQH4Sblpcgn/Pp+u1be +2Kl/7wqr3TA+w/unLnBnB859v3wDhSW4hhKASoFwyX3VfJ43AkmWFUBX/bpDvHto +rFw+MvbSLsS3QD5KlZmega1pNZtin5KV8H/oJI/CjEc9HHwd27alW9VkUu0WrH0j +c98wLHB/9xXLjunabxSmd+wv25SgYNqpsRNOLgcJraJbaRh4XkbDyuvjF2bRJVP4 +pIjntxQHS/oDFFFK3wc7fp/rTAl0PJ7tytYj4urg45N3ts7unwnB8WmKzD9Avcwe +8Kst12cEibS8X2sg8wOqgB0yarC17mBEqONK7Fw4VH+VzZYw0KGF5DWjeSXj/XsD +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEvTMHe27npmyJUBxQeHcNFniMJUWZf0 +i9EGd+XlF+m/l3rh1/mCecV7s32QTZEiFHv4UJPYASbgtx7+mEZuq7dVsxIUICWs +gyRkwvKjMqK2tR5IRkquhK5PuDS0QC3M/ZsDwnTgaezFrplFYf80z1kAAkm/c7eh +ZEjI6+1vuaS+HX1w2unk42PiAEB6oKFi3b8xl4TC6acYfMYiC3cOa/d3ZKHhqXhT +wM0VtDe0Qn1kExe+19XJG5cROelxmMXBm1+/c2KUw1yK8up6kJlEsmd8JLw/wMUp +xcJUKIH1qGBlRlFTYbVell+dB7IkHhadrnw27Z47uHobB/lzN69r63c= +-----END CERTIFICATE----- +``` + +```bash +$ cat client.pem +-----BEGIN RSA PRIVATE KEY----- +PLLMNTLEDDNFDjHDc/OcVpqSDCkOyRA4Gs/KkhVSFQu5oTu8FjHQuQOgRUIeZ0PY +qyZVjw3uVruCeY31dKW8AhL1QbkwY5+yWdBUzx2A53mQV6G5ewJzu/zoLLzzjQXQ +mRYQC13wHqQAYc/T0YqMQJTUYNPdis5FjO/Yt+M+2L3M+sd24jQGob1LJ3cC/N8S +F7+0IKoALt7vLxhQq99+P1SaBQ9v7yWkg4HHk3W4ybOzqw11lYJIp592PWFHuKCN +7z9q1JfokNIPH6nSWI296KAs5yw2nMpL+hqA8z4OLmNmXmF7qyUZja171Mdz8Xw1 +TbLMiG4Gjdq970Fe4S/3z+LyjnNHtbNmEyRnQ8bwYDikMxZobFi8+q67Yw7BtA/y +FtygPG7G+6fxfJfKcq2/iDRIMelJHrENjAGMigY8qmfFVCBYTIi9xnR8h2lvAG4b +9wLxaOgDSntYpC6EuZn1p2NinsAzi+jnm8NPUc0fiE3ewtYe1ZUV7UdviVQc3cDv +fK/3IfxR6gsyINC37F/eoNEj2tpaH04xEzpwrowsJKkhUvSN6+PACwHoX/lnlRh3 +IDgO+jPXXXuiEcw+q+wPFAT8qx3N1lSl6xGmn3h2cx6iFfKadBuPS0F9cE7ztb3A +CzVMwOaidbGcD6tDKWMtvOAxBHVr40uvAGkXi5AQomGTrBAnQdQ5MhS9hzPFDzHD +DTNFDjDw5fF/KxY6z6RO2SMPTDArcr6qgOY7tTBFN0Qdewz3JYdktTiiryLrjoWM +llA9Ytbz1roUN3o1dF3lImR6Kusb3PDAeiodESlr9j1dhqxcZI3rTC4UFEgbkl+t +W9ctSDNdDrj/XTupQJ3KqtVwFFsjJvmJjY0jRa24hxKcSbMBQwIlWW0c6dfXnfhl +wavYkVjnOn8Ojb6ZsEqJHGVqmPo0LJT6z7s6UjXLSy8SAc3ZH5EoEJO7twqR7voD +Lg3MaUqHXGk3X3gE7VV5R7rB9y0e/3FGsvxAh3zg1J432H0Cpi0M9T63w46FCtpg +g+l9ByUH0EshpQGIqpuu3xT+a43tDUwrqHHOluB99dZ0kXXiG7SlhOQqCS7wxhYE +M80JXX5fnMkq9x6VoNCwyEX2pDZydNCHy1+9yGk4Oh8iQwxesF5BwVNcwf66fvO6 +GOwbl81liwsI2BwGYNN8XE35NbJ/0LZnAbitxRnBxO8UzuMU9wQO1+Cv4JtjF5vK +iYLzU6/+z/nshK9qS8/0YdAUrtFmNTsYmIj9i645uCT/RcfqTQy6n8Vu+4cKdKrj +xIzRr7s4TiLSLEiX8+8UG36F5X/s5SlrxU8vQ+uiGFx0Q07ANpKDskotymWU+RJ/ +M5r3mJjDhuPCq3jplJXV+LgpuSz7zh8jf8m8H8F6WmyoDOTQRTNFDTHD6bVySbPz +kltid9WhBfh1jL2KwUblFP1u7rwIpWtV/L53kh7e9ODC5+jtAaPV/SZ9TWyNHW2t +yUX+aCBG4k/l9U+tCW3v7HvQEAETKnyk0p0tQUwuvVjDBFZOvL/0qXRNLc6reKx5 +5VaV8b3b1w9VrAyZscWsDqxn91EYPwVhski/4/kAoK2g1ZqRF0VtV979dUup8QH/ +ugW5tfkkbVbiCnBeDGab5DKKtrIWwnaJqOyfeB0A/rML3cQBFINWIQpe6/61faxE +UHmzUHXIRkqhABe9pEJ4eaxwhFc65PbvklQ1HDvYPqL6dHxKU6HDyw1Mvoy7Tl1d +2XNP61AfO8p/oTNFDTHD4pWJdrCM1bc+WFNPxdh33B9dvvARBDTsge3PB2XWjcTj +MDCBpzdDvEdF1h49J0hBYDS+hGL4x0RU0i0FZ9Si+RgJUZxCiYxp0g+SpfLkMijP +mAvU4FMsSfA7YCOKPC77TIGk/aEKQAU8I1odwSAIBU3bwfAao4KHwrgGsV84DjlR +57bSlwB78PV16o3JMJZojGgUS/OyYlaxjK2vwhKFwn8o932/tdbXhhceBVHkbT6O +13i0tUdEkyUvrXOm3KyTZQSapJBN3s+Q+cAf1FHuI6a8vGt4mhAbQj+26jCnqhd8 +3VHNNa+Zi4yW3uoXHhBb0xIxeJ06tBFgwm2CxVRNQzNFDTHDjMsTtnUUus8cG6Rg +hKenLrqIYg1bIXUfNoyOYgI+TIlhvDdFG+LfZTUY4Fhuf+SpiS35QwN2UeJS4ms4 +scayTXeyU23I3Wqbal2188ypowNWliBXTUFbp+PP8lWCXTY2XJ5gdR+JOSx/5mBX +LXdHk8PZH97UOXY4COCy0ozP5NTwoK3qXITigZ/qh6zcT0pV6RDLyI6H6HtCyVcY +soTfAeDu5nBsT+EkYmUmI0qFRPkC9bU6ribCCIIQeXiXK0zjkfNMgLqyhhz2X/D3 +xs4CVthjqLKfnD/jLRGj2b/Em59pc75y+pBX4dRoRkbsOurVN1X5MxowWARmCOCU +wZXxyTNFDTDYfwl9kRZDEor9ZoVfcnDHQN2bKG12NX7LThjBWs4yrzRfklIn5sST +pzIjUXebb7Fs3TvE1mj7ydBZG/QpTfhMeIimDgRc5ejGXGyssISEslRFW/RfpBBD +/W3ApNYBoVkZtsPRxGvZ3JgCVyWpFkehLCn6HAyAG8wXT7Mu9yMJgzvd92ohGSi2 +0szwmU7Yph+5JzVRp3VGCLj/nllKywGXwxGz9t/x4oSdcX7qi90XnIX9A7fITjZC +kMV6Kq06FYcx3A2CL6qM97Kd5/s4+q97teOVh226x9wewggwlshGzmZLheAg6jv3 +LHf9Cd+NYsAjIv2DCnWYkHOv3k8yUFh3DrLEDTGUu0n5RhSFvGev6UdgJL9Wd+si +L30x8lpNz8Ulk++127XSmsf8RFcdTQyZqsgDrMWjr12iTMtJljUXiMPIITq7x3mc +jjDt9ZOUvAUCsHAn8QAGu/ZknvRrZpnaOi4xQR7o2DbwLItCee1spWg0j+qs2bEH +8YjGU45LaeJSTOvWcNAhAMxARl7xw2hkM+YjvV84EvUWhR4y+B2tsJfbz6iAwX3H +QGuZh/F5TfhLOwGfg+MlAXjNuKUN+tuidzraSEGYkBM+Q/B7VtyC2JyaleqUv8BD +fekHheniXKUTVHtnSu+qgUKLqzZWPDZI4LkVxTRsWyW7SB7XQhw2lr8Z8Sb6 +-----END RSA PRIVATE KEY----- +``` + +Now, use the key and certificate to authenticate against Kong and use the +service: + +```bash +$ curl --key client.key --cert client.crt https://$PROXY_IP/foo -k -I +HTTP/2 200 +content-type: text/plain; charset=UTF-8 +date: Mon, 11 May 2020 18:27:22 GMT +server: echoserver +x-kong-upstream-latency: 1 +x-kong-proxy-latency: 1 +via: kong/2.0.4.0-enterprise-k8s +``` + +## Conclusion + +This guide demonstrates how to implement client TLS authentication +using Kong. +You are free to use other features that mtls-auth plugin in Kong to +achieve more complicated use-cases. diff --git a/tests/fixtures/cases/code_in_markdown.md b/tests/fixtures/cases/code_in_markdown.md new file mode 100644 index 0000000..5d87849 --- /dev/null +++ b/tests/fixtures/cases/code_in_markdown.md @@ -0,0 +1,292 @@ +# Spring Cloud Azure Config Conversion Sample client library for Java + +This sample shows how to convert a Spring Cloud Application with Cosmos DB to be using App Configuration + Key Vault + +## Key concepts +## Getting started +### Prerequisite + +* An Azure subscription; if you don't already have an Azure subscription, you can activate your [MSDN subscriber benefits](https://azure.microsoft.com/pricing/member-offers/msdn-benefits-details/) or sign up for a [free Azure account](https://azure.microsoft.com/free/). + +* A [Java Development Kit (JDK)](https://docs.microsoft.com/java/azure/jdk/?view=azure-java-stable), version 8. + +* [Apache Maven](http://maven.apache.org/), version 3.0 or later. + +### Quick Start + +#### Create an Azure Cosmos DB on Azure + +1. Use the Azure CLI [az cosmosdb create](https://docs.microsoft.com/cli/azure/cosmosdb?view=azure-cli-latest#az-cosmosdb-create). + + ```azurecli + az cosmosdb create --name my-cosmos-db --resource-group MyResourceGroup + ``` + + This operation will return json, among them is a documentEndpoint, record this. + + ```azurecli + { + ... + "documentEndpoint": "https://my-cosmos.documents.azure.com:443/", + ... + } + ``` + +1. Then use the [az cosmosdb keys list](https://docs.microsoft.com/cli/azure/cosmosdb/keys?view=azure-cli-latest#az-cosmosdb-keys-list). + + ```azurecli + az cosmosdb keys list --name my-cosmos-db -g MyResourceGroup + ``` + + Record the primaryMasterKey. + + ```azurecli + { + "primaryMasterKey": "...", + "primaryReadonlyMasterKey": "...", + "secondaryMasterKey": "...", + "secondaryReadonlyMasterKey": "..." + } + ``` + +#### Clone the sample Project + +In this section, you clone a containerized Spring Boot application and test it locally. + +1. Open a command prompt or terminal window and create a local directory to hold your Spring Boot application, and change to that directory; for example: + + ```shell + md C:\SpringBoot + cd C:\SpringBoot + ``` + + -- or -- + + ```shell + md /users/robert/SpringBoot + cd /users/robert/SpringBoot + ``` + +1. Clone the [Spring Boot on Docker Getting Started] sample project into the directory you created; for example: + + ```shell + git clone https://github.com/microsoft/spring-cloud-azure.git + ``` + +1. Change directory to the initial project; for example: + + ```shell + cd sdk/spring/azure-spring-boot-samples/azure-appconfiguration-conversion-sample-initial + ``` + +#### Config the sample + +1. Navigate to `src/main/resources` and open `application.properties`. + +1. Replace below properties in `application.properties` with information from your database. + + ```properties + azure.cosmosdb.uri=your-cosmosdb-uri + azure.cosmosdb.key=your-cosmosdb-key + azure.cosmosdb.database=your-cosmosdb-databasename + + ``` + +#### Run the sample + +1. Build the JAR file using Maven; for example: + + ```shell + mvn clean package + ``` + +1. When the web app has been created, start the web app using Maven; for example: + + ```shell + mvn spring-boot:run + ``` + +1. View the results in the console. + +1. You should see the following message displayed: **findOne in User collection get result: testFirstName** + +#### Convert to Using App Configuration + +1. Use the Azure CLI [az keyvault create](https://docs.microsoft.com/cli/azure/keyvault?view=azure-cli-latest#az-keyvault-create) + + ```azurecli + az keyvault create --name myVaultName -g MyResourceGroup + ``` + +1. Use the Azure CLI [az ad sp](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac) + + ```azurecli + az ad sp create-for-rbac -n "http://mySP" --sdk-auth + ``` + + This operation returns a series of key/value pairs: + + ```console + { + "clientId": "7da18cae-779c-41fc-992e-0527854c6583", + "clientSecret": "e421e443-1669-4fg7-e5e1-394g5f945002", + "subscriptionId": "443e30da-feca-47c4-b68f-1636b75e16b3", + "tenantId": "35ad10f1-7799-4766-9acf-f2d946161b77", + "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", + "resourceManagerEndpointUrl": "https://management.azure.com/", + "activeDirectoryGraphResourceId": "https://graph.windows.net/", + "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", + "galleryEndpointUrl": "https://gallery.azure.com/", + "managementEndpointUrl": "https://management.core.windows.net/" + } + ``` + +1. Run the following command to let the service principal access your key vault: + + ```console + az keyvault set-policy -n --spn --secret-permissions delete get + ``` + +1. Use the Azure CLI [az appconfig create](https://docs.microsoft.com/cli/azure/appconfig?view=azure-cli-latest#az-appconfig-create) + + ```azurecli + az appconfig create -n myAppconfigName -g MyResourceGroup -l westus --sku Standard + ``` + +1. Run the following command to get your object-id, then add it to App Configuration. + + ```console + az ad sp show --id + az role assignment create --role "App Configuration Data Reader" --assignee-object-id --resource-group + ``` + +1. Create the following environment variables, using the values for the service principal that were displayed in the previous step: + + * **AZURE_CLIENT_ID**: *clientId* + * **AZURE_CLIENT_SECRET**: *clientSecret* + * **AZURE_TENANT_ID**: *tenantId* + +1. Upload your Cosmos DB key to Key Vault. + + ```azurecli + az keyvault secret set --vault-name myVaultName --name "COSMOSDB-KEY" --value your-cosmosdb-key + ``` + +1. Upload your Configurations Cosmos DB name and URI to App Configuration + + ```azurecli + az appconfig kv set --name myConfigStoreName --key "/application/azure.cosmosdb.database" --value your-cosmos-db-databasename --yes + az appconfig kv set --name myConfigStoreName --key "/application/azure.cosmosdb.uri" --value your-cosmosdb-uri --yes + ``` + +1. Add a Key Vault Reference to App Configuration, make sure to update the uri with your config store name. + + ```azurecli + az appconfig kv set-keyvault --name myConfigStoreName --key "/application/azure.cosmosdb.key" --secret-identifier https://myVaultName.vault.azure.net/secrets/COSMOSDB-KEY --yes + ``` + +1. Delete `application.propertes` from `src/main/resources`. + +1. Create a new file called `bootstrap.properties` in `src/main/resources`, and add the following. + + ```properties + spring.cloud.azure.appconfiguration.stores[0].endpoint=https://{my-configstore-name}.azconfig.io + + ``` + +1. Update the pom.xml file to now include. + + ```xml + + com.microsoft.azure + spring-cloud-starter-azure-appconfiguration-config + 1.2.2 + + ``` + +1. Create a new file called *AzureCredentials.java* and add the code below. + + ```java + /* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + package sample.convert; + + import com.azure.core.credential.TokenCredential; + import com.azure.identity.EnvironmentCredentialBuilder; + import com.microsoft.azure.spring.cloud.config.AppConfigurationCredentialProvider; + import com.microsoft.azure.spring.cloud.config.KeyVaultCredentialProvider; + + public class AzureCredentials implements AppConfigurationCredentialProvider, KeyVaultCredentialProvider{ + + @Override + public TokenCredential getKeyVaultCredential(String uri) { + return getCredential(); + } + + @Override + public TokenCredential getAppConfigCredential(String uri) { + return getCredential(); + } + + private TokenCredential getCredential() { + return new EnvironmentCredentialBuilder().build(); + } + + } + ``` + + 1. Create a new file called *AppConfiguration.java*. And add the code below. + + ```java + /* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + package sample.convert; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class AppConfiguration { + + @Bean + public AzureCredentials azureCredentials() { + return new AzureCredentials(); + } + } + ``` + +1. Create a new folder in your resources directory called META-INF. Then in that folder create a file called *spring.factories* and add. + + ```factories + org.springframework.cloud.bootstrap.BootstrapConfiguration=\ + sample.convert.AppConfiguration + ``` + +#### Run the updated sample + +1. Build the JAR file using Maven; for example: + + ```shell + mvn clean package + ``` + +1. When the web app has been created, start the web app using Maven; for example: + + ```shell + mvn spring-boot:run + ``` + +1. View the results in the console. + +1. You should see the following message displayed: **findOne in User collection get result: testFirstName** + +## Examples +## Troubleshooting +## Next steps +## Contributing diff --git a/tests/fixtures/cases/code_in_markdown_2.md b/tests/fixtures/cases/code_in_markdown_2.md new file mode 100644 index 0000000..0e814a5 --- /dev/null +++ b/tests/fixtures/cases/code_in_markdown_2.md @@ -0,0 +1,320 @@ +--- +title: "1.8 Spec-generated docs" +permalink: /publishingapidocs1-8/ +course: "Publishing API documentation" +weight: 1.8 +--- + +Swagger online editor + +## Specs replace program-specific doc blocks + +With REST APIs, the platform and architecture behind the API can be in any programming language. This makes it difficult for tools to parse through comments within the code and run them through the same processing. + +However, with content in a standard specification, the tools can read and parse the content. There are at least two main specifications: + +* [Swagger](http://swagger.io/) +* [RAML](http://raml.org/) +* [API Blueprint](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md) + +Swagger is open source and has a strong community. RAML is more tied to Mulesoft's tooling platform. But both following generally the same principles. + +There's also an API Blueprint spec. + +## Swagger + +In this part of the course, we'll dive more into Swagger. There are a few different pieces to get straight: + +* [Swagger spec](https://github.com/swagger-api/swagger-spec) +* [Swagger editor](http://editor.swagger.io/#/) +* [Swagger-UI](https://github.com/swagger-api/swagger-ui) + +There are a lot of other [Swagger services and tools](http://swagger.io/open-source-integrations/). You can actually describe the code of your API using Swagger annotations, and then generate the Swagger output that way. However, in this example, we'll create the spec manually that will drive the visual display. + +## Some samples + +Before we get into this tutorial, check out a few Swagger implementations: + +* [Reverb](https://reverb.com/swagger#!/accounts). +* [VocaDB](https://github.com/swagger-api/swagger-codegen) +* [Watson Developer Cloud](http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/) + +Most of them look pretty much the same, with minimal branding. You'll notice the documentation is short and sweet in a Swagger implementation. This is because the Swagger display is meant to be an interactive experience where you can try out calls and see responses -- using your own API key to see your own data. It's the learn-by-doing-and-seeing-it approach. + +## WORKSHOP ACTIVITY + +In this activity, you'll create a Swagger UI display for the weatherdata endpoint in this [Mashape Weather API](https://www.mashape.com/fyhao/weather-13#weatherdata). + +### Create a Swagger spec file + +First we need to create a Swagger spec. + +1. Go to the [Swagger online editor](http://editor.swagger.io/#/). +2. Select **File > Open Example** and choose **PetStore Simple**. Click **Open**. + + You could just customize this sample YML file with the weatherdata endpoint documentation. However, to save you some time, you can just copy and paste the following into the Swagger editor: + + ``` + swagger: '2.0' + info: + version: '1.0.0' + title: Weather API + description: A sample API that uses a Mashape weather API as an example to demonstrate features in the swagger-2.0 specification + termsOfService: http://helloreverb.com/terms/ + contact: + name: Your name + email: youremail@mail.com + url: http://swagger.io + license: + name: MIT + url: http://opensource.org/licenses/MIT + host: simple-weather.p.mashape.com + schemes: + - https + consumes: + - application/json + produces: + - application/json + paths: + /aqi: + get: + description: gets air quality index + operationId: getWeatherData + produces: + - text/xml + - text/html + parameters: + - name: lat + in: query + description: latitude + required: false + type: string + - name: lng + in: query + description: longitude + required: false + type: string + responses: + '200': + description: aqi response + default: + description: unexpected error + /weather: + get: + description: gets weather forecast in short label + operationId: getWeatherData + produces: + - text/xml + - text/html + parameters: + - name: lat + in: query + description: latitude + required: false + type: string + - name: lng + in: query + description: longitude + required: false + type: string + responses: + '200': + description: aqi response + default: + description: unexpected error + /weatherdata: + get: + description: Get weather forecast by Latitude and Longtitude + operationId: getWeatherData + produces: + - application/json + - application/xml + - text/xml + - text/html + parameters: + - name: lat + in: query + description: latitude + required: false + type: string + - name: lng + in: query + description: longitude + required: false + type: string + responses: + '200': + description: aqi response + default: + description: unexpected error + ``` + + {{note}} YML syntax is a more human-readable form of JSON. Spacing matters! New levels appear existing text with two indented spaces. The colon indicates ano bject. Hyphens represent an array.{{end}} + +3. Go to **File > Download JSON** and save the file as "swagger.json" on your computer. + +### Set Up the Swagger UI + +1. Download [Swagger UI](https://github.com/swagger-api/swagger-ui). Click the **Download ZIP** button and download the files to a convenient location on your computer. Extract the files. + + The only file you'll be working with here is the dist folder. Everything else is used only if you're regenerating the files. + +2. Drag the dist folder out of the swagger-ui-master folder. +3. Rename the dist folder to your name. My name is Tom, so I'll rename my folder to "tom." +3. Inside your "tom" folder, open index.html. +4. Look for the following code: + + ``` + $(function () { + var url = window.location.search.match(/url=([^&]+)/); + if (url && url.length > 1) { + url = decodeURIComponent(url[1]); + } else { + url = "http://petstore.swagger.io/v2/swagger.json"; + } + ``` +5. Change the `url` value to the following: `"http://69.195.124.51/~idrathe1/workshopuploads/tom/swagger.json";`. + +In the `url` value, change "tom" to your name. This path is where you will upload your swagger file. Unfortunately, you can't run Swagger locally because it requires an http path in the URL. You will be uploading these files into this web host location. (This web host doesn't have a very friendly path -- sorry.) + +6. The Mashape API also requires a header authorization, so you'll need to make another change. Scroll down until you find the `addApiKeyAuthorization` function: + +``` + function addApiKeyAuthorization(){ + var key = encodeURIComponent($('#input_apiKey')[0].value); + if(key && key.trim() != "") { + var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("api_key", key, "query"); + window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth); + log("added key " + key); + } +``` + +Change the line ` window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth);` to `("X-Mashape-Key", "BTdeRzJ8h9rxmhtkEfGpj3pb7qRyu1MoALFoxsivNUgZ9s2jJY", "header"));`, so that it looks like this: + +``` + function addApiKeyAuthorization(){ + var key = encodeURIComponent($('#input_apiKey')[0].value); + if(key && key.trim() != "") { + var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("api_key", key, "query"); + swaggerUi.api.clientAuthorizations.add("key", new SwaggerClient.ApiKeyAuthorization("X-Mashape-Key", "BTdeRzJ8h9rxmhtkEfGpj3pb7qRyu1MoALFoxsivNUgZ9s2jJY", "header")); + log("added key " + key); + } +``` + +The `BTdeRzJ8h9rxmhtkEfGpj3pb7qRyu1MoALFoxsivNUgZ9s2jJY` is my Mashape API test key. You can swap in your own here if you have one (from the previous course) or use this one. + +7. Uncomment out the lines here by removing the `/*` and `*/`. + +``` +// if you have an apiKey you would like to pre-populate on the page for demonstration purposes... + /* + var apiKey = "myApiKeyXXXX123456789"; + $('#input_apiKey').val(apiKey); + */ +``` +8. Add in your API key, like this: + +``` +var apiKey = "BTdeRzJ8h9rxmhtkEfGpj3pb7qRyu1MoALFoxsivNUgZ9s2jJY"; +$('#input_apiKey').val(apiKey); +``` + +9. Save the file. +10. Drag the **swagger.json** file that you created earlier into the same directory as the index.html file you just edited. + +{{tip}} If the above instructions didn't work out, just go to http://69.195.124.51/~idrathe1/workshopuploads/tom/index.html, right-click to view the source, and then copy the text.{{end}} + +### Upload the Files to a Web Host + +1. Download and install [Filezilla](https://filezilla-project.org/), unless you already have another FTP editor you're used to working with. +2. Go to **File > Site Manager** and create a new site. +3. Enter the following information: + + host: 69.195.124.51 + protocol: ftp + user: stcsummit@idratherbewriting.com + pass: apiworkshop2015 + +4. Click **Connect**. +5. Upload your "tom" folder into the directory that you connect into by default. +6. Now view your site: http://69.195.124.51/~idrathe1/workshopuploads/tom, replacing "tom" with your name. + +Swagger instance + +### Interact with the Swagger UI + +1. Go to Google Maps and search for an address. +2. Get the latitude and longitude from the URL, and plug it into your Swagger UI. For example, (37.3708905, -121.9675525 is Santa Clara). +3. Click **Try it out**. + +If successful, you should see something in the response body like this: + +``` +17 c, Partly Cloudy at Santa Clara, United States +``` + +Try working with each of your endpoints and see the data that gets returned. + +## YML tutorial + +When you created the Swagger spec file, you used a syntax called YML. YML stands for "YAML Ain't Markup Language." YML is easier to work with because it removes the brackets and curly braces that get in the way of reading content. In other words, YML is an attempt to create a more human readable data exchange format. It's similar to JSON but without the curly braces and brackets. Many computers ingest data in a YML or JSON format. + +With a YML file, spacing is significant. This is a new object level: + +``` +level1: + level2: + level3: +``` + +To create a new level, indent two spaces. + +Each level can contain either a single value or an array: + +``` +--- + level3: + - + itema: "one" + itemameta: "two" + - + itemb: "three" + itembmeta: "four" +``` + +Converted to JSON, level3 would look like this: + +``` +{ + "level3": [ + { + "itema": "one", + "itemameta": "two" + }, + { + "itemb": "three", + "itembmeta": "four" + } + ] +} +``` + +If you learn how YML translates JSON syntax, it reduces the complexity of this format. + +## Swagger libraries + +Swagger can also be implemented directly in the code. There are many Swagger libraries for different code bases. If your developers want to integrate Swagger at the code level, they add certain annotations directly in the code. Swagger libraries can then parse the annotations and generate the same spec file that you produced manually using the earlier steps. [Swagger codegen](https://github.com/swagger-api/swagger-codegen) parses the annotations to create the swagger spec from your code. + +By integrating Swagger into the code, you allow developers to easily write documentation, make sure new features are always documented, and keep the documentation more current. + +The annotation methods for Swagger doc blocks vary based on the programming language. + +## Limitations with spec-driven documentation + +Notice a few limitations with Swagger: + +* There's not much room to describe in detail the workings of the endpoint. +* Making links or other markup in the YML file will require you to add some escape characters (`\`). + + + diff --git a/tests/fixtures/cases/code_in_markdown_with_lang_labels.md b/tests/fixtures/cases/code_in_markdown_with_lang_labels.md new file mode 100644 index 0000000..e92944f --- /dev/null +++ b/tests/fixtures/cases/code_in_markdown_with_lang_labels.md @@ -0,0 +1,87 @@ +# PAY.JP for Java + +[![Build Status](https://travis-ci.org/payjp/payjp-java.svg?branch=master)](https://travis-ci.org/payjp/payjp-java) + +Requirements +============ + +Java 1.6 and later. + +Installation +============ + +### Maven users + +Add this dependency to your project\'s POM: + +```xml + + jp.pay + payjp-java + 0.1.0 + +``` + +### Gradle users + +Add this dependency to your project\'s build file: + +```groovy +compile "jp.pay:payjp-java:0.1.0" +``` + +### Others + +You\'ll need to manually install the following JARs: + +* The Payjp JAR from https://github.com/payjp/payjp-java/releases/latest +* [Google Gson](http://code.google.com/p/google-gson/) from . +* [javax.mail](http://www.oracle.com/technetwork/java/javamail/index.html) from + +### [ProGuard](http://proguard.sourceforge.net/) + +If you\'re planning on using ProGuard, make sure that you exclude the Payjp bindings. You can do this by adding the following to your `proguard.cfg` file: + + -keep class jp.pay.** { *; } + +Usage +===== + +PayjpExample.java + +```java +import java.util.HashMap; +import java.util.Map; + +import jp.pay.Payjp; +import jp.pay.exception.PayjpException; +import jp.pay.model.Charge; +import jp.pay.net.RequestOptions; + +public class PayjpExample { + + public static void main(String[] args) { + Payjp.apiKey = "sk_test_a23maot6a746c40cd76d7036"; + Map chargeMap = new HashMap(); + chargeMap.put("amount", 3500); + chargeMap.put("currency", "jpy"); + Map cardMap = new HashMap(); + cardMap.put("number", "4242424242424242"); + cardMap.put("exp_month", 12); + cardMap.put("exp_year", 2020); + chargeMap.put("card", cardMap); + try { + Charge charge = Charge.create(chargeMap +); + System.out.println(charge); + } catch (PayjpException e) { + e.printStackTrace(); + } + } +} +``` + +Testing +======= + +You must have Maven installed. To run the tests, simply run `mvn test`. You can run particular tests by passing `-D test=Class#method` -- for example, `-D test=PayjpTest#testChargeCreate`. diff --git a/tests/fixtures/cases/html_inside_python.py b/tests/fixtures/cases/html_inside_python.py new file mode 100644 index 0000000..14773ad --- /dev/null +++ b/tests/fixtures/cases/html_inside_python.py @@ -0,0 +1,1300 @@ +# -*- coding: utf-8 -*- + +import unittest + +from dexter.models import Document, db +from dexter.models.seeds import seed_db +from dexter.processing.crawlers import CitizenCrawler + + +class TestTimesliveCrawler(unittest.TestCase): + def setUp(self): + self.crawler = CitizenCrawler() + + self.db = db + self.db.drop_all() + self.db.create_all() + seed_db(db) + + def tearDown(self): + self.db.session.remove() + self.db.drop_all() + + def test_canonicalise_url(self): + self.assertEqual( + self.crawler.canonicalise_url('https://www.citizen.co.za/153078/outa-claims-proof-e-toll-mismanagement'), + 'http://citizen.co.za/153078/outa-claims-proof-e-toll-mismanagement/', + ) + + def test_offer(self): + self.assertEqual( + self.crawler.offer('http://citizen.co.za/153078/outa-claims-proof-e-toll-mismanagement/'), True + ) + # ignore non-citizen + self.assertEqual( + self.crawler.offer( + 'http://www.iol.co.za/news/crime-courts/justice-system-not-equipped-for-cable-theft-1.1760799' + ), + False, + ) + + def test_extract(self): + html = """ + + + + + + + + + + + + +Outa claims proof of e-toll mismanagement | The Citizen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ +
+ + + +
+ +
+ +
+
+ + National + + 1.4.2014 01:50 pm +
+

+ Outa claims proof of e-toll mismanagement

+
+ + +
+
+ +
+ FILE PICTURE: E-toll objectors gather, 25 January 2014, at the Portuguese Hall in Joburg South, before taking part in a mass protest drive along the tolled highways. Picture: Michel Bega + +

+ FILE PICTURE: E-toll objectors gather, 25 January 2014, at the Portuguese Hall in Joburg South, before taking part in a mass protest drive along the tolled highways. Picture: Michel Bega

+
+

+ The Opposition to Urban Tolling Alliance (Outa) has once again pleaded with government to listen to “critics” against e-tolling, in light of new information provided by a whistle blower on the user payment system. +

+
+

This, as Outa awaits feedback from the Public Protector’s office on a complaint it laid, following “damning” information it received from a source within the system.

+

City Press reported on Sunday that an employee of Austrian Company Kapsch – used to design the e-toll system – had warned the SA National Roads Agency Ltd (Sanral) of the high risk in the implementation of a national roll-out.

+

He said the e-tolling system was designed to monitor 7 000 km of national roads, with tolls being planned for Durban and Cape Town, it reported.

+

The source further stated that there were design flaws within the system and Sanral’s control centre in Midrand had been created to monitor all roads in South Africa.

+

If the claims are true, it suggests that Sanral saw the Gauteng highway tolling project as either a starting point for national e-tolling, or it significantly over-invested in a system it did not need.

+

Sanral spokesman Vusi Mona, in rejecting the allegations by the “so-called informant”, said the Public Protector Thuli Madonsela had not yet contacted the entity.

+

It would however “co-operate”, should her office conduct an investigation, he added.

+

Mona said Sanral and its concessionaires were in the process of installing the “electronic toll collection equipment” needed at conventional toll plazas across the country.

+

It would make the exact details as to which toll plazas and when this payment method will be available, at an “opportune time”.

+

“Electronic toll collection – the use of e-tags – is a method of payment for one’s toll fees. Electronic toll collection does not replace existing toll plazas. It is a tool that toll plazas will use in addition to current methods of payment.

+

“By obtaining and e-tag and registering it, e-tolling allows an account holder to use the same e-tag and e-toll account to pay toll fees at any toll plaza equipped to accept e-tags.”

+

Deputy Public Protector Kevin Malunga, who was handling the complaint, according to Outa, was not immediately available for comment.

+

Outa chairman Wayne Duvenage told The Citizen that the alliance had always provided an open door for Parliament’s Portfolio Committee on Transport and Government’s Inter-Ministerial Committee (IMC), which previously engaged with stakeholders against e-tolling, prior to its implementation.

+

It was important for government to learn from its critics, said Duvenage.

+

“… And they are not learning from their critics. We are happy to present to anybody.”

+

He described previous IMC engagements as being unfruitful with government unwilling to unpack and explore the rationality of arguments.

+

The insider has described “them as arrogant and dangerous people who steam-roll public opinion … bully politicians, and business people and do not act in the interests of the country”, according to Outa.

+

Duvenage hailed the disclosures as a breakthrough which he hoped would start laying the table for “meaningful multi-lateral engagement with all stakeholders to transcend the mess”.

+

“The whistle blower shows the sheer arrogance of the system.”

+

He further asked motorists not the “fall” for a system which was not working.

+

“The compliance levels are far too low. Don’t be hoodwinked by the propaganda. Now the chickens are coming out to roost.”

+

Justice Project South Africa (JPSA) chairperson Howard Dembovsky said it was only a matter of time until someone with a “paining conscious” came forward.

+

“This is a very good thing, we need more people to tell the truth behind what has happened and what is happening with the e-tolls,” said Dembovsky.

+

The JPSA is convinced that there is corruption behind the controversial system: “It will come out in the dirty washing, a system rolled out veiled in shrouds of secrecy is a dead giveaway.”

+

Dembovsky echoed Duvenage’s comments, indicating that further IMC engagements would be “a waste of time”.

+

“The last round of engagements was just Sanral telling people what was going to happen. Do not waste more time and our money,” he said.

+

“Take the money and lets have a referendum, this is after all a democracy and we have not held a single referendum on the matter,” said Dembovsky.

+

“Let’s do it, let’s show what democracy is all about,” he added.

+

 

+ +
+ + +
+ + +
+
+ + +
+
+ + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+ +
+

readers' choice

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + +
+ +
+ + + +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+

poll

+ + +
+
+

today in print

+ + +
+ + Read Today's edition + +
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + doc = Document() + doc.url = 'http://citizen.co.za/153078/outa-claims-proof-e-toll-mismanagement/' + self.crawler.extract(doc, html) + + self.assertEqual(doc.title, 'Outa claims proof of e-toll mismanagement') + self.assertEqual( + doc.summary, + u'The Opposition to Urban Tolling Alliance (Outa) has once again pleaded with government to listen to \u201ccritics\u201d against e-tolling, in light of new information provided by a whistle blower on the user payment system.', + ) + self.assertEqual(doc.published_at.strftime('%d %m %Y'), '01 04 2014') + self.assertEqual(doc.author.name, "Yadhana Jadoo & Alex Mitchley") + self.assertEqual(doc.medium.name, 'Citizen') + + self.assertEqual( + doc.text, + u'The Opposition to Urban Tolling Alliance (Outa) has once again pleaded with government to listen to \u201ccritics\u201d against e-tolling, in light of new information provided by a whistle blower on the user payment system.\n\nThis, as Outa awaits feedback from the Public Protector\u2019s office on a complaint it laid, following \u201cdamning\u201d information it received from a source within the system.\n\nCity Press reported on Sunday that an employee of Austrian Company Kapsch \u2013 used to design the e-toll system \u2013 had warned the SA National Roads Agency Ltd (Sanral) of the high risk in the implementation of a national roll-out.\n\nHe said the e-tolling system was designed to monitor 7 000 km of national roads, with tolls being planned for Durban and Cape Town, it reported.\n\nThe source further stated that there were design flaws within the system and Sanral\u2019s control centre in Midrand had been created to monitor all roads in South Africa.\n\nIf the claims are true, it suggests that Sanral saw the Gauteng highway tolling project as either a starting point for national e-tolling, or it significantly over-invested in a system it did not need.\n\nSanral spokesman Vusi Mona, in rejecting the allegations by the \u201cso-called informant\u201d, said the Public Protector Thuli Madonsela had not yet contacted the entity.\n\nIt would however \u201cco-operate\u201d, should her office conduct an investigation, he added.\n\nMona said Sanral and its concessionaires were in the process of installing the \u201celectronic toll collection equipment\u201d needed at conventional toll plazas across the country.\n\nIt would make the exact details as to which toll plazas and when this payment method will be available, at an \u201copportune time\u201d.\n\n\u201cElectronic toll collection \u2013 the use of e-tags \u2013 is a method of payment for one\u2019s toll fees. Electronic toll collection does not replace existing toll plazas. It is a tool that toll plazas will use in addition to current methods of payment.\n\n\u201cBy obtaining and e-tag and registering it, e-tolling allows an account holder to use the same e-tag and e-toll account to pay toll fees at any toll plaza equipped to accept e-tags.\u201d\n\nDeputy Public Protector Kevin Malunga, who was handling the complaint, according to Outa, was not immediately available for comment.\n\nOuta chairman Wayne Duvenage told The Citizen that the alliance had always provided an open door for Parliament\u2019s Portfolio Committee on Transport and Government\u2019s Inter-Ministerial Committee (IMC), which previously engaged with stakeholders against e-tolling, prior to its implementation.\n\nIt was important for government to learn from its critics, said Duvenage.\n\n\u201c\u2026 And they are not learning from their critics. We are happy to present to anybody.\u201d\n\nHe described previous IMC engagements as being unfruitful with government unwilling to unpack and explore the rationality of arguments.\n\nThe insider has described \u201cthem as arrogant and dangerous people who steam-roll public opinion \u2026 bully politicians, and business people and do not act in the interests of the country\u201d, according to Outa.\n\nDuvenage hailed the disclosures as a breakthrough which he hoped would start laying the table for \u201cmeaningful multi-lateral engagement with all stakeholders to transcend the mess\u201d.\n\n\u201cThe whistle blower shows the sheer arrogance of the system.\u201d\n\nHe further asked motorists not the \u201cfall\u201d for a system which was not working.\n\n\u201cThe compliance levels are far too low. Don\u2019t be hoodwinked by the propaganda. Now the chickens are coming out to roost.\u201d\n\nJustice Project South Africa (JPSA) chairperson Howard Dembovsky said it was only a matter of time until someone with a \u201cpaining conscious\u201d came forward.\n\n\u201cThis is a very good thing, we need more people to tell the truth behind what has happened and what is happening with the e-tolls,\u201d said Dembovsky.\n\nThe JPSA is convinced that there is corruption behind the controversial system: \u201cIt will come out in the dirty washing, a system rolled out veiled in shrouds of secrecy is a dead giveaway.\u201d\n\nDembovsky echoed Duvenage\u2019s comments, indicating that further IMC engagements would be \u201ca waste of time\u201d.\n\n\u201cThe last round of engagements was just Sanral telling people what was going to happen. Do not waste more time and our money,\u201d he said.\n\n\u201cTake the money and lets have a referendum, this is after all a democracy and we have not held a single referendum on the matter,\u201d said Dembovsky.\n\n\u201cLet\u2019s do it, let\u2019s show what democracy is all about,\u201d he added.\n\n\xa0', + ) diff --git a/tests/fixtures/cases/inline_yaml_in_markdown.md b/tests/fixtures/cases/inline_yaml_in_markdown.md new file mode 100644 index 0000000..7d3c45f --- /dev/null +++ b/tests/fixtures/cases/inline_yaml_in_markdown.md @@ -0,0 +1,33 @@ + +{% endnavtab %} +{% navtab Universal %} + +```yaml +type: OPAPolicy +mesh: default +name: opa-1 +selectors: +- match: + kuma.io/service: '*' +conf: + agentConfig: + inlineString: | + services: + acmecorp: + url: https://example.com/control-plane-api/v1 + credentials: + bearer: + token: "hMLfg2XwFsdng3TqgsFyg2Lwi2Xyg2FwECzyi2XwFszxgsXsECdqi2zs" + discovery: + name: example + resource: /configuration/example/discovery + fdsafdsa: test +``` + +{% endnavtab %} +{% endnavtabs %} + + + + + diff --git a/tests/fixtures/cases/inline_yaml_inside_yaml.yaml b/tests/fixtures/cases/inline_yaml_inside_yaml.yaml new file mode 100644 index 0000000..52623c4 --- /dev/null +++ b/tests/fixtures/cases/inline_yaml_inside_yaml.yaml @@ -0,0 +1,24 @@ +type: OPAPolicy +mesh: default +name: opa-1 +selectors: +- match: + kuma.io/service: '*' +conf: + agentConfig: + inlineString: | + services: + acmecorp: + url: https://example.com/control-plane-api/v1 + credentials: + bearer: + token: "hMLfg2XwFsdng3TqgsFyg2Lwi2Xyg2FwECzyi2XwFszxgsXsECdqi2zs" + discovery: + name: example + resource: /configuration/example/discovery + fdsafdsa: test + + + + + diff --git a/tests/fixtures/cases/inline_yaml_inside_yaml_inside_markdown.md b/tests/fixtures/cases/inline_yaml_inside_yaml_inside_markdown.md new file mode 100644 index 0000000..e03ebad --- /dev/null +++ b/tests/fixtures/cases/inline_yaml_inside_yaml_inside_markdown.md @@ -0,0 +1,33 @@ + +### Hello! + +```yaml +type: OPAPolicy +mesh: default +name: opa-1 +selectors: +- match: + kuma.io/service: '*' +conf: + agentConfig: + inlineString: | + services: + acmecorp: + url: https://example.com/control-plane-api/v1 + credentials: + bearer: + token: "hMLfg2XwFsdng3TqgsFyg2Lwi2Xyg2FwECzyi2XwFszxgsXsECdqi2zs" + discovery: + name: example + resource: /configuration/example/discovery + fdsafdsa: test +``` + +## BYE +{% endnavtab %} +{% endnavtabs %} + + + + + diff --git a/tests/fixtures/cases/json_in_markdown.md b/tests/fixtures/cases/json_in_markdown.md new file mode 100644 index 0000000..3ded2b7 --- /dev/null +++ b/tests/fixtures/cases/json_in_markdown.md @@ -0,0 +1,292 @@ +# Spring Cloud Azure Config Conversion Sample client library for Java + +This sample shows how to convert a Spring Cloud Application with Cosmos DB to be using App Configuration + Key Vault + +## Key concepts +## Getting started +### Prerequisite + +* An Azure subscription; if you don't already have an Azure subscription, you can activate your [MSDN subscriber benefits](https://azure.microsoft.com/pricing/member-offers/msdn-benefits-details/) or sign up for a [free Azure account](https://azure.microsoft.com/free/). + +* A [Java Development Kit (JDK)](https://docs.microsoft.com/java/azure/jdk/?view=azure-java-stable), version 8. + +* [Apache Maven](http://maven.apache.org/), version 3.0 or later. + +### Quick Start + +#### Create an Azure Cosmos DB on Azure + +1. Use the Azure CLI [az cosmosdb create](https://docs.microsoft.com/cli/azure/cosmosdb?view=azure-cli-latest#az-cosmosdb-create). + + ```azurecli + az cosmosdb create --name my-cosmos-db --resource-group MyResourceGroup + ``` + + This operation will return json, among them is a documentEndpoint, record this. + + ```azurecli + { + ... + "documentEndpoint": "https://my-cosmos.documents.azure.com:443/", + ... + } + ``` + +1. Then use the [az cosmosdb keys list](https://docs.microsoft.com/cli/azure/cosmosdb/keys?view=azure-cli-latest#az-cosmosdb-keys-list). + + ```azurecli + az cosmosdb keys list --name my-cosmos-db -g MyResourceGroup + ``` + + Record the primaryMasterKey. + + ```azurecli + { + "primaryMasterKey": "...", + "primaryReadonlyMasterKey": "...", + "secondaryMasterKey": "...", + "secondaryReadonlyMasterKey": "..." + } + ``` + +#### Clone the sample Project + +In this section, you clone a containerized Spring Boot application and test it locally. + +1. Open a command prompt or terminal window and create a local directory to hold your Spring Boot application, and change to that directory; for example: + + ```shell + md C:\SpringBoot + cd C:\SpringBoot + ``` + + -- or -- + + ```shell + md /users/robert/SpringBoot + cd /users/robert/SpringBoot + ``` + +1. Clone the [Spring Boot on Docker Getting Started] sample project into the directory you created; for example: + + ```shell + git clone https://github.com/microsoft/spring-cloud-azure.git + ``` + +1. Change directory to the initial project; for example: + + ```shell + cd sdk/spring/azure-spring-boot-samples/azure-appconfiguration-conversion-sample-initial + ``` + +#### Config the sample + +1. Navigate to `src/main/resources` and open `application.properties`. + +1. Replace below properties in `application.properties` with information from your database. + + ```properties + azure.cosmosdb.uri=your-cosmosdb-uri + azure.cosmosdb.key=your-cosmosdb-key + azure.cosmosdb.database=your-cosmosdb-databasename + + ``` + +#### Run the sample + +1. Build the JAR file using Maven; for example: + + ```shell + mvn clean package + ``` + +1. When the web app has been created, start the web app using Maven; for example: + + ```shell + mvn spring-boot:run + ``` + +1. View the results in the console. + +1. You should see the following message displayed: **findOne in User collection get result: testFirstName** + +#### Convert to Using App Configuration + +1. Use the Azure CLI [az keyvault create](https://docs.microsoft.com/cli/azure/keyvault?view=azure-cli-latest#az-keyvault-create) + + ```azurecli + az keyvault create --name myVaultName -g MyResourceGroup + ``` + +1. Use the Azure CLI [az ad sp](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac) + + ```azurecli + az ad sp create-for-rbac -n "http://mySP" --sdk-auth + ``` + + This operation returns a series of key/value pairs: + + ```console + { + "clientId": "7da18cae-779c-41fc-992e-0527854c6583", + "clientSecret": "h421h443-1669-4ij7-h5h1-394j5i945002", + "subscriptionId": "443e30da-feca-47c4-b68f-1636b75e16b3", + "tenantId": "35ad10f1-7799-4766-9acf-f2d946161b77", + "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", + "resourceManagerEndpointUrl": "https://management.azure.com/", + "activeDirectoryGraphResourceId": "https://graph.windows.net/", + "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", + "galleryEndpointUrl": "https://gallery.azure.com/", + "managementEndpointUrl": "https://management.core.windows.net/" + } + ``` + +1. Run the following command to let the service principal access your key vault: + + ```console + az keyvault set-policy -n --spn --secret-permissions delete get + ``` + +1. Use the Azure CLI [az appconfig create](https://docs.microsoft.com/cli/azure/appconfig?view=azure-cli-latest#az-appconfig-create) + + ```azurecli + az appconfig create -n myAppconfigName -g MyResourceGroup -l westus --sku Standard + ``` + +1. Run the following command to get your object-id, then add it to App Configuration. + + ```console + az ad sp show --id + az role assignment create --role "App Configuration Data Reader" --assignee-object-id --resource-group + ``` + +1. Create the following environment variables, using the values for the service principal that were displayed in the previous step: + + * **AZURE_CLIENT_ID**: *clientId* + * **AZURE_CLIENT_SECRET**: *clientSecret* + * **AZURE_TENANT_ID**: *tenantId* + +1. Upload your Cosmos DB key to Key Vault. + + ```azurecli + az keyvault secret set --vault-name myVaultName --name "COSMOSDB-KEY" --value your-cosmosdb-key + ``` + +1. Upload your Configurations Cosmos DB name and URI to App Configuration + + ```azurecli + az appconfig kv set --name myConfigStoreName --key "/application/azure.cosmosdb.database" --value your-cosmos-db-databasename --yes + az appconfig kv set --name myConfigStoreName --key "/application/azure.cosmosdb.uri" --value your-cosmosdb-uri --yes + ``` + +1. Add a Key Vault Reference to App Configuration, make sure to update the uri with your config store name. + + ```azurecli + az appconfig kv set-keyvault --name myConfigStoreName --key "/application/azure.cosmosdb.key" --secret-identifier https://myVaultName.vault.azure.net/secrets/COSMOSDB-KEY --yes + ``` + +1. Delete `application.propertes` from `src/main/resources`. + +1. Create a new file called `bootstrap.properties` in `src/main/resources`, and add the following. + + ```properties + spring.cloud.azure.appconfiguration.stores[0].endpoint=https://{my-configstore-name}.azconfig.io + + ``` + +1. Update the pom.xml file to now include. + + ```xml + + com.microsoft.azure + spring-cloud-starter-azure-appconfiguration-config + 1.2.2 + + ``` + +1. Create a new file called *AzureCredentials.java* and add the code below. + + ```java + /* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + package sample.convert; + + import com.azure.core.credential.TokenCredential; + import com.azure.identity.EnvironmentCredentialBuilder; + import com.microsoft.azure.spring.cloud.config.AppConfigurationCredentialProvider; + import com.microsoft.azure.spring.cloud.config.KeyVaultCredentialProvider; + + public class AzureCredentials implements AppConfigurationCredentialProvider, KeyVaultCredentialProvider{ + + @Override + public TokenCredential getKeyVaultCredential(String uri) { + return getCredential(); + } + + @Override + public TokenCredential getAppConfigCredential(String uri) { + return getCredential(); + } + + private TokenCredential getCredential() { + return new EnvironmentCredentialBuilder().build(); + } + + } + ``` + + 1. Create a new file called *AppConfiguration.java*. And add the code below. + + ```java + /* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + package sample.convert; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class AppConfiguration { + + @Bean + public AzureCredentials azureCredentials() { + return new AzureCredentials(); + } + } + ``` + +1. Create a new folder in your resources directory called META-INF. Then in that folder create a file called *spring.factories* and add. + + ```factories + org.springframework.cloud.bootstrap.BootstrapConfiguration=\ + sample.convert.AppConfiguration + ``` + +#### Run the updated sample + +1. Build the JAR file using Maven; for example: + + ```shell + mvn clean package + ``` + +1. When the web app has been created, start the web app using Maven; for example: + + ```shell + mvn spring-boot:run + ``` + +1. View the results in the console. + +1. You should see the following message displayed: **findOne in User collection get result: testFirstName** + +## Examples +## Troubleshooting +## Next steps +## Contributing diff --git a/tests/fixtures/cases/markdown_with_yaml.txt b/tests/fixtures/cases/markdown_with_yaml.txt new file mode 100644 index 0000000..a883c09 --- /dev/null +++ b/tests/fixtures/cases/markdown_with_yaml.txt @@ -0,0 +1,71 @@ +:desc: Build a Rasa Chat Bot on Facebook Messenger + +.. _facebook-messenger: + +Facebook Messenger +================== + +.. edit-link:: + +Facebook Setup +-------------- + +You first need to set up a facebook page and app to get credentials to connect to +Facebook Messenger. Once you have them you can add these to your ``credentials.yml``. + + +Getting Credentials +^^^^^^^^^^^^^^^^^^^ + +**How to get the Facebook credentials:** +You need to set up a Facebook app and a page. + + 1. To create the app head over to + `Facebook for Developers `_ + and click on **My Apps** → **Add New App**. + 2. Go onto the dashboard for the app and under **Products**, + find the **Messenger** section and click **Set Up**. Scroll down to + **Token Generation** and click on the link to create a new page for your + app. + 3. Create your page and select it in the dropdown menu for the + **Token Generation**. The shown **Page Access Token** is the + ``page-access-token`` needed later on. + 4. Locate the **App Secret** in the app dashboard under **Settings** → **Basic**. + This will be your ``secret``. + 5. Use the collected ``secret`` and ``page-access-token`` in your + ``credentials.yml``, and add a field called ``verify`` containing + a string of your choice. Start ``rasa run`` with the + ``--credentials credentials.yml`` option. + 6. Set up a **Webhook** and select at least the **messaging** and + **messaging_postback** subscriptions. Insert your callback URL which will + look like ``https:///webhooks/facebook/webhook``. Insert the + **Verify Token** which has to match the ``verify`` + entry in your ``credentials.yml``. + + +For more detailed steps, visit the +`Messenger docs `_. + + +Running On Facebook Messenger +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to connect to Facebook using the run script, e.g. using: + +.. code-block:: bash + + rasa run + +you need to supply a ``credentials.yml`` with the following content: + +.. code-block:: yaml + + facebook: + verify: "rasa-bot" + secret: "3e34709d01ea89032asdebfe5a74518" + page-access-token: "EAAbHPa7H9rEBAAuFk4Q3gPKbDedQnx4djJJ1JmQ7CAqO4iJKrQcNT0wtD" + +The endpoint for receiving Facebook messenger messages is +``http://localhost:5005/webhooks/facebook/webhook``, replacing +the host and port with the appropriate values. This is the URL +you should add in the configuration of the webhook. \ No newline at end of file diff --git a/tests/fixtures/cases/secret_in_querystring.py b/tests/fixtures/cases/secret_in_querystring.py new file mode 100644 index 0000000..84013a9 --- /dev/null +++ b/tests/fixtures/cases/secret_in_querystring.py @@ -0,0 +1,7 @@ +ip_sources = ["Auto select", "ipinfo.io", "IP-API", "ipstack"] +ip_urls = [ + "", + "http://ipinfo.io/json", + "http://ip-api.com/json", + "http://api.ipstack.com/check?access_key=8g0gi323hg3036l3h4j0555jk7527970", +] diff --git a/tests/fixtures/cases/secret_in_yml_string.yml b/tests/fixtures/cases/secret_in_yml_string.yml new file mode 100644 index 0000000..fece9a4 --- /dev/null +++ b/tests/fixtures/cases/secret_in_yml_string.yml @@ -0,0 +1,54 @@ +--- +http_interactions: +- request: + method: post + uri: https://circleci.com/api/v1.1/project/mtchavez/circleci/ssh-key?circle-token= + body: + encoding: UTF-8 + string: '{"hostname":"hostname","private_key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAwwuwmtxV5vZwAuZKSEPrgddDb7Qv8eeX0PnCtS\nt428lZGSF0cNSmQIVIxBgi7twmLKpFgqS1LAAAb9+lXW+yh/f5Fl4VBirEvC3olS\n6yUAxWhDQ27waOK7ptTkYYGw+9g7QWSjjeK+vwIDAQABAoIBAG+oa4vcA1lbTzh5\kH1\nj2DjPlOi+Tno5OupI7xPY4HqHokToGkXxmKScxSnIefibATTzkRgVvIsyWjRxYbI\nZOuonGEkEUDmmvhhkNbuTqBd0XHcZxG8EJaeGsTLiJG4fG9x3ib/o4w7Lrfh2usb\nCjQt52L5Hg3JHXNokpwFOvhTN4uXUmjZKTQGmJceRz0Rqcemwn0ulcTYrQwQGyw/\nzncwkaNbiWRLJZJtRbMoTd3Mhr8ZvKp91vZGGLhn/BE2s7D5d/OXgz5N11T8iyN8\nSCW5YaECgYEA+Rlrlhui+tan9vo39VXh7wqJujrxlNTLP70ImpAfIoVVhAuPg+pL\naEPs/HIzSJOYppyI3fv6Bz8lKK46gO5VouokxmNQFNz2bxHn2FaXt37k/ye4tbqQ\nCuou+UvAlR/r4OT0Yj9wla0P5wo7IWRrdwWiAhj1DSkvkyGL3VLxF8kCgYEAyHLt\no43wRg50l63gectGCSRfyXuml3o38BLwQhToqbqPQFVncur+lNar9Xh5FRO9T+am\nd8ET+GLdCUXxfpu6eytEUgG3yndsHgXJz5Ta2hQdQT4lkkOlirO4ozYrBIilSrZh\nr8CQP2GKrDZ0QZkZRsBYKUZ0OnNPQXyboCfa9kcCgYB79mCJ9ProZYZ07BSI7NJg\nyRe9K7QpYrQ65fGwKWS1IzFpYu9qsGASZSs8fgBzb7AZyfB6t/i3Pn9ZfUrz+qd3\nSZo/eBDUMRoaMAj2qjEaSfXf3H6ZQVyJcf0qZr5R9+7EnmvXsMZwVg2B5p+CgJzS\nQGVdMdpRUFuylpEp9SqxsQKBgDkuB7obOEpTv1.1CxahJ0ORNMjAKGwlv9ok1aqazA\nGCqqrEiW1D7E6EB/CGiqqCeqDNvpGN2ad17onNMTX7NVKxoNmpymHs6jyHS8A/iy\nJsgE6t98oe0aXPO3FtmADz9o13X7ltwy2zMpWQyNMBayXLKBFeUYUvmFgTtWF3LV\nS7HrAoGBANhxhjIHYrsAy008UAHHebsyoteIPv2i/5uaRqgkEAVA+6lgTGBxK/Hq\nJ0WRSY+9eJAJn29Br0TO6pCbXS9PUwilFxfEs2iUvDkjYjpR/p3FMNfuzHf0eqoz\nCbCL2YiGWojMIyPkfhOn/vGeE8O7qAJ2xsNFARXes5tYY8h+MmR2\n-----END + RSA PRIVATE KEY-----\n"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + User-Agent: + - Ruby + Host: + - circleci.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + Date: + - Sun, 19 Feb 2017 00:03:27 GMT + Server: + - nginx + Set-Cookie: + - ring-session=fy6ymRVjTcpOkNdkpyuzc0BymqpnU4K%2BCLODQLol0y8bjQdK3YmbLWmrRmCdscJJJmjx4ZdfpkXf%2BFcBrXReWvT4mCQlR5G2pzplpYQPLBOyfr3yAJWcz2nJ23T%2FOH%2BJB0i9zJB2PM3nJeZ9kTTGNpo%2FMhlai3NjQ%2BEayDB%2FsnDQ0WldVdV%2FW2Wdmbd2NL6gDwm3vZ9JmqhU42g0S0cSrq8eFzZxZVL3GOEh4Tqp8QY%3D--AQH%2B64wrEQoM%2F8bE7MtULgNcej4uth5kc2S5S7iJcxM%3D;Path=/;HttpOnly;Expires=Sun, + 18 Feb 2018 08:57:47 +0000;Max-Age=31536000;Secure + Strict-Transport-Security: + - max-age=15724800 + X-Circleci-Identity: + - i-51a59da9 + X-Circleci-Request-Id: + - 0eb0d4b8-55b0-48fd-8d39-c5c5db49968e + X-Frame-Options: + - DENY + X-Route: + - "/ssh-key" + Content-Length: + - '2' + Connection: + - keep-alive + body: + encoding: UTF-8 + string: '""' + http_version: + recorded_at: Sat, 18 Feb 2017 23:59:39 GMT +recorded_with: VCR 3.0.3 diff --git a/tests/fixtures/cases/tricky_secrets.min.js b/tests/fixtures/cases/tricky_secrets.min.js new file mode 100644 index 0000000..ea64af5 --- /dev/null +++ b/tests/fixtures/cases/tricky_secrets.min.js @@ -0,0 +1,26 @@ +App.MODE_GOOGLE="google";App.MODE_DROPBOX="dropbox";App.MODE_ONEDRIVE="onedrive";App.MODE_GITHUB="github";App.MODE_DEVICE="device";App.MODE_BROWSER="browser";App.MODE_TRELLO="trello";App.DROPBOX_APPKEY="olezov2id9vcgml";App.DROPBOX_URL="https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js";App.DROPINS_URL="https://www.dropbox.com/static/api/2/dropins.js";App.ONEDRIVE_URL="https://js.live.net/v7.2/OneDrive.js";App.TRELLO_URL="https://api.trello.com/1/client.js";App.TRELLO_JQUERY_URL="https://code.jquery.com/jquery-1.7.1.min.js"; +App.FOOTER_PLUGIN_URL="https://www.jgraph.com/drawio-footer.js"; +Wa={Cisco_cisco_telecommuter_house_pc:"shape=mxgraph.cisco.buildings.telecommuter_house_pc;fillColor=#036897;strokeColor=#ffffff", +Cisco_cisco_telecommuter_house:"shape=mxgraph.cisco.buildings.telecommuter_house;fillColor=#036897;strokeColor=#ffffff",Cisco_cisco_telecommuter_icon:"shape=mxgraph.cisco.misc.telecommuter_icon;fillColor=#036897;strokeColor=#ffffff",Cisco_cisco_Telepresence_3200:"shape=mxgraph.cisco.misc.telepresence;fillColor=#036897;strokeColor=#ffffff",Cisco_cisco_terminal:"shape=mxgraph.cisco.computers_and_peripherals.terminal;fillColor=#036897;strokeColor=#ffffff",Cisco_cisco_token:"shape=mxgraph.cisco.misc.token;strokeColor=#036897", +Cisco_cisco_TP_MCU:"shape=mxgraph.cisco.misc.tp_mcu;fillColor=#036897;strokeColor=#ffffff"} + +PageSetupDialog.getFormats = function () { + return [{ key: "letter", title: 'US-Letter (8,5" x 11")', format: mxConstants.PAGE_FORMAT_LETTER_PORTRAIT }, { key: "legal", title: 'US-Legal (8,5" x 14")', format: new mxRectangle(0, 0, 850, 1400) }, { key: "tabloid", title: 'US-Tabloid (11" x 17")', format: new mxRectangle(0, 0, 1100, 1700) }, { key: "executive", title: 'US-Executive (7" x 10")', format: new mxRectangle(0, 0, 700, 1E3) }, { key: "a0", title: "A0 (841 mm x 1189 mm)", format: new mxRectangle(0, 0, 3300, 4681) }, { + key: "a1", title: "A1 (594 mm x 841 mm)", + format: new mxRectangle(0, 0, 2339, 3300) + }, { key: "a2", title: "A2 (420 mm x 594 mm)", format: new mxRectangle(0, 0, 1654, 2336) }, { key: "a3", title: "A3 (297 mm x 420 mm)", format: new mxRectangle(0, 0, 1169, 1654) }, { key: "a4", title: "A4 (210 mm x 297 mm)", format: mxConstants.PAGE_FORMAT_A4_PORTRAIT }, { key: "a5", title: "A5 (148 mm x 210 mm)", format: new mxRectangle(0, 0, 583, 827) }, { key: "a6", title: "A6 (105 mm x 148 mm)", format: new mxRectangle(0, 0, 413, 583) }, { key: "a7", title: "A7 (74 mm x 105 mm)", format: new mxRectangle(0, 0, 291, 413) }, + { key: "b4", title: "B4 (250 mm x 353 mm)", format: new mxRectangle(0, 0, 980, 1390) }, { key: "b5", title: "B5 (176 mm x 250 mm)", format: new mxRectangle(0, 0, 690, 980) }, { key: "16-9", title: "16:9 (1600 x 900)", format: new mxRectangle(0, 0, 1600, 900) }, { key: "16-10", title: "16:10 (1920 x 1200)", format: new mxRectangle(0, 0, 1920, 1200) }, { key: "4-3", title: "4:3 (1600 x 1200)", format: new mxRectangle(0, 0, 1600, 1200) }, { key: "custom", title: mxResources.get("custom"), format: null }] +}; + +t112.default.createElement("div", + { + key: "remove-button", + className: "form-nested-item-remove", + algolia:{ + appId: "RK0UG797F3", + apiKey: "39d7eb90d8b31d464e309375a52d674f", + indexName: "datahubproject", + contextualSearch:!1 + } + } +) \ No newline at end of file diff --git a/tests/fixtures/regex_checks.txt b/tests/fixtures/regex_checks.txt index 1b7334f..2894dae 100644 --- a/tests/fixtures/regex_checks.txt +++ b/tests/fixtures/regex_checks.txt @@ -65,4 +65,8 @@ amqp://login:${password}@example.com amqp://login:%password%@example.com // redis://:@: http://localhost:3001/@whatever/path/more -http://user:sneakypass@localhost:3001/@whatever/path/more \ No newline at end of file +http://user:sneakypass@localhost:3001/@whatever/path/more + +on PLAINTEXT://localhost:9092 123456terrence@igloo /usr/local/kafka15:51:25 𝜆 diff config/server.pro + +eing prematurely saved to database\n ([230e4a11](https://gitlab-ci-token:ridCNWnbTpavfVuJvWmS@git.sickrage.ca/SiCKRAGE/sickrage/commit/230e4a11b46145fc881186a3e \ No newline at end of file diff --git a/tests/generic_fixture_scans/test_run_full_scan.py b/tests/generic_fixture_scans/test_run_full_scan.py index 01eef9f..d842989 100644 --- a/tests/generic_fixture_scans/test_run_full_scan.py +++ b/tests/generic_fixture_scans/test_run_full_scan.py @@ -26,9 +26,10 @@ def test_everything(config: Config) -> None: mode = CliScanMode(config=config) mode.progress_bar = Mock() mode.progress_bar.add_task.return_value = 0 - findings = mode.run() + mode.progress_bar.task_ids = [] + findings, errors = mode.run() detections = [finding.detection for finding in findings] assert 'bAicxJVa5uVY7MjDlapthw' in detections assert 'nacc6opq' in detections - assert 'xBfiGBARuoQ9HoLWtw1HwbrkPurCI8v7fO7RJDaZFp7gkBqWxRjQc9WemTVrwu1c' in detections \ No newline at end of file + assert 'xBfiGBARuoQ9HoLWtw1HwbrkPurCI8v7fO7RJDaZFp7gkBqWxRjQc9WemTVrwu1c' in detections diff --git a/tests/output/test_sarif.py b/tests/output/test_sarif.py index 718fca2..f570f66 100644 --- a/tests/output/test_sarif.py +++ b/tests/output/test_sarif.py @@ -1,16 +1,18 @@ from unittest.mock import Mock +from jschema_to_python.to_json import to_json import pytest from deepsecrets.config import Config, Output from deepsecrets.core.engines.regex import RegexEngine from deepsecrets.core.engines.semantic import SemanticEngine -from deepsecrets.core.model.finding import FindingResponse +from deepsecrets.core.model.response.dojo_sarif import DojoSarifResponseBuilder from deepsecrets.core.rulesets.false_findings import FalseFindingsBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.scan_modes.cli import CliScanMode FP_TO_BE_EXCLUDED = '/app/tests/fixtures/service.postman_collection.json' + @pytest.fixture() def config() -> Config: config = None @@ -30,11 +32,15 @@ def test_dojo_sarif(config: Config) -> None: mode.progress_bar.add_task.return_value = 0 findings = [] - for file in mode.filepaths: - findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file)) - - findings = [] - findings = mode.run() - - sarif_response = FindingResponse.dojo_sarif_from_list(findings) - assert sarif_response is not None \ No newline at end of file + for file in mode.filepaths[:10]: + pfar = mode._per_file_analyzer(mode.analyzer_bundle(), file) + findings.extend(pfar.findings) + + sarif_response = to_json( + DojoSarifResponseBuilder() + .with_current_mode(mode) + .with_findings_list(findings) + .with_masking_enabled(not config.disable_masking) + .build() + ) + assert sarif_response is not None diff --git a/tests/scan_modes/test_cli_scan_mode.py b/tests/scan_modes/test_cli_scan_mode.py index 860a3f2..c9c7103 100644 --- a/tests/scan_modes/test_cli_scan_mode.py +++ b/tests/scan_modes/test_cli_scan_mode.py @@ -9,6 +9,7 @@ FP_TO_BE_EXCLUDED = '/app/tests/fixtures/service.postman_collection.json' + @pytest.fixture() def config() -> Config: config = None @@ -31,10 +32,11 @@ def test_cli_scan_mode(config: Config) -> None: mode.progress_bar = Mock() mode.progress_bar.add_task.return_value = 0 + mode.progress_bar.task_ids = [] findings = [] for file in mode.filepaths: - findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file)) + findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file, 0, {}).findings) assert len(findings) == 3 From 1e7914904f0aa5a98631196725265c2f81ba4011 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Tue, 19 May 2026 15:21:43 +0300 Subject: [PATCH 04/20] 2.0.0 RC1 --- deepsecrets/__main__.py | 25 +++- deepsecrets/cli.py | 30 +++-- deepsecrets/config.py | 2 +- deepsecrets/core/engines/semantic.py | 4 +- .../core/helpers/variable_evaluator.py | 26 +++- deepsecrets/core/model/finding.py | 4 +- deepsecrets/core/model/internal/processing.py | 4 +- deepsecrets/core/model/rules/regex.py | 31 ++++- .../core/model/rules/variable_scoring.py | 2 + deepsecrets/core/model/semantic.py | 15 ++- deepsecrets/core/model/token.py | 2 +- deepsecrets/core/modes/iscan_mode.py | 95 +++++++++------ .../core/tokenizers/cheap_var_search.py | 35 ++++++ .../helpers/cheap_var_search/__init__.py | 0 .../helpers/semantic/deep_analyzer.py | 8 +- .../semantic/var_detection/detector.py | 72 ++++++++++- .../helpers/semantic/var_detection/rules.py | 107 +++++++++++++---- .../helpers/single_token_improver.py | 10 +- deepsecrets/core/tokenizers/itokenizer.py | 25 +++- deepsecrets/core/tokenizers/lexer.py | 21 +--- deepsecrets/core/utils/guess_filetype.py | 10 +- deepsecrets/core/utils/lexer_finder.py | 8 +- deepsecrets/core/utils/lifecycle_hooks.py | 7 ++ deepsecrets/rules/regexes.json | 60 ++++++++-- deepsecrets/rules/variable_scoring_rules.json | 32 +++-- deepsecrets/scan_modes/cli.py | 5 +- deepsecrets/utils.py | 20 ++++ tests/case_helpers.py | 14 +++ tests/conftest.py | 6 + tests/core/cohesive/test_specific_cases.py | 26 ++-- tests/core/engines/regex/test_regex.py | 22 +++- tests/core/engines/semantic/test_semantic.py | 21 ++-- tests/core/helpers/test_variable_evaluator.py | 104 +++++++++++++++- tests/core/model/test_token.py | 4 +- .../lexer/variable_detection/test_js.py | 48 ++------ .../lexer/variable_detection/test_json.py | 11 ++ .../test_markdown_with_code_blocks.py | 2 +- .../lexer/variable_detection/test_py.py | 2 +- .../lexer/variable_detection/test_r_rd.py | 13 ++ .../core/tokenizers/test_cheap_var_search.py | 10 ++ tests/fixtures/4.json | 38 ++++++ tests/fixtures/5.js | 39 ++++++ tests/fixtures/5.json | 11 ++ tests/fixtures/5.py | 26 +++- tests/fixtures/cases/js_in_html.html | 113 ++++++++++++++++++ tests/fixtures/cases/private_keys.txt | 71 +++++++++++ tests/fixtures/cases/tricky_secrets.min.js | 10 +- tests/fixtures/cheap_var_detector_cases.txt | 8 ++ tests/fixtures/regex_checks.txt | 16 ++- .../test_run_full_scan.py | 2 +- 50 files changed, 1056 insertions(+), 221 deletions(-) create mode 100644 deepsecrets/core/tokenizers/cheap_var_search.py create mode 100644 deepsecrets/core/tokenizers/helpers/cheap_var_search/__init__.py create mode 100644 deepsecrets/utils.py create mode 100644 tests/core/tokenizers/lexer/variable_detection/test_json.py create mode 100644 tests/core/tokenizers/lexer/variable_detection/test_r_rd.py create mode 100644 tests/core/tokenizers/test_cheap_var_search.py create mode 100644 tests/fixtures/4.json create mode 100644 tests/fixtures/5.js create mode 100644 tests/fixtures/5.json create mode 100644 tests/fixtures/cases/js_in_html.html create mode 100644 tests/fixtures/cases/private_keys.txt create mode 100644 tests/fixtures/cheap_var_detector_cases.txt diff --git a/deepsecrets/__main__.py b/deepsecrets/__main__.py index 11cf1eb..05de819 100644 --- a/deepsecrets/__main__.py +++ b/deepsecrets/__main__.py @@ -1,11 +1,30 @@ import sys -from deepsecrets.cli import DeepSecretsCliTool + +from rich.align import Align + +from deepsecrets.utils import setup_interrupts + +setup_interrupts() def runnable_entrypoint(): + from deepsecrets.cli import DeepSecretsCliTool + return_code = DeepSecretsCliTool(sys.argv).start() sys.exit(return_code) -if __name__ == '__main__': - runnable_entrypoint() +try: + if __name__ == '__main__': + runnable_entrypoint() + +except KeyboardInterrupt: + from deepsecrets import console + + console.rule(style='red') + console.print(Align('[italic]Feel free to report bugs and difficulties here', align='left')) + console.print(Align('[italic]https://github.com/ntoskernel/deepsecrets/issues', align='left')) + console.line() + console.print(Align('[bold yellow]FINISHED WITH EXIT CODE 130', align='left')) + console.line() + sys.exit(130) diff --git a/deepsecrets/cli.py b/deepsecrets/cli.py index c7dd0a7..14bdb5f 100644 --- a/deepsecrets/cli.py +++ b/deepsecrets/cli.py @@ -27,7 +27,6 @@ from rich.text import Text from rich.align import Align - DISABLED = 'disabled' @@ -42,13 +41,14 @@ class ReturnCodes: TextColumn("[progress.description]{task.description}", table_column=Column(max_width=60, no_wrap=True)), TextColumn("[bold blue]{task.fields[size]}"), BarColumn(bar_width=None), - TaskProgressColumn(), + TaskProgressColumn('[progress.percentage]{task.percentage:>3.1f}%'), TimeRemainingColumn(), TextColumn("[bold red]{task.fields[findings]}", justify="right"), TextColumn("[bold red]{task.fields[errors]}", justify="right"), console=console, refresh_per_second=5, expand=True, + speed_estimate_period=90, ) @@ -198,12 +198,12 @@ def _build_argparser(self) -> None: parser.add_argument('--outfile', required=True, type=str) parser.add_argument( '--outformat', - default='json', + default='sarif', type=str, choices=['json', 'sarif', 'dojo-sarif'], - help='"json": internal format (default, will be deprecated soon)\n' - '"sarif": SARIF format (specification accurate, will become default soon)\n' - '"dojo-sarif": SARIF format (compatible with DefectDojo\'s parser)\n', + help='"sarif": SARIF format (specification accurate, default)\n' + '"dojo-sarif": SARIF format (compatible with DefectDojo\'s parser)\n' + '"json": old internal format (deprecated and will be removed soon)', ) parser.add_argument( @@ -280,6 +280,9 @@ def parse_arguments(self) -> None: def get_current_config(self) -> Config: return config + def _add_ignorefiles(self, files: List[str]): + config.set_global_exclusion_paths(files) + def start(self) -> int: # pragma: nocover startup_time = datetime.now() try: @@ -290,13 +293,13 @@ def start(self) -> int: # pragma: nocover if config.output.type == 'json': console.print('\n') - if SCANNER_VERSION_NUMERIC[0] == 1 and SCANNER_VERSION_NUMERIC[1] < 5: + if SCANNER_VERSION_NUMERIC[0] == 2 and SCANNER_VERSION_NUMERIC[1] < 1: console.print( Align( Panel( - "The internal JSON report format is now DEPRECATED.\n\nThe tool will begin reporting in SARIF BY DEFAULT starting from the release 1.5.0 (January 2026)\n\nConsider switching now.", + "The internal JSON report format is now DEPRECATED and will be removed in release 2.1.0\n\nConsider switching now.", padding=(1, 2), - title=Text('SWITCHING TO SARIF NEXT RELEASE', style='reverse'), + title=Text('SARIF IS NOW DEFAULT OUTPUT FORMAT', style='reverse'), highlight=True, subtitle=Text(' --outformat sarif ', style='reverse'), title_align='center', @@ -314,11 +317,11 @@ def start(self) -> int: # pragma: nocover console.print( Align( Panel( - f"The internal JSON report format was DEPRECATED in the release 1.4.1.\nNow ({SCANNER_VERSION}) it is REMOVED.\n.", + f"The internal JSON report format was DEPRECATED since the release 2.0.0.\nNow ({SCANNER_VERSION}) it is REMOVED. Switch to SARIF\n.", padding=(1, 2), title=Text('SARIF IS NOW DEFAULT OUTPUT FORMAT', style='reverse'), highlight=True, - subtitle=Text('', style='reverse'), + subtitle=Text(' --outformat sarif ', style='reverse'), title_align='center', width=90, subtitle_align='center', @@ -359,8 +362,9 @@ def start(self) -> int: # pragma: nocover findings: List[Finding] errors: Dict[str, List[str]] + timings: Dict[str, int] - findings, errors = mode.run() + findings, errors, timings = mode.run() ''' for finding in findings: @@ -403,7 +407,7 @@ def start(self) -> int: # pragma: nocover console.print(Align(table, align='center')) if config._benchmarking_mode is True: - return findings, errors, mode._oneshot_file + return findings, errors, timings, mode._oneshot_file with open(report_path, 'w+') as f: diff --git a/deepsecrets/config.py b/deepsecrets/config.py index 1f30fd5..bb84a58 100644 --- a/deepsecrets/config.py +++ b/deepsecrets/config.py @@ -11,7 +11,7 @@ FALLBACK_PROCESS_COUNT = 4 SCANNER_NAME = "DeepSecrets" -SCANNER_VERSION = "1.4.1" +SCANNER_VERSION = "2.0.0" SCANNER_VERSION_NUMERIC = [int(subver) for subver in SCANNER_VERSION.split('.')] SCANNER_URL = "https://github.com/ntoskernel/deepsecrets" diff --git a/deepsecrets/core/engines/semantic.py b/deepsecrets/core/engines/semantic.py index dbd7e54..a78c25d 100644 --- a/deepsecrets/core/engines/semantic.py +++ b/deepsecrets/core/engines/semantic.py @@ -87,7 +87,7 @@ def search(self, token: Token) -> List[Finding]: confidence=evaluation_result.export_confidence, ) ], - internal_score=evaluation_result.summary(), + internal_score={'var': token.semantic.name} | evaluation_result.summary(), ) ) else: @@ -103,7 +103,7 @@ def search(self, token: Token) -> List[Finding]: confidence=evaluation_result.export_confidence, ) ], - internal_score=evaluation_result.summary(), + internal_score={'var': token.semantic.name} | evaluation_result.summary(), ) ) diff --git a/deepsecrets/core/helpers/variable_evaluator.py b/deepsecrets/core/helpers/variable_evaluator.py index 62ecf05..3450290 100644 --- a/deepsecrets/core/helpers/variable_evaluator.py +++ b/deepsecrets/core/helpers/variable_evaluator.py @@ -25,8 +25,13 @@ class EvaluationResult: # < 3: 0 # 3-4: 0 -> 35 - def summary(self) -> str: - return f'conf: {self.export_confidence}; n+c:{self.naming_and_content_score}; e:{self.entropy_score}; gib:{self.nonsence_value_score}' + def summary(self) -> dict: + return { + 'conf': self.export_confidence, + 'n+c': self.naming_and_content_score, + 'e': round(self.entropy_score, 2), + 'gib': self.nonsence_value_score, + } HOPELESS_THRESHOLD = -100 @@ -41,6 +46,9 @@ def __init__(self, rules: List[VariableScoringRule]) -> None: self.rules = rules def calculate_entropy_score(self, entropy: float) -> float: + if entropy == 0: + return -1 + if entropy < 3: return 0 @@ -89,6 +97,11 @@ def evaluate(self, variable: Union[Variable | Context]) -> EvaluationResult: naming_and_content_score = 0 matched_rules = [] + + if len(context.value) <= 4: + matched_rules.append('SEM_INTRNL_VAL_LENGTH') + return EvaluationResult(total_score=-100, is_dangerous=False, matched_rules=matched_rules) + for rule in self.rules: fired = rule.match_by_context(context) if fired: @@ -104,9 +117,16 @@ def evaluate(self, variable: Union[Variable | Context]) -> EvaluationResult: entropy = EntropyHelper.get_for_string(context.value) entropy_score = self.calculate_entropy_score(entropy) + if entropy == 0 and entropy_score == -1: + return EvaluationResult( + total_score=naming_and_content_score, + is_dangerous=False, + entropy=entropy, + entropy_score=entropy_score, + matched_rules=matched_rules, + ) nonsense_value_score = self.calculate_nonsense_value_score(context.value_parts, context.value_normalized) - total_score = naming_and_content_score + entropy_score result = EvaluationResult( diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index c160379..4737085 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -23,7 +23,7 @@ class Finding(BaseModel): end_offset: int reason: str = Field(default='') final_rule: Optional[Rule] = Field(default=None) - internal_score: Optional[str] = Field(default_factory=str) + internal_score: Optional[dict] = Field(default_factory=dict) _mapped_on_file: bool = PrivateAttr(default=False) model_config = ConfigDict(arbitrary_types_allowed=True) @@ -77,7 +77,7 @@ def __hash__(self) -> int: # pragma: nocover return hash(f'{self.file.path}{self.detection}{self.start_offset}{self.end_offset}') def get_id(self) -> int: - return 99000 + self.__hash__() + return int(str(abs(self.__hash__()))[:8]) def __eq__(self, other: Any) -> bool: if not isinstance(other, Finding): diff --git a/deepsecrets/core/model/internal/processing.py b/deepsecrets/core/model/internal/processing.py index 3d48a69..0c247f7 100644 --- a/deepsecrets/core/model/internal/processing.py +++ b/deepsecrets/core/model/internal/processing.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import field, dataclass from typing import List, Optional from deepsecrets.core.model.file import File from deepsecrets.core.model.finding import Finding @@ -10,5 +10,7 @@ class PerFileAnalysisResult: findings: List[Finding] errors: List[str] + processing_time_seconds: int = field(default=0) + # ONLY FOR BENCHMARKING MODE _file: Optional[File] = None diff --git a/deepsecrets/core/model/rules/regex.py b/deepsecrets/core/model/rules/regex.py index d57431c..d3a7cd4 100644 --- a/deepsecrets/core/model/rules/regex.py +++ b/deepsecrets/core/model/rules/regex.py @@ -12,10 +12,12 @@ class RegexRule(Rule): # type: ignore pattern: re.Pattern + negative_pattern: Optional[re.Pattern] = Field(default=None) match_rules: Optional[Dict[int, RegexRule]] = Field(default={}) # type: ignore target_group: int = Field(default=0) entropy_settings: Optional[float] = Field(default=None) escaping_needed: bool = False + case_sensitive: bool = False model_config = ConfigDict(arbitrary_types_allowed=True) @@ -27,12 +29,33 @@ def serialize_dt(self, pattern: re.Pattern, _info): @classmethod def build_pattern(cls, values: Dict) -> Dict: pattern_str = values.get('pattern', None) + negative_pattern_str = values.get('negative_pattern', None) + if pattern_str is not None and isinstance(pattern_str, str): escaping_needed = values.get('escaping_needed', False) + + flags = 0 + case_sensitive = values.get('case_sensitive', False) if escaping_needed: pattern_str = re.escape(pattern_str) - values['pattern'] = re.compile(pattern_str, re.IGNORECASE) + if case_sensitive is False: + flags = flags | re.IGNORECASE + + values['pattern'] = re.compile(pattern_str, flags) + + if negative_pattern_str is not None and isinstance(negative_pattern_str, str): + escaping_needed = values.get('escaping_needed', False) + + flags = 0 + case_sensitive = values.get('case_sensitive', False) + if escaping_needed: + negative_pattern_str = re.escape(negative_pattern_str) + + if case_sensitive is False: + flags = flags | re.IGNORECASE + + values['negative_pattern'] = re.compile(negative_pattern_str, flags) match_rules = values.get('match_rules', {}) for _, match_rule in match_rules.items(): @@ -51,6 +74,12 @@ def match(self, token: Union[Token, str]) -> List[re.Match]: contents.extend(token.uncovered_content if isinstance(token, Token) else []) for i, content in enumerate(contents): + if ( + self.negative_pattern is not None + and re.search(pattern=self.negative_pattern, string=content) is not None + ): + continue + matches = re.finditer(self.pattern, content) for match in matches: diff --git a/deepsecrets/core/model/rules/variable_scoring.py b/deepsecrets/core/model/rules/variable_scoring.py index 9746eac..5dfae6e 100644 --- a/deepsecrets/core/model/rules/variable_scoring.py +++ b/deepsecrets/core/model/rules/variable_scoring.py @@ -9,6 +9,7 @@ class Target(str, Enum): NAME_NORMALIZED = "NAME_NORMALIZED" # "apiaccesskey" (Best for fuzzy) VALUE = "VALUE" FILEPATH = "FILEPATH" # The file path + VALUE_NORMALIZED = 'VALUE_NORMALIZED' target_to_fields = { @@ -16,6 +17,7 @@ class Target(str, Enum): Target.NAME_SPACED: 'name_spaced', Target.VALUE: 'value', Target.NAME_NORMALIZED: 'name_normalized', + Target.VALUE_NORMALIZED: 'value_normalized', } diff --git a/deepsecrets/core/model/semantic.py b/deepsecrets/core/model/semantic.py index da90091..09a672c 100644 --- a/deepsecrets/core/model/semantic.py +++ b/deepsecrets/core/model/semantic.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import List +from typing import List, Optional from deepsecrets.core.model.token import Token from deepsecrets.core.utils.string import StringUtils @@ -55,9 +55,12 @@ def normalize_punctuation(self, string: str, split_camel_case=True): class Variable: - name: Token - value: Token + name_token: Token = None + value_token: Token = None _context: Context = None + + name_override: Optional[str] + value_override: Optional[str] span: List[int] found_by: 'VariableDetector' @@ -65,9 +68,9 @@ class Variable: def context(self): if self._context is None: self._context = Context( - name=self.name.content, - value=self.value.content, - filepath=self.name.file.path, + name=self.name_token.content if self.name_token is not None else self.name_override, + value=self.value_token.content if self.value_token is not None else self.value_override, + filepath=self.value_token.file.path, ) return self._context diff --git a/deepsecrets/core/model/token.py b/deepsecrets/core/model/token.py index 83a6ef1..4d98948 100644 --- a/deepsecrets/core/model/token.py +++ b/deepsecrets/core/model/token.py @@ -68,7 +68,7 @@ def __repr__(self) -> str: # pragma: no cover return f'{self.content} | {self.type[0]}\n' out = f'======== VAR: {self.semantic.payload.context.name} = {self.content}' # type: ignore - if self.type is not None: + if self.type is not None and len(self.type) > 0: out += f' | {self.type[0]}\n' return out diff --git a/deepsecrets/core/modes/iscan_mode.py b/deepsecrets/core/modes/iscan_mode.py index 332d27d..09715a7 100644 --- a/deepsecrets/core/modes/iscan_mode.py +++ b/deepsecrets/core/modes/iscan_mode.py @@ -1,3 +1,8 @@ +from deepsecrets.utils import setup_interrupts_for_subprocess + +setup_interrupts_for_subprocess() +import time + from dataclasses import dataclass, field from multiprocessing.pool import AsyncResult import regex as re @@ -188,14 +193,15 @@ def refresh_overall_progress_bar(self, pb_task_id): size=f'{self.stats.finished}/{self.stats.total_files}', ) - def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: + def run(self) -> Tuple[List[Finding], Dict[str, List[str]], Dict[str, int]]: final: List[Finding] = [] errors: Dict[str, List[str]] = dict() + timings: Dict[str, int] = dict() bundle = self.analyzer_bundle() proc_count = self._get_process_count_for_runner() if proc_count == 0: - return final, errors + return final, errors, timings overall_progress_task = self.progress_bar.add_task( "[green bold]OVERALL\nPROGRESS\n", @@ -213,37 +219,57 @@ def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: ).findings ) else: - with self.pool_engine(processes=proc_count) as pool: - tid = 0 - for file in self.filepaths: - tid += 1 - result = pool.apply_async( - pool_wrapper, - (bundle, self._per_file_analyzer, tid, self.active_task_reporter, file), - ) - self.file_results.append(result) - self.file_jobs[tid] = FileJob(name=file, internal_id=tid, pb_task_id=None, result_holder=result) - pool.close() - - self.stats.total_files = len(self.file_jobs.keys()) - while self.stats.finished < self.stats.total_files: - self.refresh_jobs_progress_bars() - self.refresh_overall_progress_bar(overall_progress_task) - # self.refresh_overall_debug_progress_bar(overall_debug) - self.stop_progress_bar(overall_progress_task) - console.print('[*] Collecting results..') - pool.join() - - for job_result in self.file_results: - analysis_result: PerFileAnalysisResult = job_result.get() - self._oneshot_file = analysis_result._file - - job = self.file_jobs.get(analysis_result.internal_task_id) - errors[job.name] = analysis_result.errors - - if analysis_result.findings is None or len(analysis_result.findings) == 0: - continue - final.extend(analysis_result.findings) + try: + with self.pool_engine(processes=proc_count) as pool: + tid = 0 + for file in self.filepaths: + tid += 1 + result = pool.apply_async( + pool_wrapper, + (bundle, self._per_file_analyzer, tid, self.active_task_reporter, file), + ) + self.file_results.append(result) + self.file_jobs[tid] = FileJob(name=file, internal_id=tid, pb_task_id=None, result_holder=result) + pool.close() + + self.stats.total_files = len(self.file_jobs.keys()) + while self.stats.finished < self.stats.total_files: + self.refresh_jobs_progress_bars() + self.refresh_overall_progress_bar(overall_progress_task) + time.sleep(0.1) + # self.refresh_overall_debug_progress_bar(overall_debug) + self.stop_progress_bar(overall_progress_task) + console.print('[*] Collecting results..') + pool.join() + + except KeyboardInterrupt: + if getattr(self, 'progress_bar', None): + self.stop_progress_bar(overall_progress_task) + + console.print( + "\n[bold red][!] Scan abort request was received (Ctrl+C).\n Intermediate results will NOT be saved.\n Shutting down workers...[/bold red]" + ) + + if 'pool' in locals(): + pool.terminate() + pool.join() + + if getattr(self, '_mp_manager', None): + self._mp_manager.shutdown() + + raise + + for job_result in self.file_results: + analysis_result: PerFileAnalysisResult = job_result.get(timeout=1000) + self._oneshot_file = analysis_result._file + + job = self.file_jobs.get(analysis_result.internal_task_id) + errors[job.name] = analysis_result.errors + timings[job.name] = analysis_result.processing_time_seconds + + if analysis_result.findings is None or len(analysis_result.findings) == 0: + continue + final.extend(analysis_result.findings) console.line() console.print('[*] Merging similar findings..') @@ -251,7 +277,7 @@ def run(self) -> Tuple[List[Finding], Dict[str, List[str]]]: console.print('[*] Filtering predefined false Findings..') fin = self.filter_false_positives(fin) - return fin, errors + return fin, errors, timings def dispose(self): self.task_reporter = None @@ -355,5 +381,6 @@ def filter_false_positives(self, results: List[Finding]) -> List[Finding]: def pool_wrapper( bundle: DotWiz, runner: Callable, task_id: Optional[int], task_reporter: DictProxy, file: str ) -> PerFileAnalysisResult: # pragma: nocover + result = runner(bundle, file, task_id, task_reporter) return result diff --git a/deepsecrets/core/tokenizers/cheap_var_search.py b/deepsecrets/core/tokenizers/cheap_var_search.py new file mode 100644 index 0000000..7c76466 --- /dev/null +++ b/deepsecrets/core/tokenizers/cheap_var_search.py @@ -0,0 +1,35 @@ +from typing import List + +from deepsecrets.core.model.file import File +from deepsecrets.core.model.semantic import Variable +from deepsecrets.core.model.token import Semantic, SemanticType, Token +from deepsecrets.core.tokenizers.helpers.semantic.var_detection.rules import CheapVariableDetectionRules +from deepsecrets.core.tokenizers.itokenizer import Tokenizer + + +class CheapVarSearchTokenizer(Tokenizer): + + def tokenize(self, file: File) -> List[Token]: + rules = CheapVariableDetectionRules.rules + vars: List[Variable] = [] + for rule in rules: + vars.extend(rule.match(file.content)) + + for variable in vars: + name_token = Token( + file=file, content=variable.name, span=file.get_span_for_string(variable.name, variable.span) + ) + variable.name_token = name_token + + value_token = Token( + file=file, content=variable.value, span=file.get_span_for_string(variable.value, variable.span) + ) + variable.value_token = value_token + variable.value_token.semantic = Semantic( + type=SemanticType.VARIABLE, + payload=variable, + creds_probability=variable.found_by.creds_probability, + ) + self.tokens.append(value_token) + + return self.tokens diff --git a/deepsecrets/core/tokenizers/helpers/cheap_var_search/__init__.py b/deepsecrets/core/tokenizers/helpers/cheap_var_search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py b/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py index 1a8365c..bf8a78b 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py @@ -68,16 +68,18 @@ def analyze_token_sequence(self, language: Language, tokens: List[Token], stream for var in true_var_detections: suppressed = self._if_suppressed(var, suppression_regions) if suppressed: - exclude_after.update([var.name, var.value]) + exclude_after.update([var.name_token, var.value_token]) continue - var.value.semantic = Semantic( + var.value_token.semantic = Semantic( type=SemanticType.VARIABLE, # name=var.name.content, payload=var, creds_probability=var.found_by.creds_probability, ) - exclude_after.add(var.name) + + if var.name_token is not None: + exclude_after.add(var.name_token) return exclude_after diff --git a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py index 054b302..9e6a1fa 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py @@ -84,20 +84,28 @@ class RegionDetector(BaseModel): stream_pattern: re.Pattern match_rules: Dict[int, Match] - match_semantics: Dict[int, str] + match_semantics: Dict[int | str, str] + + # Useful when positive lookaheads are used as a Match's span window is empty + span_by_group_index: Optional[int] = None creds_probability: int = 0 model_config = ConfigDict(arbitrary_types_allowed=True) def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: true_detections = [] - for match in re.finditer(self.stream_pattern, token_stream, overlapped=True): if not self._verify(match, tokens): continue reg = Region() for i, name in self.match_semantics.items(): - setattr(reg, name, [match.span(i)[0], match.span(i)[1]]) + if isinstance(i, int): + setattr(reg, name, [match.span(i)[0], match.span(i)[1]]) + elif isinstance(i, str): + setattr(reg, name, i) + else: + pass + reg.found_by = self reg.span = [match.span(0)[0], match.span(0)[1]] @@ -128,9 +136,20 @@ def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: var = Variable() for i, name in self.match_semantics.items(): - setattr(var, name, tokens[match.span(i)[0]]) + if isinstance(i, int): + setattr(var, name, tokens[match.span(i)[0]]) + elif isinstance(i, str): + setattr(var, name, i) + else: + pass + var.found_by = self - var.span = [match.span(0)[0], match.span(0)[1]] + + if self.span_by_group_index is not None: + span_group = match.span(self.span_by_group_index) + var.span = [span_group[0], span_group[1]] + else: + var.span = [match.span(0)[0], match.span(0)[1]] true_detections.append(var) @@ -148,4 +167,47 @@ def match(self, tokens: List[Token], token_stream: str) -> List['Variable']: return spans +class CheapVariableDetector(RegionDetector): + + def match(self, content: str) -> List['Variable']: + true_detections = [] + for m in re.finditer(self.stream_pattern, content, overlapped=True): + if not self._verify(m): + continue + + var = Variable() + for i, name in self.match_semantics.items(): + if isinstance(i, int): + setattr(var, name, content[m.span(i)[0] : m.span(i)[1]]) + elif isinstance(i, str): + setattr(var, name, i) + else: + pass + + var.found_by = self + + if self.span_by_group_index is not None: + span_group = m.span(self.span_by_group_index) + var.span = [span_group[0], span_group[1]] + else: + var.span = [m.span(0)[0], m.span(0)[1]] + + true_detections.append(var) + + return true_detections + + def _verify(self, match: re.Match) -> bool: + match_ok = True + + if self.match_rules is not None: + for group_i, match_rule in self.match_rules.items(): + span = match.span(group_i) + window = match.string[span[0] : span[1]] + if not match_rule.match(window): # type: ignore + match_ok = False + return False + + return match_ok + + from deepsecrets.core.model.semantic import Region, Variable diff --git a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py index efc2610..b82bce2 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py @@ -3,6 +3,7 @@ from deepsecrets.core.tokenizers.helpers.semantic.language import Language from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import ( + CheapVariableDetector, Match, VariableDetector, VariableSuppressor, @@ -28,19 +29,19 @@ class VariableDetectionRules: language=Language.PYTHON, stream_pattern=re.compile('(n)(o|p)(?:\n?)(L)(?:\n|p|\?)'), # noqa match_rules={2: Match(values=[re.compile('^=$'), re.compile('^:$')])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.PYTHON, stream_pattern=re.compile('(L)(p)(L)(?:p|\n)'), match_rules={2: Match(values=[':'])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.PYTHON, stream_pattern=re.compile('(L)(p)(o)(L)'), match_rules={2: Match(values=[']']), 3: Match(values=['='])}, - match_semantics={1: 'name', 4: 'value'}, + match_semantics={1: 'name_token', 4: 'value_token'}, ), VariableDetector( language=Language.PYTHON, @@ -50,14 +51,24 @@ class VariableDetectionRules: 3: Match(values=['(']), 5: Match(values=[')']), }, - match_semantics={1: 'name', 4: 'value'}, + match_semantics={1: 'name_token', 4: 'value_token'}, + ), + VariableDetector( + language=Language.PYTHON, + stream_pattern=re.compile('(n)(p)(L)(p)(L)', flags=re.MULTILINE | re.S), + match_rules={ + 1: Match(values=['getenv']), + 2: Match(values=['(']), + 4: Match(values=[',']), + }, + match_semantics={3: 'name_token', 5: 'value_token'}, ), # GOLANG VariableDetector( language=Language.GOLANG, stream_pattern=re.compile('(n)(o|p)(L)(?:p|\n)?'), match_rules={2: Match(values=[':', '=', ':='])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.GOLANG, @@ -67,7 +78,7 @@ class VariableDetectionRules: 2: Match(values=['(']), 5: Match(values=[')']), }, - match_semantics={3: 'name', 4: 'value'}, + match_semantics={3: 'name_token', 4: 'value_token'}, ), VariableDetector( language=Language.GOLANG, @@ -76,20 +87,20 @@ class VariableDetectionRules: 2: Match(values=[':=']), 3: Match(not_values=['Getenv', 'Setenv', 'Format']), }, - match_semantics={1: 'name', 5: 'value'}, + match_semantics={1: 'name_token', 5: 'value_token'}, ), VariableDetector( language=Language.GOLANG, stream_pattern=re.compile('(n)(?:o|p){1,3}(\?|u)p(L)p'), # noqa match_rules={2: Match(values=['byte', 'string'])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), # PHP VariableDetector( language=Language.PHP, stream_pattern=re.compile('(n|v|L)(o)(L)'), match_rules={2: Match(values=['=', '=>'])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.PHP, @@ -99,32 +110,32 @@ class VariableDetectionRules: 3: Match(values=['env']), 4: Match(values=['(']), }, - match_semantics={1: 'name', 5: 'value'}, + match_semantics={1: 'name_token', 5: 'value_token'}, ), # CONFIGS AND FORMATS VariableDetector( language=Language.TOML, stream_pattern=re.compile('(n)(o)(L)\n'), match_rules={2: Match(values=['='])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.YAML, stream_pattern=re.compile('(L)(p)(L)'), match_rules={2: Match(values=[':'])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.INI, stream_pattern=re.compile('(n)(o)(L)'), match_rules={2: Match(values=['='])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.PUPPET, stream_pattern=re.compile('(v|n)(o)(L)'), match_rules={2: Match(values=['=>', '='])}, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.ANY, @@ -137,7 +148,7 @@ class VariableDetectionRules: ] ) }, - match_semantics={1: 'name', 3: 'value'}, + match_semantics={1: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.SHELL, @@ -147,7 +158,7 @@ class VariableDetectionRules: 2: Match(values=[re.compile('^-u$')]), 4: Match(not_values=[re.compile('^\\$')]), }, - match_semantics={3: 'name', 4: 'value'}, + match_semantics={3: 'name_token', 4: 'value_token'}, creds_probability=9, ), VariableDetector( @@ -157,7 +168,7 @@ class VariableDetectionRules: 1: Match(values=[re.compile('^KeyValuePair$')]), 4: Match(not_values=[re.compile('^}$')]), }, - match_semantics={2: 'name', 3: 'value'}, + match_semantics={2: 'name_token', 3: 'value_token'}, ), VariableDetector( language=Language.CSHARP, @@ -167,7 +178,7 @@ class VariableDetectionRules: 3: Match(values=[re.compile('^,$')]), 5: Match(values=[re.compile('^}$')]), }, - match_semantics={2: 'name', 4: 'value'}, + match_semantics={2: 'name_token', 4: 'value_token'}, ), VariableDetector( language=Language.JAVA, @@ -177,7 +188,7 @@ class VariableDetectionRules: 2: Match(values=[re.compile('^\\($')]), 4: Match(values=[re.compile('^,$')]), }, - match_semantics={3: 'name', 5: 'value'}, + match_semantics={3: 'name_token', 5: 'value_token'}, ), VariableDetector( language=Language.MARKDOWN, @@ -187,7 +198,36 @@ class VariableDetectionRules: 2: Match(values=[re.compile('^\\($')]), 4: Match(values=[re.compile('^,$')]), }, - match_semantics={3: 'name', 5: 'value'}, + match_semantics={3: 'name_token', 5: 'value_token'}, + ), + VariableDetector( + language=Language.JS, + stream_pattern=re.compile('(L)(o)(L)'), + match_rules={ + 1: Match(types=[PygmentsToken.Literal.String.Double, PygmentsToken.Literal.String.Single]), + 2: Match(values=[re.compile('^:$')]), + 3: Match(types=[PygmentsToken.Literal.String.Double, PygmentsToken.Literal.String.Single]), + }, + match_semantics={1: 'name_token', 3: 'value_token'}, + ), + VariableDetector( + language=Language.JS, + stream_pattern=re.compile('(n)(p)Lp(L)p'), + match_rules={ + 1: Match(values=[re.compile('^algoliasearch$')]), + 2: Match(values=[re.compile('^\\($')]), + }, + match_semantics={'algolia_api_secret_key': 'name_override', 3: 'value_token'}, + ), + VariableDetector( + language=Language.JS, + stream_pattern=re.compile('(n)p(n)p(n)(o)(L)'), + match_rules={ + 1: Match(values=[re.compile('^process$')]), + 2: Match(values=[re.compile('^env$')]), + 4: Match(values=[re.compile('^||')]), + }, + match_semantics={3: 'name_token', 5: 'value_token'}, ), ] @@ -296,4 +336,31 @@ class VariableSuppressionRules(VariableDetectionRules): }, match_semantics={}, ), + VariableSuppressor( + language=Language.JSON, + stream_pattern=re.compile('(?=((n)pL(?:.|\n)*?(n)pL))'), + match_rules={ + 2: Match(values=[re.compile('.*key.*'), re.compile('.*value.*')]), + 3: Match(values=[re.compile('.*key.*'), re.compile('.*value.*')]), + }, + match_semantics={}, + span_by_group_index=1, + ), + ] + + +class CheapVariableDetectionRules(VariableDetectionRules): + rules = [ + # looking for generic key-value + CheapVariableDetector( + stream_pattern=re.compile('(["\'])([^\\[\\]"\'\\)\\(;\\s]+)\\1\\s*[:=]\\s*(["\'])([^\\[\\]"\';\\s]+)\\3'), + match_rules={}, + match_semantics={2: 'name', 4: 'value'}, + ), + # looking for random urls + CheapVariableDetector( + stream_pattern=re.compile('(?:\\:\\/\\/[^\n\r" ]*?|\\G)[?&]([^=&\\s]+)=([^&\\s"\',]*)'), + match_rules={}, + match_semantics={1: 'name', 2: 'value'}, + ), ] diff --git a/deepsecrets/core/tokenizers/helpers/single_token_improver.py b/deepsecrets/core/tokenizers/helpers/single_token_improver.py index 02996f7..22a1087 100644 --- a/deepsecrets/core/tokenizers/helpers/single_token_improver.py +++ b/deepsecrets/core/tokenizers/helpers/single_token_improver.py @@ -3,6 +3,7 @@ from pygments.token import Token as PygmentsToken +from deepsecrets.core.model.rules.regex import RegexRule from deepsecrets.core.model.token import Token from deepsecrets.core.tokenizers.helpers.semantic.language import Language from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import Match, RegionDetector @@ -15,11 +16,16 @@ class SingleTokenImprover: def __init__(self, lang: Language) -> None: self.language = lang - self.acc = {Language.SHELL: [self._curl_argstring_breakdown]} + self.acc = { + Language.SHELL: [self._curl_argstring_breakdown], + } def improve(self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token) -> List[Token]: + checkers: List[Callable] = self.acc.get(Language.ANY, []) + checkers.extend(self.acc.get(self.language, [])) + tokens = [] - for improvement in self.acc.get(self.language, []): + for improvement in checkers: tokens.extend(improvement(so_far_tokens, so_far_type_stream, current_token)) if len(tokens) == 0: diff --git a/deepsecrets/core/tokenizers/itokenizer.py b/deepsecrets/core/tokenizers/itokenizer.py index cb9c9c4..40c5179 100644 --- a/deepsecrets/core/tokenizers/itokenizer.py +++ b/deepsecrets/core/tokenizers/itokenizer.py @@ -1,9 +1,9 @@ from abc import abstractmethod from collections import namedtuple -from typing import List, NamedTuple +from typing import List, NamedTuple, Optional from deepsecrets.core.model.file import File -from deepsecrets.core.model.token import Token +from deepsecrets.core.model.token import SemanticType, Token from deepsecrets.core.utils.lifecycle_hooks import FileLifecycleHooks @@ -40,12 +40,25 @@ def on_new_offset_processed(self, new_offset: float): def tokenize(self, file: File) -> List[Token]: pass - @abstractmethod - def get_variables(self): - return [] - def __hash__(self) -> int: # pragma: nocover return hash(type(self)) def __repr__(self) -> str: # pragma: no cover return self.__class__.__name__ + + def get_variables(self, tokens: Optional[List[Token]] = None) -> List[Token]: + tokens = tokens if tokens is not None else self.tokens + vars = [] + if len(tokens) == 0: + return [] + + for token in tokens: + if token.semantic is None: + continue + + if token.semantic.type != SemanticType.VARIABLE: + continue + + vars.append(token) + + return vars diff --git a/deepsecrets/core/tokenizers/lexer.py b/deepsecrets/core/tokenizers/lexer.py index 6195149..5af2124 100644 --- a/deepsecrets/core/tokenizers/lexer.py +++ b/deepsecrets/core/tokenizers/lexer.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Type, Union +from typing import List, Type, Union from deepsecrets.core.model.tokenized_region import TokenizedRegion @@ -9,7 +9,7 @@ from pygments.token import Token as PygmentsToken from deepsecrets.core.model.file import File -from deepsecrets.core.model.token import SemanticType, Token +from deepsecrets.core.model.token import Token from deepsecrets.core.tokenizers.helpers.semantic.language import Language from deepsecrets.core.tokenizers.helpers.single_token_improver import SingleTokenImprover from deepsecrets.core.tokenizers.helpers.type_stream import ( @@ -128,22 +128,5 @@ def add_to_token_stream(self, tokens: List[Token]) -> None: def print_token_type_stream(self) -> None: print(self.token_stream) - def get_variables(self, tokens: Optional[List[Token]] = None) -> List[Token]: - tokens = tokens if tokens is not None else self.tokens - vars = [] - if len(tokens) == 0: - return [] - - for token in tokens: - if token.semantic is None: - continue - - if token.semantic.type != SemanticType.VARIABLE: - continue - - vars.append(token) - - return vars - from deepsecrets.core.tokenizers.helpers.subfile_regions_helper import SubFileRegionsHelper diff --git a/deepsecrets/core/utils/guess_filetype.py b/deepsecrets/core/utils/guess_filetype.py index 1351fc6..79e9706 100644 --- a/deepsecrets/core/utils/guess_filetype.py +++ b/deepsecrets/core/utils/guess_filetype.py @@ -9,6 +9,9 @@ class FileTypeGuesser: def __init__(self) -> None: + + self.host_swaps = {'Rd': 'R'} + self.probes = { 'json': self._is_json, 'toml': self._is_toml, @@ -19,7 +22,12 @@ def __init__(self) -> None: # 'properties': self._dot_properties, } - def guess(self, content: str) -> Optional[str]: + def guess(self, name: str, content: str, extension: Optional[str]) -> Optional[str]: + + swap = self.host_swaps.get(extension) + if swap is not None: + return swap + for ext, probe in self.probes.items(): if probe(content): return ext diff --git a/deepsecrets/core/utils/lexer_finder.py b/deepsecrets/core/utils/lexer_finder.py index 2e79970..b89fa3b 100644 --- a/deepsecrets/core/utils/lexer_finder.py +++ b/deepsecrets/core/utils/lexer_finder.py @@ -67,7 +67,7 @@ def find(self, file: File): return lexer def _determine_extension(self): - meta_extensions = ['txt', 'conf'] + meta_extensions = ['txt', 'conf', 'Rd'] if self.file.extension is None or self.file.extension in meta_extensions: return self._try_guess_extension() @@ -87,7 +87,11 @@ def _if_hocon_coffeescript_hack(self): return 'coffeescript' def _try_guess_extension(self) -> Optional[str]: - guess = FileTypeGuesser().guess(self.file.content) + guess = FileTypeGuesser().guess( + name=self.file.name, + extension=self.file.extension, + content=self.file.content, + ) if guess is not None: return guess diff --git a/deepsecrets/core/utils/lifecycle_hooks.py b/deepsecrets/core/utils/lifecycle_hooks.py index 6af5aa4..2c3bdc7 100644 --- a/deepsecrets/core/utils/lifecycle_hooks.py +++ b/deepsecrets/core/utils/lifecycle_hooks.py @@ -1,9 +1,13 @@ +from datetime import datetime from typing import Optional from deepsecrets.core.utils.progress import FileProgress, Progress from multiprocessing.managers import DictProxy class LifecycleHooks: + start_ts: datetime + end_ts: datetime + progress: Progress reporter: DictProxy task_id: int @@ -14,14 +18,17 @@ def __init__(self, task_id: int, progress: Progress, reporter: DictProxy) -> Non self.reporter = reporter def on_start(self): + self.start_ts = datetime.now() self.progress.on_start() self._report() def on_failure(self, child_report: Optional[dict] = None): + self.end_ts = datetime.now() self.progress.on_failure() self._report(child_report) def on_finish(self, child_report: Optional[dict] = None): + self.end_ts = datetime.now() self.progress.on_finish() self._report(child_report) diff --git a/deepsecrets/rules/regexes.json b/deepsecrets/rules/regexes.json index e09ab87..ccf5ccf 100644 --- a/deepsecrets/rules/regexes.json +++ b/deepsecrets/rules/regexes.json @@ -9,31 +9,37 @@ "id": "S1", "name": "RSA private key", "confidence": 9, - "pattern": "-----BEGIN[\\S\\s].*RSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*RSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" + "pattern": "-BEGIN[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-" }, { "id": "S2", "name": "SSH (OPENSSH) private key", "confidence": 9, - "pattern": "-----BEGIN[\\S\\s].*OPENSSH[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*OPENSSH[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" + "pattern": "-BEGIN[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S3", "name": "SSH (DSA) private key", "confidence": 9, - "pattern": "-----BEGIN[\\S\\s].*DSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*DSA[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" + "pattern": "-BEGIN[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S4", "name": "SSH (EC) private key", "confidence": 9, - "pattern": "-----BEGIN[\\S\\s].*EC[\\S\\s].*PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*EC[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" + "pattern": "-BEGIN[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S5", "name": "PGP private key block", "confidence": 9, - "pattern": "-----BEGIN[\\S\\s].*PGP[\\S\\s].*PRIVATE[\\S\\s].*KEY[\\S\\s].*BLOCK-----" + "pattern": "-BEGIN[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-----([^-][\\s\\S]{50,3000})[^-]-----END[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-", + "match_rules": { + "1": { + "pattern": ".*", + "negative_pattern": "[-{}()<>]" + } + } }, { "id": "S7", @@ -80,7 +86,8 @@ "target_group": 2, "match_rules": { "2": { - "pattern": "^[^\\$|{|%|<|][^ ]+[^\\$|}|%|>]$" + "pattern": "^[^\\${%<*][^ ]+[^}%>*]$", + "negative_pattern": "^(.*symb.*|.*mock.*|.*color.*|.*password.*|.*ctrl.*|.*help.*|.*numb.*|.*long.*|.*short.*|.*tip.*|.*down.*|.*head.*|.*info.*|.*last.*|.*next.*|.*title.*|.*label.*|.*error.*|.*view.*|.*center.*|.*date.*|.*paren.*|.*able.*|.*examp.*|.*servi.*|.*class.*|.*prope.*|.*reset.*|.*eract.*|.*rule.*|.*track.*|.*cloud.*|.*call.*|.*index.*|.*limit.*|.*acces.*|.*param.*|.*note.*|.*name.*|.*descr.*|.*value.*|.*polic.*|.*part.*|.*correc.*|.*conte.*|.*media.*|.*exten.*|.*defau.*|.*fill.*|.*unive.*|.*star.*|.*addit.*|.*stac.*|.*excep.*|.*messa.*|.*zone.*|.*total.*|.*type.*|.*valid.*|.*instan.*|.*seria.*|.*docum.*|.*base.*|.*addre.*|.*size.*|.*first.*|.*sourc.*|.*cover.*|.*gener.*|.*config.*|.*with.*|.*window.*|.*support.*|.*comment.*|.*batch.*|.*vault.*|.*secret.*|.*copy.*|.*filte.*|.*clear.*|.*status.*|.*private.*|.*password.*|.*invalid.*|.*link.*|.*test.*|.*expir.*|.*empty.*|.*token.*|null|bearer|foo|foobar|xyz|undefined|none|.*todo.*|.*change.*|.*restore.*|[^A-Za-z0-9]*|%.*%|\\[.*\\]|{.*})$" } } }, @@ -112,7 +119,13 @@ "id": "S26", "name": "Custom private key", "confidence": 9, - "pattern": "-----BEGIN[\\S\\s]{,10}?PRIVATE[\\S\\s].*KEY-----[\\S\\s]{15,}?-----END[\\S\\s].*PRIVATE[\\S\\s].*KEY-----" + "pattern": "-BEGIN[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-----([\\s\\S]{50,3500})?-----END[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-", + "match_rules": { + "1": { + "pattern": ".*", + "negative_pattern": "[-]" + } + } }, { "id": "S28", @@ -121,7 +134,7 @@ "confidence": 0, "match_rules": { "2": { - "pattern": "^\\s*(?:'|:|=)*\\s*$" + "pattern": "^\\s*(?:'|:|=)+\\s*$" } }, "target_group": 3, @@ -171,13 +184,40 @@ "id": "S35", "name": "AWS Access Key ID", "confidence": 9, - "pattern": "\\b(A3T[A-Z0-9]|AKIA|AGPA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\\b" + "pattern": "\\b(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16,17}\\b", + "case_sensitive": true }, { "id": "S36", "description": "https://docs.stripe.com/keys", "name": "Stripe Secret", "confidence": 9, - "pattern": "\\b(sk|rk)_(test|live)_[0-9a-zA-Z]{24,35}\\b" + "pattern": "\\b(?:pk|sk|rk)_(?:test|live)_[0-9a-zA-Z]{24,45}\\b" + }, + { + "id": "S37", + "name": "BitCoin WIF Private Key", + "confidence": 7, + "pattern": "\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b", + "case_sensitive": true + }, + { + "id": "S38", + "name": "Google API Key", + "confidence": 9, + "pattern": "\\bAIzaSy[A-Za-z0-9\\-_]{33}\\b" + }, + { + "id": "S39", + "name": "Mailchimp API Key", + "confidence": 6, + "pattern": "\\b[0-9a-f]{32}-[a-z]{2}\\d{1,2}\\b" + }, + { + "id": "S40", + "name": "LocationIQ Key", + "confidence": 9, + "pattern": "\\bpk\\.[a-zA-Z0-9]{30,32}\\b" } + ] diff --git a/deepsecrets/rules/variable_scoring_rules.json b/deepsecrets/rules/variable_scoring_rules.json index 25ec9cf..03ce4a8 100644 --- a/deepsecrets/rules/variable_scoring_rules.json +++ b/deepsecrets/rules/variable_scoring_rules.json @@ -2,7 +2,7 @@ { "id": "SEM_VAR_FALSE_STARTING_SEQ", "name": "Ignore variable values with specific starting", - "pattern": "^(\\$\\{|%env|true|false|\\(|<)", + "pattern": "^(\\$\\{|%env|true|false|#|\\(|<|-|_)", "target": "VALUE", "score": -1000 }, @@ -10,13 +10,13 @@ "id": "SEM_VAR_DUMMY_VALUES", "name": "Ignore variable values with specific flags in values", "description": "For me in future: The last part of the regex matches only special symbols", - "pattern": "^(null|bearer|undefined|none|todo|.*change.*|.*restore.*|[^A-Za-z0-9]*|%.*%|\\[.*\\]|{.*})$", - "target": "VALUE", + "pattern": "^(.*${.*|.*obje.*|.*menu.*|.*settin.*|.*locat.*|.*option.*|.*histor.*|record.*|.*versi.*|.*origi.*|.*year.*|.*day.*|.*week.*|.*detai.*|.*gatew.*|.*packa.*|.*prefer.*|.*chain.*|.*provid.*|.*phon.*|.*email.*|.*convers.*|.*perso.*|.*recomm.*|.*referen.*|.*master.*|.*impor.*|.*encrypt.*|.*sched.*|.*contac.*|.*verla.*|.*mediu.*|.*extern.*|.*intern.*|.*progr.*|.*contr.*|.*positi.*|.*facebook.*|.*scale.*|.*button.*|.*untime.*|.*modif.*|.*service.*|.*volum.*|.*length.*|.*guest.*|.*point.*|.*speci.*|.*networ.*|.*mount.*|.*instruc.*|.*kuberne.*|.*layer.*|.*notifi.*|.*submi.*|.*success.*|.*operati.*|.*percen.*|.*persona.*|.*direct.*|.*prefix.*|.*print.*|.*previ.*|.*price.*|.*protoco.*|.*reaso.*|.*count.*|.*reques.*|.*return.*|.*event.*|.*desig.*|.*place.*|.*select.*|.*search.*|.*share.*|.*shape.*|.*show.*|.*stora.*|.*width.*|.*plan.*|.*suggest.*|.*style.*|.*syste.*|.*targe.*|.*export.*|.*amou.*|.*actio.*|.*encod.*|.*trans.*|.*page.*|.*local.*|.*symb.*|.*userid.*|.*useragent.*|.*mock.*|.*color.*|.*password.*|.*ctrl.*|.*help.*|.*numb.*|.*long.*|.*short.*|.*tip.*|.*down.*|.*head.*|.*foote.*|.*info.*|.*last.*|.*next.*|.*title.*|.*label.*|.*error.*|.*view.*|.*center.*|.*date.*|.*paren.*|.*able.*|.*examp.*|.*servi.*|.*class.*|.*prope.*|.*reset.*|.*eract.*|.*rule.*|.*track.*|.*cloud.*|.*call.*|.*index.*|.*limit.*|.*acces.*|.*param.*|.*note.*|.*name.*|.*descr.*|.*value.*|.*polic.*|.*part.*|.*correc.*|.*conte.*|.*media.*|.*exten.*|.*defau.*|.*fill.*|.*unive.*|.*star.*|.*addit.*|.*stac.*|.*excep.*|.*messa.*|.*zone.*|.*total.*|.*type.*|.*valid.*|.*instan.*|.*seria.*|.*docum.*|.*base.*|.*addre.*|.*size.*|.*first.*|.*sourc.*|.*cover.*|.*gener.*|.*config.*|.*with.*|.*window.*|.*support.*|.*comment.*|.*batch.*|.*vault.*|.*secret.*|.*copy.*|.*filte.*|.*clear.*|.*status.*|.*private.*|.*password.*|.*invalid.*|.*link.*|.*test.*|.*expir.*|.*empty.*|.*token.*|null|bearer|foo|foobar|xyz|undefined|none|.*todo.*|.*change.*|.*restore.*|[^A-Za-z0-9]*|%.*%|\\[.*\\]|{.*})$", + "target": "VALUE_NORMALIZED", "score": -50 }, { "id": "SEM_VAR_FILE_PATHS", - "enabled": false, + "enabled": true, "description": "Slightly reduce score for test/mock/minified files", "target": "FILEPATH", "pattern": "(.*\\.min\\.js$|/test/|/tests/|/spec/|/mock/|_test\\.)", @@ -26,42 +26,42 @@ "id": "SEM_VAR_NAME_SLICE_REDFLAGS", "description": "Reduce score for common non-secret words inside the variable name PARTS", "target": "NAME_SPACED", - "pattern": "\\b(item|limit|result|window|open|public|str|path|location|field|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template)\\b", + "pattern": "\\b(.*agram.*|.*expira.*|.*contin.*|hashed|storage|operation|contains|last|used|button|meta|method|convert|title|region|label|saving|state|list|extra|group|list|time|id|base|row|status|item|limit|result|window|open|public|str|path|location|field|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template|address)\\b", "score": -20 }, { "id": "SEM_VAR_FULLNAME_REDFLAGS", "description": "Reduce score for common non-secret words inside the variable name", "target": "NAME_NORMALIZED", - "pattern": "(item|limit|result|public|path|location|input|field|data|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template)", + "pattern": "(.*method.*|.*button.*|.*extra.*|.*ternal.*|.*parti.*|.*entry.*|.*time.*|.*status.*|.*expira.*|.*expect.*|.*lastuse.*|.*urlkey.*|.*signs.*|.*signal.*|item|meta|signature|limit|result|public|path|location|input|field|data|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template|address)", "score": -15 }, { "id": "SEM_VAR_HIGH_CONFIDENCE_SLICES_1", "description": "Critical confidence: api key, auth token, etc", "target": "NAME_SPACED", - "pattern": "\\b(csrf|api|access|refresh|auth|oauth|site|app)\\s+(key|token|secret)\\b", + "pattern": "\\b(csrf|api|access|refresh|meta|auth|oauth|site|app|priv)\\s+(key|token|secret)\\b", "score": 25 }, { "id": "SEM_VAR_HIGH_CONFIDENCE_FULLNAME_1", "description": "Critical confidence: api key, auth token, etc", "target": "NAME_NORMALIZED", - "pattern": "(csrf|site|api|access|refresh|auth|oauth|site|app)(key|token|secret)", - "score": 16 + "pattern": "(csrf|site|api|access|refresh|auth|oauth|site|app|sign)(key|token|secret)", + "score": 20 }, { "id": "SEM_VAR_HIGH_CONFIDENCE_SLICES_2", "description": "Critical confidence: password, pwd, private key", "target": "NAME_SPACED", - "pattern": "(oauth|sitekey|passw|pwd|secret)|private\\s+key\\b", + "pattern": "(oauth|sitekey|passw|pwd|secret|\\bsign)|private\\s+key\\b", "score": 15 }, { "id": "SEM_VAR_HIGH_CONFIDENCE_FULLNAME_2", "description": "Critical confidence: password, pwd, private key", "target": "NAME_NORMALIZED", - "pattern": "(authke|oauth|passw|pwd|secret|rivateke|cesstoke)", + "pattern": "(authke|oauth|passw|pwd|secret|privke|rivateke|cesstoke)", "score": 10 }, { @@ -71,6 +71,13 @@ "pattern": "(\\btoken\\b)", "score": 8 }, + { + "id": "SEM_VAR_KEY_AS_FINAL_PART_OF_NAME", + "description": "Key as last slice", + "target": "NAME_SPACED", + "pattern": "^[a-zA-Z0-9_ ]{3,} (?:key|token)$", + "score": 10 + }, { "id": "SEM_VAR_TOKEN_AS_NAME", "description": "", @@ -83,8 +90,9 @@ "description": "Key in a variable name slice", "target": "NAME_SPACED", "pattern": "\\bkey\\b", - "score": 5 + "score": 3 }, + { "id": "SEM_VAR_KEY_AS_NAME", "description": "Only key", diff --git a/deepsecrets/scan_modes/cli.py b/deepsecrets/scan_modes/cli.py index 8574d26..126cb40 100644 --- a/deepsecrets/scan_modes/cli.py +++ b/deepsecrets/scan_modes/cli.py @@ -14,6 +14,7 @@ from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.tokenizers.cheap_var_search import CheapVarSearchTokenizer from deepsecrets.core.tokenizers.full_content import FullContentTokenizer from deepsecrets.core.tokenizers.lexer import LexerTokenizer from deepsecrets.core.utils.lifecycle_hooks import JobLifecycleHooks @@ -59,6 +60,7 @@ def __finalize(result: PerFileAnalysisResult): if bundle.benchmarking_mode is True: result._file = file + result.processing_time_seconds = int((lifecycle.end_ts - lifecycle.start_ts).total_seconds()) result.errors = get_error_list() return result @@ -93,6 +95,7 @@ def __finalize(result: PerFileAnalysisResult): file_analyzer.attach_global_task_reporter(task_reporter=task_reporter, task_id=task_id) fct = FullContentTokenizer() + cheap_var_search = CheapVarSearchTokenizer() lex = LexerTokenizer(deep_token_inspection=True) regex_engine = RegexEngine( @@ -116,7 +119,7 @@ def __finalize(result: PerFileAnalysisResult): semantic_engine = SemanticEngine( regex_engine, ruleset=bundle.rulesets.get(VariableScoringRulesetBuilder.ruleset_name, []) ) - file_analyzer.add_engine(semantic_engine, [lex]) + file_analyzer.add_engine(semantic_engine, [lex, cheap_var_search]) try: result.findings = file_analyzer.process() diff --git a/deepsecrets/utils.py b/deepsecrets/utils.py new file mode 100644 index 0000000..ebe9ae4 --- /dev/null +++ b/deepsecrets/utils.py @@ -0,0 +1,20 @@ +import multiprocessing +import signal + + +def handle_sigint(signum, frame): + raise KeyboardInterrupt() + + +def setup_interrupts(): + signal.signal(signal.SIGINT, handle_sigint) + + +def setup_interrupts_for_subprocess(): + if multiprocessing.current_process().name == 'MainProcess': + return + + try: + signal.signal(signal.SIGINT, signal.SIG_IGN) + except ValueError: + pass diff --git a/tests/case_helpers.py b/tests/case_helpers.py index 343adc1..86efe8c 100644 --- a/tests/case_helpers.py +++ b/tests/case_helpers.py @@ -4,6 +4,7 @@ from deepsecrets.core.model.file import File from deepsecrets.core.model.finding import Finding from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.tokenizers.cheap_var_search import CheapVarSearchTokenizer from deepsecrets.core.tokenizers.itokenizer import Tokenizer from deepsecrets.core.tokenizers.lexer import LexerTokenizer from deepsecrets.core.utils.file_analyzer import FileAnalyzer @@ -27,6 +28,14 @@ def semantic_case(file: File): return run(file, engine, tokenizer) +def semantic_case_with_cheap_var_search(file: File): + builder = VariableScoringRulesetBuilder() + builder.with_rules_from_file(get_path_inside_package('rules/variable_scoring_rules.json')) + engine = SemanticEngine(ruleset=builder.rules) + tokenizer = CheapVarSearchTokenizer() + return run(file, engine, tokenizer) + + def regex_case(tokenizer: Tokenizer, engine: RegexEngine, file: File): return run(file, engine, tokenizer) @@ -34,3 +43,8 @@ def regex_case(tokenizer: Tokenizer, engine: RegexEngine, file: File): def variable_detection_case(tokenizer: LexerTokenizer, file: File, post_filter=False): tokenizer.tokenize(file, post_filter=post_filter) return tokenizer.get_variables(), tokenizer.lexer, tokenizer.tokens + + +def cheap_variable_search_case(tokenizer: CheapVarSearchTokenizer, file: File, post_filter=False): + tokenizer.tokenize(file) + return tokenizer.get_variables(), None, tokenizer.tokens diff --git a/tests/conftest.py b/tests/conftest.py index 2195e8e..bf28bbf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.tokenizers.cheap_var_search import CheapVarSearchTokenizer from deepsecrets.core.tokenizers.full_content import FullContentTokenizer from deepsecrets.core.tokenizers.lexer import LexerTokenizer from deepsecrets.core.tokenizers.per_line import PerLineTokenizer @@ -76,3 +77,8 @@ def full_content_tokenizer(): @pytest.fixture def per_line_tokenizer(): yield PerLineTokenizer() + + +@pytest.fixture +def cheap_var_search_tokenizer(): + yield CheapVarSearchTokenizer() diff --git a/tests/core/cohesive/test_specific_cases.py b/tests/core/cohesive/test_specific_cases.py index 3fbf534..f60f2ef 100644 --- a/tests/core/cohesive/test_specific_cases.py +++ b/tests/core/cohesive/test_specific_cases.py @@ -8,7 +8,7 @@ @pytest.mark.fixture_file_path('cases/inline_yaml_inside_yaml_inside_markdown.md') -def test_6(file: File): +def test_inline_yaml_inside_yaml_inside_markdown(file: File): findings, tokens, variables = semantic_case(file) assert len(variables) == 7 @@ -16,29 +16,41 @@ def test_6(file: File): @pytest.mark.fixture_file_path('cases/inline_yaml_inside_yaml.yaml') -def test_7(file: File, lexer_tokenizer: LexerTokenizer): +def test_inline_yaml_inside_yaml(file: File, lexer_tokenizer: LexerTokenizer): vars, _, tokens = variable_detection_case(lexer_tokenizer, file) assert len(vars) == 7 @pytest.mark.fixture_file_path('cases/code_in_markdown_with_lang_labels.md') -def test_8(file: File, lexer_tokenizer: LexerTokenizer): +def test_code_in_markdown_with_lang_labels(file: File, lexer_tokenizer: LexerTokenizer): vars, _, tokens = variable_detection_case(lexer_tokenizer, file) assert 1 == 1 @pytest.mark.fixture_file_path('cases/tricky_secrets.min.js') -def test_9(file: File): +def test_tricky_secrets(file: File): - findings, tokens, variables = semantic_case(file) - assert len(findings) == 4 + findings, _, _ = semantic_case(file) + assert len(findings) == 5 @pytest.mark.fixture_file_path('1.pem') -def test_10(file: File, full_content_tokenizer: FullContentTokenizer, regex_engine: RegexEngine): +def test_1_pem(file: File, full_content_tokenizer: FullContentTokenizer, regex_engine: RegexEngine): findings, tokens, variables = regex_case( tokenizer=full_content_tokenizer, engine=regex_engine, file=file, ) assert 1 == 1 + + +@pytest.mark.fixture_file_path('4.json') +def test_4_json(file: File): + findings, tokens, variables = semantic_case(file) + assert len(findings) == 1 + + +@pytest.mark.fixture_file_path('cases/js_in_html.html') +def test_5_jsinhtml(file: File): + findings, tokens, variables = semantic_case(file) + assert len(findings) == 1 diff --git a/tests/core/engines/regex/test_regex.py b/tests/core/engines/regex/test_regex.py index ebd8276..5586bda 100644 --- a/tests/core/engines/regex/test_regex.py +++ b/tests/core/engines/regex/test_regex.py @@ -8,7 +8,7 @@ @pytest.mark.fixture_file_path('regex_checks.txt') def test_1(file: File, regex_engine: RegexEngine): - findings, tokens, variables = regex_case( + findings, _, _ = regex_case( tokenizer=FullContentTokenizer(), engine=regex_engine, file=file, @@ -22,13 +22,12 @@ def test_1(file: File, regex_engine: RegexEngine): assert findings[4].final_rule.id == 'S3' assert findings[5].final_rule.id == 'S4' assert findings[6].final_rule.id == 'S5' - assert findings[7].final_rule.id == 'S19' - assert findings[8].final_rule.id == 'S19' - assert findings[9].final_rule.id == 'S19' - assert findings[9].detection == 'sneakypass' + assert findings[7].final_rule.id == 'S19' + assert findings[7].detection == 'sneakypass' - assert findings[10].final_rule.id == 'S19' + assert findings[8].final_rule.id == 'S19' + assert findings[8].detection == 'ridCNWnbTpavfVuJvWmS' @pytest.mark.fixture_file_path('extless/radius') @@ -52,3 +51,14 @@ def test_go_7(file: File, regex_engine: RegexEngine): ) assert len(findings) == 0 + + +@pytest.mark.fixture_file_path('cases/private_keys.txt') +def test_private_keys(file: File, regex_engine: RegexEngine): + findings, tokens, variables = regex_case( + tokenizer=FullContentTokenizer(), + engine=regex_engine, + file=file, + ) + + assert len(findings) == 2 diff --git a/tests/core/engines/semantic/test_semantic.py b/tests/core/engines/semantic/test_semantic.py index f2744b2..e56309f 100644 --- a/tests/core/engines/semantic/test_semantic.py +++ b/tests/core/engines/semantic/test_semantic.py @@ -2,7 +2,7 @@ from deepsecrets.core.model.file import File from deepsecrets.core.model.token import SemanticType -from tests.case_helpers import semantic_case +from tests.case_helpers import semantic_case, semantic_case_with_cheap_var_search @pytest.mark.fixture_file_path('4.py') @@ -27,10 +27,9 @@ def test_json_2(file: File): assert tokens[1].semantic.type == SemanticType.VARIABLE assert tokens[1].semantic.name == 'accessToken' - assert len(findings) == 3 + assert len(findings) == 2 assert findings[0].rules[0].name == 'Entropy+Var naming' assert findings[1].rules[0].name == 'Entropy+Var naming' - assert findings[2].rules[0].name == 'Var naming' @pytest.mark.fixture_file_path('1.toml') @@ -67,10 +66,10 @@ def test_html_1(file: File): assert len(findings) == 0 -@pytest.mark.fixture_file_path('5_1.min.js') +@pytest.mark.fixture_file_path('cases/tricky_secrets.min.js') def test_minjs_5_1(file: File): findings, tokens, vars = semantic_case(file) - assert len(findings) == 2 + assert len(findings) == 5 @pytest.mark.fixture_file_path('3.html') @@ -79,7 +78,7 @@ def test_html_3(file: File): assert len(findings) == 1 -@pytest.mark.fixture_file_path('edge_cases/code_in_markdown.md') +@pytest.mark.fixture_file_path('cases/code_in_markdown.md') def test_ec_code_in_markdown(file: File): findings, tokens, vars = semantic_case(file) assert len(findings) == 2 @@ -88,13 +87,13 @@ def test_ec_code_in_markdown(file: File): @pytest.mark.fixture_file_path('8.go') def test_go_8(file: File): findings, tokens, vars = semantic_case(file) - assert len(findings) == 2 + assert len(findings) == 1 @pytest.mark.fixture_file_path('1.go') def test_go_1(file: File): findings, tokens, vars = semantic_case(file) - assert len(findings) == 2 + assert len(findings) == 1 @pytest.mark.fixture_file_path('3.conf') @@ -102,3 +101,9 @@ def test_conf_3(file: File): # TODO: HOCON findings, tokens, vars = semantic_case(file) assert len(findings) == 1 + + +@pytest.mark.fixture_file_path('cheap_var_detector_cases.txt') +def test_with_cheap_var_search(file: File): + findings, tokens, vars = semantic_case_with_cheap_var_search(file) + assert len(findings) == 2 diff --git a/tests/core/helpers/test_variable_evaluator.py b/tests/core/helpers/test_variable_evaluator.py index 510cb2b..acafab9 100644 --- a/tests/core/helpers/test_variable_evaluator.py +++ b/tests/core/helpers/test_variable_evaluator.py @@ -8,6 +8,7 @@ def test_1(variable_scoring_rules): result: EvaluationResult = ve.evaluate(ctx) assert result.is_dangerous is True and result.nonsence_value_score >= 0.5 + assert result.export_confidence >= 7 def test_2(variable_scoring_rules): @@ -19,7 +20,7 @@ def test_2(variable_scoring_rules): ) ve = VariableEvaluator(variable_scoring_rules) result: EvaluationResult = ve.evaluate(ctx) - assert result.is_dangerous is True and result.nonsence_value_score < 0.5 + assert result.is_dangerous is False and result.nonsence_value_score < 0.5 def test_3(variable_scoring_rules): @@ -32,6 +33,7 @@ def test_3(variable_scoring_rules): ve = VariableEvaluator(variable_scoring_rules) result: EvaluationResult = ve.evaluate(ctx) assert result.is_dangerous is True and result.nonsence_value_score > 0.5 + assert result.export_confidence >= 6 def test_4(variable_scoring_rules): @@ -93,3 +95,103 @@ def test_8(variable_scoring_rules): ve = VariableEvaluator(variable_scoring_rules) result: EvaluationResult = ve.evaluate(ctx) assert result.is_dangerous is True and result.nonsence_value_score >= 0.5 + assert result.export_confidence <= 6 + + +def test_9(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='savingStatusKey', + value='saving', + filepath='2.conf', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False + + +def test_10(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='sporkprivkey', + value='cW2YM2xaeCaebfpKguBahUAgEzLXgSserWRuD29kSyKHq1TTgwRQ', + filepath='2.py', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 7 + + +def test_11(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='client_secret', + value='5846d428b5340812b76c9637eceaee979340b922', + filepath='1.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 7 + + +def test_12(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='minisign_key', + value='YDXm6SYJNH9p53tsFljV4PgA51ANWwcDbjUZJo1JIT0XAhSu73F7NMV3', + filepath='1.py', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 7 + + +def test_13(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='borderDesign', + value='headline', + filepath='1.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False + + +def test_14(variable_scoring_rules): + # input_token: MaxRecords + ctx = Context( + name='VSMSignalKanbanBlock', + value='shape=triangle;direction=south;anchorPointDirection=0', + filepath='1.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is False + + +def test_15(variable_scoring_rules): + ctx = Context( + name='bugsnag_key', + value='ae7bc49d1285848342342bb5c321a2cf', + filepath='1.min.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 4 + + +def test_16(variable_scoring_rules): + ctx = Context( + name='SLOBS_STREAM_KEY', + value='live_137546668_M4qFRbcNbYwEzVP5Ljgrexq2lZ5BX6', + filepath='1.js', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 8 diff --git a/tests/core/model/test_token.py b/tests/core/model/test_token.py index 6094f90..0142a85 100644 --- a/tests/core/model/test_token.py +++ b/tests/core/model/test_token.py @@ -32,8 +32,8 @@ def test_semantic_token(file: File): token.set_type(['Variable']) variable = Variable() - variable.name = token - variable.value = token + variable.name_token = token + variable.value_token = token token.semantic = Semantic(type=SemanticType.VARIABLE, payload=variable) diff --git a/tests/core/tokenizers/lexer/variable_detection/test_js.py b/tests/core/tokenizers/lexer/variable_detection/test_js.py index f39303d..c95d32e 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_js.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_js.py @@ -5,42 +5,6 @@ from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_js_3(): - path = 'tests/fixtures/3.js' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_jsx_1(): - path = 'tests/fixtures/1.jsx' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_jsx_2(): - path = 'tests/fixtures/2.jsx' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_jsx_3(): - path = 'tests/fixtures/3.jsx' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_js_4(): - path = 'tests/fixtures/4.js' - return File(path=path, relative_path=path) - - -@pytest.fixture(scope='module') -def file_minjs_5_1(): - path = 'tests/fixtures/5_1.min.js' - return File(path=path, relative_path=path) - - @pytest.mark.fixture_file_path('3.js') def test_1(file: File, lexer_tokenizer: LexerTokenizer): variables, lexer, _ = variable_detection_case(lexer_tokenizer, file) @@ -75,8 +39,14 @@ def test_5_js(file: File, lexer_tokenizer: LexerTokenizer): assert len(variables) == 0 -@pytest.mark.fixture_file_path('5_1.min.js') -def test_minjs_5_1(file, lexer_tokenizer): +@pytest.mark.fixture_file_path('cases/tricky_secrets.min.js') +def test_6_minjs_5_1(file, lexer_tokenizer): tokens = lexer_tokenizer.tokenize(file, post_filter=True) variables = lexer_tokenizer.get_variables(tokens) - assert len(variables) == 17 + assert len(variables) == 27 + + +@pytest.mark.fixture_file_path('5.js') +def test_7_js(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, tokens = variable_detection_case(lexer_tokenizer, file, post_filter=False) + assert len(variables) == 5 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_json.py b/tests/core/tokenizers/lexer/variable_detection/test_json.py new file mode 100644 index 0000000..c0ef90c --- /dev/null +++ b/tests/core/tokenizers/lexer/variable_detection/test_json.py @@ -0,0 +1,11 @@ +import pytest + +from deepsecrets.core.model.file import File +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case + + +@pytest.mark.fixture_file_path('5.json') +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 2 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py b/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py index f14c2f3..eae8394 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_markdown_with_code_blocks.py @@ -5,7 +5,7 @@ from tests.case_helpers import variable_detection_case -@pytest.mark.fixture_file_path('1_markdown.txt') +@pytest.mark.fixture_file_path('cases/markdown_with_yaml.txt') def test_1(file: File, lexer_tokenizer: LexerTokenizer): variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 3 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_py.py b/tests/core/tokenizers/lexer/variable_detection/test_py.py index dc2c105..b73c7bf 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_py.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_py.py @@ -37,4 +37,4 @@ def test_4(file: File, lexer_tokenizer: LexerTokenizer): @pytest.mark.fixture_file_path('5.py') def test_5(file: File, lexer_tokenizer: LexerTokenizer): variables, _, _ = variable_detection_case(lexer_tokenizer, file) - assert len(variables) == 2 + assert len(variables) == 5 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py b/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py new file mode 100644 index 0000000..0c5a40c --- /dev/null +++ b/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py @@ -0,0 +1,13 @@ +import pytest + +from deepsecrets.core.model.file import File +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case + + +@pytest.mark.fixture_file_path( + 'problem_files/dmpe-rbitly_5e7b14925d70ccc7f59a05e4b5a398c4acce6e76_man-link_Metrics_EncodersByCount.Rd' +) +def test_1(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 1 diff --git a/tests/core/tokenizers/test_cheap_var_search.py b/tests/core/tokenizers/test_cheap_var_search.py new file mode 100644 index 0000000..d2418d3 --- /dev/null +++ b/tests/core/tokenizers/test_cheap_var_search.py @@ -0,0 +1,10 @@ +import pytest + +from deepsecrets.core.model.file import File +from deepsecrets.core.tokenizers.cheap_var_search import CheapVarSearchTokenizer + + +@pytest.mark.fixture_file_path('cheap_var_detector_cases.txt') +def test_1(file: File, cheap_var_search_tokenizer: CheapVarSearchTokenizer): + tokens = cheap_var_search_tokenizer.tokenize(file=file) + assert len(tokens) == 4 diff --git a/tests/fixtures/4.json b/tests/fixtures/4.json new file mode 100644 index 0000000..e3369e3 --- /dev/null +++ b/tests/fixtures/4.json @@ -0,0 +1,38 @@ +[ + { + "company": "", + "description": { + "ENGLISH": "Co-fondateur et Product Owner de Logmatic.io", + "FRENCH": "Co-fondateur et Product Owner de Logmatic.io" + }, + "email": null, + "emailHash": "6e7c4baafac736c922ba2c3274d28ff0", + "firstname": "Renaud", + "lastname": "Boutet", + "legacyId": 5922, + "links": [], + "login": "Renaud", + "photoUrl": null, + "role": "USER", + "token": "8fc3344199b6", + "tokenExpiration": "2018-03-03T17:17:35.524" + }, + { + "company": "", + "description": { + "ENGLISH": "Co-Fondateur & CEO de Logmatic.io", + "FRENCH": "Co-Fondateur & CEO de Logmatic.io" + }, + "email": null, + "emailHash": "e7a279c50ff3efb9efdaa17d55e4e772", + "firstname": "Amirhossein", + "lastname": "Malekzadeh", + "legacyId": 5932, + "links": [], + "login": "Amirhossein", + "photoUrl": null, + "role": "USER", + "token": "empty-token", + "tokenExpiration": "2018-03-03T17:17:35.524" + } +] \ No newline at end of file diff --git a/tests/fixtures/5.js b/tests/fixtures/5.js new file mode 100644 index 0000000..036c851 --- /dev/null +++ b/tests/fixtures/5.js @@ -0,0 +1,39 @@ + +qm.staticData.appSettings = { + "appDesign": { + "clientSecret": "AjXHyGVvBljV9V4hIcuaBs6c1XggzB38", + "privateConfig": { + "bugsnag_key": "hl7ij49k1285848342342ii5j321h2jm", + "FOURSQUARE_CLIENT_SECRET": "CBWWFWQFQXV04EFUN5KZKQGY2IX0EDD5VDJLLFAMVVRVF1WF", + } + } +} + +const client = algoliasearch('user', '7f4518df25cf869cee323bf312f02c89'); + + +await setFormInput( +t, +'Stream key', +process.env.SLOBS_STREAM_KEY || 'live_147956788_EzVP5LjgcNbYwexq2lZrM4qFRb5BX6' +); + + +var promise = jsonp('http://dev.virtualearth.net/REST/v1/Locations', { + parameters : { + query : that._searchText, + key : 'AkMnCOd4RF1U7D7qgdBz3Fk1aJB3rgCCI_DO841suDGxqOg0SMICTE8Ivy5HhAf5' + + }, + callbackParameterName : 'jsonp' +}); + +//Options +this.options = { + api_key_flickr: "f2cc870b4d233dd0a5bfe73fd0d64ef0", + api_key_googlemaps: "AIzaSyB9dW8e_iRrATFa8g24qB6BDBGdkrLDZYI", + api_key_embedly: "", // ae2da610d1454b66abdf2e6a4c44026d + credit_height: 0, + caption_height: 0, + background: 0 // is background media (for slide) +}; diff --git a/tests/fixtures/5.json b/tests/fixtures/5.json new file mode 100644 index 0000000..acda5ee --- /dev/null +++ b/tests/fixtures/5.json @@ -0,0 +1,11 @@ +[{ + "id_knwKB": 5, + "m_key": "B136:Desig=16", + "m_value": "Delta(1940) --> Delta(1232) eta" + }, +{ + "id_knwKB": 5, + "kkk": "B136:Desig=16", + "m_value": "Delta(1940) --> Delta(1232) eta" + } +] \ No newline at end of file diff --git a/tests/fixtures/5.py b/tests/fixtures/5.py index 6c0bb2a..778408b 100644 --- a/tests/fixtures/5.py +++ b/tests/fixtures/5.py @@ -2,11 +2,31 @@ def create_config(nscfg_port, admin_username, auth_token): config['auth']['admins'] = [admin_username] config['auth']['internal_auth']['token'] = auth_token config['auth']['fma']['enable'] = True - config['auth']['fma']['secret'] = 'bAicxJVa5uVY7MjDlapthw' - config['auth']['fma']['self_id'] = 1000501 + config['auth']['fma']['secret'] = 'bAicxJVa5uVY7MjDlapthw' + config['auth']['fma']['self_id'] = 1000501 config['auth']['fma']['allowed_users_ids'] = [1] config['auth']['fma']['localhost_port'] = _get_vmagt_port() local_hbf_port = os.environ['RECIPE_HBF_PORT'] config['hbf_macroses']['endpoint'] = f'http://localhost:{local_hbf_port}' - db_pass = "nacc6opq" \ No newline at end of file + db_pass = "nacc6opq" + + +WS_KEY = b"bAicxJVa5uVY7MjDlapthw" + +private_keys = ["bAicxJVa5uVY7MjDlapthw", "JtVaVYhw7MjD5ulap"] + + +def test_wallet_create_uncompressed_masterkey(self): + wlt = wallet_create_or_open( + 'uncompressed_test', + keys='68vBWcBndYGLpd4KmeNTk1gS1A71zyDX6uVQKCxq6umYKyYUav5', + network='bitcoinlib_test', + databasefile=DATABASEFILE_UNITTESTS, + ) + wlt.get_key() + wlt.utxos_update() + self.assertIsNone(wlt.sweep('216xtQvbcG4o7Yz33n7VCGyaQhiytuvoqJY').error) + + +_FACEBOOK_SECRET = os.getenv('FACEBOOK_APP_SECRET', 'aA12bB34cC56dD78eE90fF12aA34bB56').encode('ascii', 'ignore') diff --git a/tests/fixtures/cases/js_in_html.html b/tests/fixtures/cases/js_in_html.html new file mode 100644 index 0000000..a0c2fbc --- /dev/null +++ b/tests/fixtures/cases/js_in_html.html @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + free CDN by cdn.xgqfrms.xyz + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/fixtures/cases/private_keys.txt b/tests/fixtures/cases/private_keys.txt new file mode 100644 index 0000000..c3eab6c --- /dev/null +++ b/tests/fixtures/cases/private_keys.txt @@ -0,0 +1,71 @@ +# Case 1: MATCH + +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAoAsMPt+4kuIG6vKyw9r3+OuZrVBee/2vDdVetW+Js5dTlgrJ +aghWWn3doGmKlEjqh7E4UTa+t2Jd6w8RSLnyHNJ/HpVhMG0M07MF6FMfILtDrrt8 +ZX7eDVt8sx5gCEpYI+XG8Y07Ga9i3Hiczt+fu6HYwu96HggmG2pqkOrn3iGfqBvV +NpscufUCgYEAxhgPfcMDy2v3nL6KtkgYjdcOyRvsAF50QRbEa8ldO+87IoMDD/Oe +osFERJ5hhyyEO78QnaLVegnykiw5DWEF02RKMhD/4XU+1UYVhY0wJjKQIBadsufB +2dnaKjvwzUhPh5BrBqNHl/FXwNCRDiYqXa79eWCPC9OFbZcUWWq70s8CgYEAztOI +61zRfmXJ7f70GgYbHg+GA7IrsAcsGRITsFR82Ho0lqdFFCxz7oK8QfL6bwMCGKyk +nzk+twh6hhj5UNp18KN8wktlo02zTgzgemHwaLa2cd6xKgmAyuPiTgcgnzt5LVNG +YVFJzSZYe7e4c1PeEs0xYcrA4k+apyGsMtpef8vRUrNicRLc7dAcvfhtgt2DXEZ2 +d72t/CR4ygtUvPXzisaTPW0G7OWAheCloqvTIIPQIjR8htFxGTz02STVXfnhnJ0Z +k8KhqKF2v1SQvIYxsZU7jaDgl5i3zpeh58cYOwIDAQABAoIBABZUJEO7Y91+UnfC +H6XKrZEZkcnH7j6/UIaOD9YhdyVKxhsnax1zh1S9vceNIgv5NltzIsfV6vrb6v2K +Dx/F7Z0O0zR5o+MlO8ZncjoNKskex10gBEWG00Uqz/WPlddiQ/TSMJTv3uCBAzp+ +S2Zjdb4wYPUlgzSgb2ygxrhsRahMcSMG9PoX6klxMXFKMD1JxiY8QfAHahPzQXy9 +F7COZ0fCVo6BE+MqNuQ8tZeIxu8mOULQCCkLFwXmkz1FpfK/kNRmhIyhxwvCS+z4 +JuErW3uXfE64RLERiLp1bSxlDdpvRO2R41HAoNELTsKXJOEt4JANRHm/CeyA5wsh +FOjIWkLwSlpkDTl7ZzY2QSy7t+mq5d750fpIrtUCgYBWXZUbcpPL88WgDB7z/Bjg +dlvW6JqLSqMK4b8/cyp4AARbNp12LfQC55o5BIhm48y/M70tzRmfvIiKnEc/gwaE +NJx4mZrGFFURrR2i/Xx5mt/lbZbRsmN89JM+iKWjCpzJ8PgIi9Wh9DIbOZOUhKVB +9RJEAgo70LvCnPTdS0CaVwKBgDJW3BllAvw/rBFIH4OB/vGnF5gosmdqp3oGo1Ik +jipmPAx6895AH4tquIVYrUl9svHsezjhxvjnkGK5C115foEuWXw0u60uiTiy+6Pt +2IS0C93VNMulenpnUrppE7CN2iWFAiaura0CY9fE/lsVpYpucHAWgi32Kok+ZxGL +WEttAoGAN9Ehsz4LeQxEj3x8wVeEMHF6OsznpwYsI2oVh6VxpS4AjgKYqeLVcnNi +TlZFsuQcqgod8OgzA91tdB+Rp86NygmWD5WzeKXpCOg9uA+y/YL+0sgZZHsuvbK6 +PllUgXdYxqClk/hdBFB7v9AQoaj7K9Ga22v32msftYDQRJ94xOI= +-----END RSA PRIVATE KEY----- + + + +# Case 2: MATCH + +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA1i7zmSGopddoujVy3OcmgTpRkqWOaupPgYhD3zAws080YuiK +h84Jcg+KzXWjunGn0vxrSPI0QDueJR2i03tEDBAtMZ0pvAsJ0gmXRdzGOc2uRzDm +fbS3y/JIufClO28OzjJ5AJkbc9XgRDeCDOFY2D375bCg2boPYmP7Iw0HVU3RQhcr +t+US27VQBRJF4cQ2CCyr0ZbdaPn41v+/A/qxF6ZPguyy+KoyQjCqK8iFArRQ48hJ +cR2pFx4hAoGBAP2uXIJAdAemrOunv2CWlUHI2iHj/kJ1AXRMpiT+eF0US9E6tipE +mL63HkUhiAs2nJnPi3RDxP+kAO2Z3anqjm1KCeGj+IYYZMavnkC8EVybv9lDwORy +e2O1bfRc/tGa341KmvXLbp8oVMIYIvKz2cZmHGJ4V4DTq8dTvmqoE4/VAoGBANgk +KWY5MJToZJJ5bV0mc2stmGt/IAZZPlKjKjrKVXKK5LktTZdGsq5dFbihwr4Xrp/7 +O5Sy4BH4n80bnmJUlLXRoOGrViMBCrGyBPgBfblE85Kv7esx8RQAFoFWev4Vuveo +6nGV98lPiwrhzy0jdK7qg+mcoJ2A1XIg5mMMXutNcTXzjmEZZunRkcerk8Rg2bX7 +vM7GjSNFMU7oE6dseXCgeukm4HZSCb3Nh2+eGu6jrG/QiH/q6ytsVHSVI+LnlBCD +MY52Nrvw3O9UoHwVZ/shXzfTBzXXE1j1xFApRBcXfzAd3Rp+4VWr18r8tzU+LVoQ +YGAaYXxFLnQxXo+TJlaueiThENlLWkPiJrHLnQIDAQABAoIBAQDMo/WKO1mKsdJA +R3HRr1ECgYEAobNQbQSLrSWZ1cozJbmNgRqqvxDNSEDi8LpXukOAw4pz1km7o3ob +hCy1ksfFzsp5glYqwZd/Bahk64u3mII+rKoYwYLrH2l2aFDmMbdTfQUycpQZyi3+ +VtGS1PFoKx9fSFDNHhR5ZhfasQcuKHYfeFfO2/DoOxQkNCI1y4I2huoZlQBG3Cay +64fV83AI7jTozkkLvoMNC+3iaBMeN3P3I+HuDmhOEL2lKVq/HKJFp+bPuW50EWPY +bOlzN+Zs0kygEMJJJxQDjCF9XzxarVPjVmKOjDyzqHRLAhsmbMyUhhgZtyj0dzSW +ILEeaEJknYRrOB48D6IqkB8VnFJyHUG8l+Za41adqRQNid0S5n50/+eYbjZpYCrA +SGmC2dhPZvRD6tOyEEJF5PZMvqxDcNRilc627HipAoGBAKzqrSQbyvtsIXKAZXLx +McwlnIp9XlLubo9Xr+iHjIPl0chMvN8S4wscxwVYVeNO1nABiI03pJCcugU7XFz2 +BR952EJ2AnFlL0w/aR+3Eh6OC7eM927Amlrc0JZAzXESoE8vC3F/uWfDlgK3cRr+ +fPM/pxl37i1iGzVDYAhTiQIBAoGAPW25nmXumsOZoc+E945wCywAP7z3mxZOEip9 +6LDexnnBDJws0w6OqW4k1kCov6kLIBTy4aPkucniwrm+T0l+n/Y807jOntfz3LT+ +7ucx6XIRlbNrVTuD6rjR6j52RFyaikvvyJz50PJwLkgHO3dGC6/VrP= +-----END RSA PRIVATE KEY----- + + +# Case 3: NOT MATCH +-BEGIN PRIVATE KEY-----\nMIIyourkey\n-----END PRIVATE KEY-----",
"client_email": "test@suppproject-id-1234567.iam.gserviceaccount.com",
"client_id": "113545814931671546333",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test%suppproject-id-1234567.iam.gserviceaccount.com"
}
  1. To provide credentials to the source, you can either: +Set an environment variable: +$ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/keyfile.json"

    or

    Set credential config in your source based on the credential json file. For example:

     credential:
project_id: project-id-1234567
private_key_id: "d0121d0000882411234e11166c6aaa23ed5d74e0"
private_key: "-----BEGIN PRIVATE KEY-----\nMIIyourkey\n-----END PRIVATE KEY- + + +# Case 4: NOT MATCH +-BEGIN PGP PRIVATE KEY BLOCK-----\r\n"),p.push(f(u)),p.push(i.default.encode(t)),p.push("\r\n=",d(h),"\r\n"),p.push("-----END PGP PRIVATE KEY BLOCK- \ No newline at end of file diff --git a/tests/fixtures/cases/tricky_secrets.min.js b/tests/fixtures/cases/tricky_secrets.min.js index ea64af5..4dec06e 100644 --- a/tests/fixtures/cases/tricky_secrets.min.js +++ b/tests/fixtures/cases/tricky_secrets.min.js @@ -23,4 +23,12 @@ t112.default.createElement("div", contextualSearch:!1 } } -) \ No newline at end of file +) + +qm.staticData.appSettings ={ + "appDesign": { + "clientSecret": "TcQArZOoUecO9O4aBvntUl6v1QzzsU38", + "privateConfig": { + "bugsnag_key": "ae7bc49d1285848342342bb5c321a2cf", + "FOURSQUARE_CLIENT_SECRET": "VUPPYPJYJQO04XYNG5DSDJZR2BQ0XWW5OWCEEYTFOOKOY1PY", + }}} \ No newline at end of file diff --git a/tests/fixtures/cheap_var_detector_cases.txt b/tests/fixtures/cheap_var_detector_cases.txt new file mode 100644 index 0000000..1e01769 --- /dev/null +++ b/tests/fixtures/cheap_var_detector_cases.txt @@ -0,0 +1,8 @@ +client_secret: '5846d428b5340812b76c9637eceaee979340b922' +client_secret = '5846d428b5340812b76c9637eceaee979340b922' + +"client_secret": "5846d428b5340812b76c9637eceaee979340b922" +'client_secret'= '5846d428b5340812b76c9637eceaee979340b922' + +https://www.youtube.com/watch?v=HHi19zvnyGM&key=5846d428b5340812b76c9637eceaee979340b922 +https://www.youtube.com/watch?key=5846d428b5340812b76c9637eceaee979340b922 \ No newline at end of file diff --git a/tests/fixtures/regex_checks.txt b/tests/fixtures/regex_checks.txt index 2894dae..36f7b1f 100644 --- a/tests/fixtures/regex_checks.txt +++ b/tests/fixtures/regex_checks.txt @@ -58,8 +58,8 @@ v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs -----END PGP PRIVATE KEY BLOCK----- # S19 -https://login:password@example.com -ftp://login:password@example.com +https://login:password@example.com # should not be found +ftp://login:password@example.com # should not be found amqp://login:$password@example.com amqp://login:${password}@example.com amqp://login:%password%@example.com @@ -67,6 +67,16 @@ amqp://login:%password%@example.com http://localhost:3001/@whatever/path/more http://user:sneakypass@localhost:3001/@whatever/path/more +ftp://login:testpassword@example.com # should not be found + +ftp://login:changeme@example.com # should not be found + + on PLAINTEXT://localhost:9092 123456terrence@igloo /usr/local/kafka15:51:25 𝜆 diff config/server.pro +http://user:*****@localhost:3001/@whatever/path/more + + +eing prematurely saved to database\n ([230e4a11](https://gitlab-ci-token:ridCNWnbTpavfVuJvWmS@git.sickrage.ca/SiCKRAGE/sickrage/commit/230e4a11b46145fc881186a3e + -eing prematurely saved to database\n ([230e4a11](https://gitlab-ci-token:ridCNWnbTpavfVuJvWmS@git.sickrage.ca/SiCKRAGE/sickrage/commit/230e4a11b46145fc881186a3e \ No newline at end of file +5Nprr13Wue677z5uvvlmLrvsMizL4rbbbuOVr3wlhw4dItyh2t8f \ No newline at end of file diff --git a/tests/generic_fixture_scans/test_run_full_scan.py b/tests/generic_fixture_scans/test_run_full_scan.py index d842989..5fe8e6d 100644 --- a/tests/generic_fixture_scans/test_run_full_scan.py +++ b/tests/generic_fixture_scans/test_run_full_scan.py @@ -27,7 +27,7 @@ def test_everything(config: Config) -> None: mode.progress_bar = Mock() mode.progress_bar.add_task.return_value = 0 mode.progress_bar.task_ids = [] - findings, errors = mode.run() + findings, errors, timings = mode.run() detections = [finding.detection for finding in findings] assert 'bAicxJVa5uVY7MjDlapthw' in detections From 36910cd4b3b1ff88d2fdf0047ae58b5dbb5a2154 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Mon, 25 May 2026 17:13:59 +0300 Subject: [PATCH 05/20] 2.0.0 RC2 --- deepsecrets/cli.py | 30 +- .../core/helpers/naturalness_scorer.py | 143 ++ .../core/helpers/variable_evaluator.py | 8 +- deepsecrets/core/model/finding.py | 2 +- .../core/model/rules/variable_scoring.py | 37 +- deepsecrets/core/model/semantic.py | 21 +- deepsecrets/core/model/tokenized_region.py | 22 + deepsecrets/core/modes/iscan_mode.py | 3 +- .../core/tokenizers/cheap_var_search.py | 12 +- .../helpers/semantic/deep_analyzer.py | 5 +- .../tokenizers/helpers/semantic/language.py | 5 + .../semantic/var_detection/detector.py | 4 +- .../helpers/semantic/var_detection/rules.py | 91 +- .../helpers/subfile_regions_helper.py | 76 +- .../core/tokenizers/helpers/type_stream.py | 1 + deepsecrets/core/tokenizers/itokenizer.py | 6 +- deepsecrets/core/tokenizers/lexer.py | 20 +- deepsecrets/core/ui/floating_header.py | 25 + deepsecrets/core/ui/progress_bar.py | 30 + deepsecrets/core/ui/time_remaining_column.py | 39 + deepsecrets/core/utils/file_analyzer.py | 7 + deepsecrets/core/utils/guess_filetype.py | 8 +- deepsecrets/core/utils/lexer_finder.py | 2 +- deepsecrets/core/utils/progress.py | 6 +- deepsecrets/rules/regexes.json | 40 +- deepsecrets/rules/value_scoring_model.json | 1 + deepsecrets/rules/variable_scoring_rules.json | 44 +- poetry.lock | 1302 +++++++++-------- pyproject.toml | 24 +- tests/case_helpers.py | 2 +- tests/core/cohesive/test_specific_cases.py | 12 +- tests/core/engines/regex/test_regex.py | 2 +- tests/core/engines/semantic/test_semantic.py | 8 +- tests/core/helpers/test_variable_evaluator.py | 34 + tests/core/model/test_variable_context.py | 8 + .../lexer/variable_detection/test_php.py | 12 +- .../lexer/variable_detection/test_r_rd.py | 1 + .../lexer/variable_detection/test_ruby.py | 10 + .../lexer/variable_detection/test_sh.py | 6 + .../core/tokenizers/test_cheap_var_search.py | 12 +- tests/fixtures/1.rb | 18 + tests/fixtures/2.php | 5 + tests/fixtures/3.sh | 15 + tests/fixtures/6.json | 18 + tests/fixtures/cheap_var_detector_cases.txt | 22 +- tests/fixtures/regex_checks.txt | 16 +- .../test_run_full_scan.py | 45 +- tests/output/test_sarif.py | 2 +- tests/scan_modes/test_cli_scan_mode.py | 5 +- 49 files changed, 1498 insertions(+), 769 deletions(-) create mode 100644 deepsecrets/core/helpers/naturalness_scorer.py create mode 100644 deepsecrets/core/ui/floating_header.py create mode 100644 deepsecrets/core/ui/progress_bar.py create mode 100644 deepsecrets/core/ui/time_remaining_column.py create mode 100644 deepsecrets/rules/value_scoring_model.json create mode 100644 tests/core/tokenizers/lexer/variable_detection/test_ruby.py create mode 100644 tests/fixtures/1.rb create mode 100644 tests/fixtures/2.php create mode 100644 tests/fixtures/3.sh create mode 100644 tests/fixtures/6.json diff --git a/deepsecrets/cli.py b/deepsecrets/cli.py index 14bdb5f..965f54e 100644 --- a/deepsecrets/cli.py +++ b/deepsecrets/cli.py @@ -16,11 +16,13 @@ from deepsecrets.core.rulesets.hashed_secrets import HashedSecretsRulesetBuilder from deepsecrets.core.rulesets.regex import RegexRulesetBuilder from deepsecrets.core.rulesets.variable_scoring import VariableScoringRulesetBuilder +from deepsecrets.core.ui.progress_bar import DSApplicationProgess +from deepsecrets.core.ui.time_remaining_column import SyncedTimeRemainingColumn from deepsecrets.core.utils.fs import get_abspath, get_path_inside_package from deepsecrets.core.utils.log import logger from deepsecrets.scan_modes.cli import CliScanMode -from rich.progress import SpinnerColumn, Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn +from rich.progress import SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn from rich.panel import Panel from rich.table import Table, Column from rich import box @@ -36,13 +38,14 @@ class ReturnCodes: FINDINGS_DETECTED = 66 -progress_bar = Progress( +overall_time_column = SyncedTimeRemainingColumn() +progress_bar = DSApplicationProgess( SpinnerColumn(), TextColumn("[progress.description]{task.description}", table_column=Column(max_width=60, no_wrap=True)), TextColumn("[bold blue]{task.fields[size]}"), BarColumn(bar_width=None), TaskProgressColumn('[progress.percentage]{task.percentage:>3.1f}%'), - TimeRemainingColumn(), + overall_time_column, TextColumn("[bold red]{task.fields[findings]}", justify="right"), TextColumn("[bold red]{task.fields[errors]}", justify="right"), console=console, @@ -50,6 +53,7 @@ class ReturnCodes: expand=True, speed_estimate_period=90, ) +overall_time_column.progress_instance = progress_bar class DeepSecretsCliTool: @@ -354,11 +358,10 @@ def start(self) -> int: # pragma: nocover mode = CliScanMode(config=config) console.line() - console.rule('Starting analysis', characters='—') - console.line() - mode.set_progress_bar(progress_bar) - progress_bar.start() + mode.set_progress_bar(progress_bar) + mode.progress_bar.set_start_time(startup_time) + mode.progress_bar.start() findings: List[Finding] errors: Dict[str, List[str]] @@ -373,7 +376,7 @@ def start(self) -> int: # pragma: nocover finding.file = None ''' - progress_bar.stop() + mode.progress_bar.stop() finish_time = datetime.now() report_path = get_abspath(config.output.path) @@ -381,9 +384,16 @@ def start(self) -> int: # pragma: nocover console.print('[bold green]Scanning finished successfully', justify='center') console.line() - console.rule('REPORT', characters='=') + console.rule('', characters='=') console.line() - table = Table(box=box.HORIZONTALS, show_header=False, row_styles=['blink'], style='dim', width=80) + table = Table( + title=Text('REPORT SUMMARY'), + box=box.HORIZONTALS, + show_header=False, + row_styles=['blink'], + style='dim', + width=80, + ) table.add_column() table.add_column(justify='right') table.add_row( diff --git a/deepsecrets/core/helpers/naturalness_scorer.py b/deepsecrets/core/helpers/naturalness_scorer.py new file mode 100644 index 0000000..d8016a9 --- /dev/null +++ b/deepsecrets/core/helpers/naturalness_scorer.py @@ -0,0 +1,143 @@ +import collections +import json +import math +import zlib + +from deepsecrets.core.utils.fs import get_path_inside_package + + +class NaturalnessScorer: + + def __init__(self, expected_elements=370000, false_positive_rate=0.01): + # Parameters for bloom filter + self.num_words = expected_elements + self.fp_rate = false_positive_rate + self.bit_size = int(-(self.num_words * math.log(self.fp_rate)) / (math.log(2) ** 2)) + self.num_hashes = int((self.bit_size / self.num_words) * math.log(2)) + self.bit_array = [0] * self.bit_size + + # Trigrams frequency dictionary + self.trigram_counts = collections.Counter() + self.total_trigrams = 0 + self.max_word_len = 0 + + self.min_log_prob = -15.0 + + def _get_hashes(self, item): + hashes = [] + for i in range(self.num_hashes): + salted = f"{item}{i}".encode("utf-8") + hashes.append(zlib.crc32(salted) % self.bit_size) + return hashes + + def _in_dictionary(self, word): + if len(word) < 2: + return False + for position in self._get_hashes(word): + if self.bit_array[position] == 0: + return False + return True + + @classmethod + def load_from_json(cls, filepath): + with open(filepath, "r", encoding="utf-8") as f: + model_data = json.load(f) + + instance = cls( + expected_elements=model_data["num_words"], + false_positive_rate=model_data["fp_rate"], + ) + + instance.bit_size = model_data["bit_size"] + instance.num_hashes = model_data["num_hashes"] + instance.max_word_len = model_data["max_word_len"] + instance.total_trigrams = model_data["total_trigrams"] + instance.trigram_counts = collections.Counter(model_data["trigram_counts"]) + + byte_data = bytes.fromhex(model_data["bit_array_hex"]) + bit_array = [] + for byte in byte_data: + for i in range(7, -1, -1): + bit = (byte >> i) & 1 + bit_array.append(bit) + + instance.bit_array = bit_array + return instance + + def _get_trigram_score(self, substring): + if not substring: + return 1.0 + + padded = f"^{substring}$" + total_score = 0.0 + count = 0 + + for i in range(len(padded) - 2): + trigram = padded[i : i + 3] + trig_count = self.trigram_counts.get(trigram, 0) + + if trig_count > 0: + prob = trig_count / self.total_trigrams + log_prob = math.log(prob) + else: + log_prob = self.min_log_prob + + total_score += log_prob + count += 1 + + if count == 0: + return 0.0 + + avg_log_prob = total_score / count + normalized = (avg_log_prob - self.min_log_prob) / (-3.0 - self.min_log_prob) + return max(0.0, min(1.0, normalized)) + + def calculate_score(self, string): + string = string.lower().strip() + if not string.isalpha() or len(string) == 0: + return 0.0 + + n = len(string) + + dp = [(0, [])] * (n + 1) + + for i in range(n): + max_j = min(n, i + self.max_word_len) + for j in range(i + 2, max_j + 1): + substring = string[i:j] + + if self._in_dictionary(substring): + word_len = len(substring) + current_coverage = dp[i][0] + word_len + if current_coverage > dp[j][0]: + dp[j] = (current_coverage, dp[i][1] + [(i, j)]) + + if dp[i][0] > dp[i + 1][0]: + dp[i + 1] = (dp[i][0], dp[i][1]) + + max_covered_chars, word_intervals = dp[n] + dict_score = max_covered_chars / n + + last_idx = 0 + leftover_trigram_scores = [] + + for start, end in word_intervals: + if start > last_idx: + garbage_chunk = string[last_idx:start] + leftover_trigram_scores.append(self._get_trigram_score(garbage_chunk)) + last_idx = end + + if last_idx < n: + garbage_chunk = string[last_idx:n] + leftover_trigram_scores.append(self._get_trigram_score(garbage_chunk)) + + if leftover_trigram_scores: + garbage_score = sum(leftover_trigram_scores) / len(leftover_trigram_scores) + else: + garbage_score = 1.0 + final_score = (dict_score * 0.8) + (garbage_score * 0.2) + + return round(final_score, 4) + + +naturalness_scorer = NaturalnessScorer.load_from_json(get_path_inside_package('rules/value_scoring_model.json')) diff --git a/deepsecrets/core/helpers/variable_evaluator.py b/deepsecrets/core/helpers/variable_evaluator.py index 3450290..3838798 100644 --- a/deepsecrets/core/helpers/variable_evaluator.py +++ b/deepsecrets/core/helpers/variable_evaluator.py @@ -98,10 +98,6 @@ def evaluate(self, variable: Union[Variable | Context]) -> EvaluationResult: naming_and_content_score = 0 matched_rules = [] - if len(context.value) <= 4: - matched_rules.append('SEM_INTRNL_VAL_LENGTH') - return EvaluationResult(total_score=-100, is_dangerous=False, matched_rules=matched_rules) - for rule in self.rules: fired = rule.match_by_context(context) if fired: @@ -126,7 +122,9 @@ def evaluate(self, variable: Union[Variable | Context]) -> EvaluationResult: matched_rules=matched_rules, ) - nonsense_value_score = self.calculate_nonsense_value_score(context.value_parts, context.value_normalized) + nonsense_value_score = ( + 1 - context.value_normalized_naturalness_score + ) # self.calculate_nonsense_value_score(context.value_parts, context.value_normalized) total_score = naming_and_content_score + entropy_score result = EvaluationResult( diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index 4737085..1e5e181 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -77,7 +77,7 @@ def __hash__(self) -> int: # pragma: nocover return hash(f'{self.file.path}{self.detection}{self.start_offset}{self.end_offset}') def get_id(self) -> int: - return int(str(abs(self.__hash__()))[:8]) + return int(str(abs(self.__hash__()))[:9]) def __eq__(self, other: Any) -> bool: if not isinstance(other, Finding): diff --git a/deepsecrets/core/model/rules/variable_scoring.py b/deepsecrets/core/model/rules/variable_scoring.py index 5dfae6e..80ff3aa 100644 --- a/deepsecrets/core/model/rules/variable_scoring.py +++ b/deepsecrets/core/model/rules/variable_scoring.py @@ -1,4 +1,8 @@ from enum import Enum +import operator +from typing import Dict, Optional + +from pydantic import model_validator from deepsecrets.core.model.rules.regex import RegexRule from deepsecrets.core.model.semantic import Context import regex as re @@ -10,6 +14,8 @@ class Target(str, Enum): VALUE = "VALUE" FILEPATH = "FILEPATH" # The file path VALUE_NORMALIZED = 'VALUE_NORMALIZED' + VALUE_LENGTH = 'VALUE_LENGTH' + VALUE_NORMALIZED_NATURALNESS_SCORE = 'VALUE_NORMALIZED_NATURALNESS_SCORE' target_to_fields = { @@ -18,12 +24,36 @@ class Target(str, Enum): Target.VALUE: 'value', Target.NAME_NORMALIZED: 'name_normalized', Target.VALUE_NORMALIZED: 'value_normalized', + Target.VALUE_LENGTH: 'value_length', + Target.VALUE_NORMALIZED_NATURALNESS_SCORE: 'value_normalized_naturalness_score', +} + +ops = { + "<=": operator.le, + ">=": operator.ge, + "==": operator.eq, + "!=": operator.ne, + "<": operator.lt, + ">": operator.gt, } class VariableScoringRule(RegexRule): score: int target: Target + method: Optional[str] = None + threshold: Optional[float] = None + + def _is_threshold_type(self): + return self.threshold is not None and self.method is not None + + @model_validator(mode='before') + @classmethod + def build_numeric(cls, values: Dict) -> Dict: + if 'method' not in values.keys() and 'threshold' not in values.keys(): + return values + + return values def _get_content_for_matching(self, context: Context): field = target_to_fields.get(self.target) @@ -31,7 +61,10 @@ def _get_content_for_matching(self, context: Context): def match_by_context(self, context: Context) -> bool: content = self._get_content_for_matching(context) - match = re.search(self.pattern, content) - if match is not None: + if self._is_threshold_type(): + match = ops.get(self.method)(content, self.threshold) + else: + match = re.search(self.pattern, content) + if match is not None and match is not False: return True return False diff --git a/deepsecrets/core/model/semantic.py b/deepsecrets/core/model/semantic.py index 09a672c..3b8447e 100644 --- a/deepsecrets/core/model/semantic.py +++ b/deepsecrets/core/model/semantic.py @@ -1,15 +1,27 @@ from dataclasses import dataclass, field from typing import List, Optional +from deepsecrets.core.helpers.naturalness_scorer import naturalness_scorer from deepsecrets.core.model.token import Token from deepsecrets.core.utils.string import StringUtils import regex as re - number_pattern = re.compile(r'\b\d+\b') hex_color = re.compile(r'#(?:[0-9a-fA-F]{3}){1,2}\b') +# TODO DS2.1: Migrate to this data structure +@dataclass +class SemanticAttributes: + raw_value: str + parts: List[str] = field(default_factory=list, repr=False) + normalized: str = field(default_factory=str, repr=False) + spaced: str = field(default_factory=str, repr=False) + length: int = field(default_factory=int, repr=False) + # is_url + # is_ + + @dataclass class Context: name: str @@ -19,16 +31,22 @@ class Context: name_parts: List[str] = field(default_factory=list, repr=False) name_normalized: str = field(default_factory=str, repr=False) name_spaced: str = field(default_factory=str, repr=False) + name_length: int = field(default_factory=int, repr=False) value_parts: List[str] = field(default_factory=list, repr=False) value_normalized: str = field(default_factory=str, repr=False) value_spaced: str = field(default_factory=str, repr=False) + value_length: int = field(default_factory=int, repr=False) + value_normalized_naturalness_score: float = field(default_factory=float, repr=False) def __post_init__(self): self.name_spaced, self.name_normalized, self.name_parts = self.normalize_punctuation(self.name) self.value_spaced, self.value_normalized, self.value_parts = self.normalize_punctuation( self.value, split_camel_case=False ) + self.name_length = len(self.name) + self.value_length = len(self.value) + self.value_normalized_naturalness_score = naturalness_scorer.calculate_score(self.value_normalized) def normalize_punctuation(self, string: str, split_camel_case=True): normalized = string.replace(' ', '_') @@ -44,6 +62,7 @@ def normalize_punctuation(self, string: str, split_camel_case=True): .replace('#', ' ') .replace('/', ' ') .replace('@', ' ') + .replace(',', '') ) parts = [] diff --git a/deepsecrets/core/model/tokenized_region.py b/deepsecrets/core/model/tokenized_region.py index f362d6c..837b5a6 100644 --- a/deepsecrets/core/model/tokenized_region.py +++ b/deepsecrets/core/model/tokenized_region.py @@ -8,5 +8,27 @@ @dataclass class TokenizedRegion: language: Language + tokens: List[Token] stream: str + + substitute_start_index: int + substitute_end_index: int + + def __hash__(self): + return hash((self.stream, self.substitute_start_index, self.substitute_end_index)) + + def __eq__(self, other: 'TokenizedRegion'): + if other.language != self.language: + return False + + if other.stream != self.stream: + return False + + if other.substitute_start_index != self.substitute_start_index: + return False + + if other.substitute_end_index != self.substitute_end_index: + return False + + return True diff --git a/deepsecrets/core/modes/iscan_mode.py b/deepsecrets/core/modes/iscan_mode.py index 09715a7..efb52ce 100644 --- a/deepsecrets/core/modes/iscan_mode.py +++ b/deepsecrets/core/modes/iscan_mode.py @@ -1,3 +1,4 @@ +from deepsecrets.core.ui.progress_bar import DSApplicationProgess from deepsecrets.utils import setup_interrupts_for_subprocess setup_interrupts_for_subprocess() @@ -64,7 +65,7 @@ class ScanMode: engines_enabled: Dict[Type, bool] active_task_reporter: DictProxy - progress_bar: ProgressBar + progress_bar: DSApplicationProgess file_results: List[AsyncResult] file_jobs: Dict[int, FileJob] diff --git a/deepsecrets/core/tokenizers/cheap_var_search.py b/deepsecrets/core/tokenizers/cheap_var_search.py index 7c76466..3fc2857 100644 --- a/deepsecrets/core/tokenizers/cheap_var_search.py +++ b/deepsecrets/core/tokenizers/cheap_var_search.py @@ -3,14 +3,24 @@ from deepsecrets.core.model.file import File from deepsecrets.core.model.semantic import Variable from deepsecrets.core.model.token import Semantic, SemanticType, Token +from deepsecrets.core.tokenizers.helpers.semantic.language import Language +from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import CheapVariableDetector from deepsecrets.core.tokenizers.helpers.semantic.var_detection.rules import CheapVariableDetectionRules from deepsecrets.core.tokenizers.itokenizer import Tokenizer +from deepsecrets.core.utils.log import logger class CheapVarSearchTokenizer(Tokenizer): def tokenize(self, file: File) -> List[Token]: - rules = CheapVariableDetectionRules.rules + language: Language = Language.ANY + if file.extension is not None: + try: + language = Language.from_text(file.extension) + except Exception as e: + logger.exception(e) + + rules: List[CheapVariableDetector] = CheapVariableDetectionRules.for_language(language) vars: List[Variable] = [] for rule in rules: vars.extend(rule.match(file.content)) diff --git a/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py b/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py index bf8a78b..f64b8bb 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/deep_analyzer.py @@ -20,11 +20,13 @@ class DeepAnalyzer: regions: List[TokenizedRegion] deep_inspection: bool post_filter: bool + silent_regions: List def __init__(self, regions: List[TokenizedRegion], post_filter: bool, deep_inspection: bool = True) -> None: self.regions = regions self.deep_inspection = deep_inspection self.post_filter = post_filter + self.silent_regions = [] def get_final_tokens(self): tokens = [] @@ -32,7 +34,7 @@ def get_final_tokens(self): if self.deep_inspection is True: # type: ignore self.run() - [tokens.extend(region.tokens) for region in self.regions] + [tokens.extend(region.tokens) for region in sorted(self.regions, key=lambda x: x.substitute_start_index)] return tokens def run(self): @@ -64,6 +66,7 @@ def analyze_token_sequence(self, language: Language, tokens: List[Token], stream suppression_regions.extend(rule.match(tokens, stream)) suppression_regions = self._collapse_suppression_regions(suppression_regions) + self.silent_regions.append(suppression_regions) for var in true_var_detections: suppressed = self._if_suppressed(var, suppression_regions) diff --git a/deepsecrets/core/tokenizers/helpers/semantic/language.py b/deepsecrets/core/tokenizers/helpers/semantic/language.py index 1671d7d..255c998 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/language.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/language.py @@ -17,6 +17,11 @@ class Language(MultiValueEnum): KOTLIN = 'kt' SWIFT = 'swift' MARKDOWN = 'md' + XML = 'xml' + HTML = 'html' + R = 'r' + NIX = 'nix' + RUBY = 'rb' ANY = 'any' UNKNOWN = 'unknown' diff --git a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py index 9e6a1fa..aebcc69 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/detector.py @@ -81,7 +81,9 @@ def regexify_values(cls, values: Dict) -> List[re.Pattern]: class RegionDetector(BaseModel): language: Optional[Language] = None + languages_exclude: List[Language] = Field(default_factory=list) stream_pattern: re.Pattern + overlapped: bool = Field(default=True) match_rules: Dict[int, Match] match_semantics: Dict[int | str, str] @@ -171,7 +173,7 @@ class CheapVariableDetector(RegionDetector): def match(self, content: str) -> List['Variable']: true_detections = [] - for m in re.finditer(self.stream_pattern, content, overlapped=True): + for m in re.finditer(self.stream_pattern, content, overlapped=self.overlapped): if not self._verify(m): continue diff --git a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py index b82bce2..ce2be9e 100644 --- a/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py +++ b/deepsecrets/core/tokenizers/helpers/semantic/var_detection/rules.py @@ -112,6 +112,16 @@ class VariableDetectionRules: }, match_semantics={1: 'name_token', 5: 'value_token'}, ), + VariableDetector( + language=Language.PHP, + stream_pattern=re.compile('(p)(L)(p)(o)(L)'), + match_rules={ + 1: Match(values=[re.compile(r'.*\[$')]), + 3: Match(values=[re.compile(r'^\]$')]), + 4: Match(values=[re.compile('^=$')]), + }, + match_semantics={2: 'name_token', 5: 'value_token'}, + ), # CONFIGS AND FORMATS VariableDetector( language=Language.TOML, @@ -229,11 +239,56 @@ class VariableDetectionRules: }, match_semantics={3: 'name_token', 5: 'value_token'}, ), + VariableDetector( + language=Language.NIX, + stream_pattern=re.compile('(L)(o)(L)(p)'), + match_rules={ + 1: Match(types=[PygmentsToken.Literal.String.Symbol]), + 2: Match(values=[re.compile('^=$')]), + 4: Match(values=[re.compile('^;$')]), + }, + match_semantics={1: 'name_token', 3: 'value_token'}, + ), + VariableDetector( + language=Language.RUBY, + stream_pattern=re.compile('(L)(p)(L)'), + match_rules={ + 1: Match(types=[PygmentsToken.Literal.String.Symbol]), + 2: Match(values=[re.compile('^:$')]), + 3: Match(types=[PygmentsToken.Literal.String.Single, PygmentsToken.Literal.String.Double]), + }, + match_semantics={1: 'name_token', 3: 'value_token'}, + ), + VariableDetector( + language=Language.RUBY, + stream_pattern=re.compile('(L)(o)(o)(L)'), + match_rules={ + 1: Match(types=[PygmentsToken.Literal.String.Symbol]), + 2: Match(values=[re.compile('^=$')]), + 3: Match(values=[re.compile('^>$')]), + 4: Match(types=[PygmentsToken.Literal.String.Single, PygmentsToken.Literal.String.Double]), + }, + match_semantics={1: 'name_token', 4: 'value_token'}, + ), + VariableDetector( + language=Language.SHELL, + stream_pattern=re.compile('(L)(L)'), + match_rules={ + 1: Match(values=[re.compile('^--')]), + 2: Match(values=[re.compile('^(?!--).*$')]), + }, + match_semantics={1: 'name_token', 2: 'value_token'}, + ), ] @classmethod def for_language(cls, language: Language) -> List[VariableDetector]: - return list(filter(lambda x: x.language in [language, Language.ANY], cls.rules)) + if language is None: + return list(filter(lambda x: x.language == Language.ANY, cls.rules)) + + return list( + filter(lambda x: x.language in [language, Language.ANY] and language not in x.languages_exclude, cls.rules) + ) class VariableSuppressionRules(VariableDetectionRules): @@ -353,14 +408,44 @@ class CheapVariableDetectionRules(VariableDetectionRules): rules = [ # looking for generic key-value CheapVariableDetector( - stream_pattern=re.compile('(["\'])([^\\[\\]"\'\\)\\(;\\s]+)\\1\\s*[:=]\\s*(["\'])([^\\[\\]"\';\\s]+)\\3'), + language=Language.ANY, + languages_exclude=[Language.JSON], + stream_pattern=re.compile(r'(["\'])([^\[\]"\'\)\(;\s]+)\1\s*[:=]\s*(["\'])([^\[\]"\';\s]+)\3'), match_rules={}, match_semantics={2: 'name', 4: 'value'}, ), + # looking for key-values when key is not in quoted, ignoring html tags (important really) + CheapVariableDetector( + language=Language.ANY, + stream_pattern=re.compile( + r'<[a-zA-Z]+[^>]*(*SKIP)(*F)|\b([a-zA-Z0-9_]+?)\b[\s]*?[:=][\s]*?([\'"])\b([^\'"()]+?)\b\2' + ), + match_rules={}, + match_semantics={1: 'name', 3: 'value'}, + ), # looking for random urls CheapVariableDetector( - stream_pattern=re.compile('(?:\\:\\/\\/[^\n\r" ]*?|\\G)[?&]([^=&\\s]+)=([^&\\s"\',]*)'), + language=Language.ANY, + # stream_pattern=re.compile(r'(?:|://[^\n\r" ]*?)[?&]([^=&\s]+)=([^&\s"\'),]*)'), + stream_pattern=re.compile( + r'(?:(?::\/\/[^\s" =]*?)[?&]|\G(?!^)[&])([^=&\s,()!".:+<>]+)=([^&\s"\'),?=[\]:]*)' + ), + overlapped=False, match_rules={}, match_semantics={1: 'name', 2: 'value'}, ), + # cases with var (or def) a = "aaaa" in documentations + CheapVariableDetector( + language=Language.ANY, + stream_pattern=re.compile(r'(var|def)[\s{,]*?([a-zA-Z0-9_]+?)[\s]*?[:=][\s]*?([\'"])([^\'"()]+?)\3'), + match_rules={}, + match_semantics={2: 'name', 4: 'value'}, + ), + # Searching key-value escaped (JSON inside JSON) + CheapVariableDetector( + language=Language.JSON, + stream_pattern=re.compile(r'(\\")([^\[\]"\'\)\(;\s]+)\1\s*[:=]\s*(\\")([^\[\]"\';\s]+)\3'), + match_rules={}, + match_semantics={2: 'name', 4: 'value'}, + ), ] diff --git a/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py b/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py index ba9b521..d9b3def 100644 --- a/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py +++ b/deepsecrets/core/tokenizers/helpers/subfile_regions_helper.py @@ -1,5 +1,5 @@ import regex as re -from typing import List +from typing import List, Set from deepsecrets.core.model.file import File from deepsecrets.core.model.semantic import Region from deepsecrets.core.model.token import Token @@ -58,14 +58,14 @@ class SubFileRegionsHelper: tokens: List[Token] stream: str file: File - regions: List[TokenizedRegion] + regions: Set[TokenizedRegion] def __init__(self, file: File, language: Language, tokens: List[Token], stream: str) -> None: self.language = language self.tokens = tokens self.stream = stream self.file = file - self.regions = [] + self.regions = set() def extract_subcontent(self, match: Region): @@ -83,47 +83,79 @@ def extract_subcontent(self, match: Region): return nizer.regions def find(self): + regions = set() detection_rules = SubfileDetectionRules.for_language(self.language) if len(detection_rules) == 0: - self.add_region(self.language, self.tokens, self.stream) + self.add_region(self.language, self.tokens, self.stream, 0, len(self.tokens)) return self.regions for rule in detection_rules: matches = rule.match(self.tokens, self.stream) - current_index = 0 for match in matches: start_index = match.code[0] end_index = match.code[1] - self.add_region( - self.language, - self.tokens[current_index:start_index], - self.stream[current_index:start_index], - ) - - regions = self.extract_subcontent(match) - if len(regions) == 0: + content_regions = self.extract_subcontent(match) + if len(content_regions) == 0: continue - for region in regions: + for content_region in content_regions: start_offset = self.tokens[start_index].span[0] - for token in region.tokens: + for token in content_region.tokens: token.span = (start_offset + token.span[0], start_offset + token.span[1]) token.file = self.file - self.add_region(region.language, region.tokens, region.stream) - current_index = end_index - - if len(self.regions) == 0: - self.add_region(self.language, self.tokens, self.stream) + regions.add( + TokenizedRegion( + content_region.language, + content_region.tokens, + content_region.stream, + substitute_start_index=start_index, + substitute_end_index=end_index, + ) + ) + + if len(regions) == 0: + self.add_region(self.language, self.tokens, self.stream, 0, len(self.tokens)) return self.regions + self.merge_regions(regions) + return self.regions - def add_region(self, lang, tokens, stream): - self.regions.append( + def merge_regions(self, regions: Set[TokenizedRegion]): + current_index = 0 + for region in sorted(regions, key=lambda x: x.substitute_start_index): + self.add_region( + lang=self.language, + substitute_start_index=current_index, + substitute_end_index=region.substitute_start_index, + tokens=self.tokens[current_index : region.substitute_start_index], + stream=self.stream[current_index : region.substitute_start_index], + ) + self.add_region( + region.language, + region.tokens, + region.stream, + region.substitute_start_index, + region.substitute_end_index, + ) + current_index = region.substitute_end_index + + self.add_region( + lang=self.language, + substitute_start_index=current_index, + substitute_end_index=len(self.tokens), + tokens=self.tokens[current_index:], + stream=self.stream[current_index:], + ) + + def add_region(self, lang, tokens, stream, substitute_start_index, substitute_end_index): + self.regions.add( TokenizedRegion( language=lang, tokens=tokens, stream=stream, + substitute_start_index=substitute_start_index, + substitute_end_index=substitute_end_index, ) ) diff --git a/deepsecrets/core/tokenizers/helpers/type_stream.py b/deepsecrets/core/tokenizers/helpers/type_stream.py index e1aac02..601be58 100644 --- a/deepsecrets/core/tokenizers/helpers/type_stream.py +++ b/deepsecrets/core/tokenizers/helpers/type_stream.py @@ -48,6 +48,7 @@ PygmentsToken.Literal: 'L', PygmentsToken.Literal.Scalar.Plain: 'L', PygmentsToken.Literal.String: 'L', + PygmentsToken.Literal.String.Symbol: 'L', PygmentsToken.String: 'L', PygmentsToken.String.Single: 'L', PygmentsToken.String.Double: 'L', diff --git a/deepsecrets/core/tokenizers/itokenizer.py b/deepsecrets/core/tokenizers/itokenizer.py index 40c5179..9d9821a 100644 --- a/deepsecrets/core/tokenizers/itokenizer.py +++ b/deepsecrets/core/tokenizers/itokenizer.py @@ -11,13 +11,14 @@ class Tokenizer: tokens: List[Token] settings: NamedTuple lifecycle: FileLifecycleHooks - + silent_regions: List last_offset_reported: float = 0 def __init__(self, **kwargs) -> None: self.tokens = [] Settings = namedtuple('Settings', kwargs.keys()) # type: ignore self.settings = Settings._make(kwargs.values()) # type: ignore + self.silent_regions = [] self.lifecycle = None def add_lifecycle_hooks(self, lifecycle): @@ -62,3 +63,6 @@ def get_variables(self, tokens: Optional[List[Token]] = None) -> List[Token]: vars.append(token) return vars + + def get_silent_regions(self): + return self.silent_regions diff --git a/deepsecrets/core/tokenizers/lexer.py b/deepsecrets/core/tokenizers/lexer.py index 5af2124..ddee11f 100644 --- a/deepsecrets/core/tokenizers/lexer.py +++ b/deepsecrets/core/tokenizers/lexer.py @@ -1,4 +1,4 @@ -from typing import List, Type, Union +from typing import List, Set, Type, Union from deepsecrets.core.model.tokenized_region import TokenizedRegion @@ -23,7 +23,7 @@ class LexerTokenizer(Tokenizer): token_stream: str lexer: Lexer language: Language = None - regions: List[TokenizedRegion] = [] + regions: Set[TokenizedRegion] = None def _get_types_for_token(self, token: PygmentsToken) -> List[Type]: # type: ignore types = [] @@ -63,6 +63,7 @@ def _find_lexer_for_file(self, file: File): return lexer def tokenize(self, file: File, post_filter=True) -> List[Token]: + self.regions = set() self.token_stream = '' # TODO: don't trust the extension, use 'file' utility ? @@ -86,10 +87,13 @@ def tokenize(self, file: File, post_filter=True) -> List[Token]: start = current_position end = start + len(content) current_position = end - if current_position >= file.length: + if current_position > file.length: continue try: + if PygmentsToken.Error in types and len(types) == 1: + continue + content = self.sanitize(content) if not content: continue @@ -105,20 +109,22 @@ def tokenize(self, file: File, post_filter=True) -> List[Token]: except Exception as e: str(e) - self.on_new_offset_processed(new_offset=offset / file.length) + self.on_new_offset_processed(new_offset=current_position / file.length) - self.regions: List[TokenizedRegion] = SubFileRegionsHelper( + self.regions: Set[TokenizedRegion] = SubFileRegionsHelper( file=file, language=self.language, tokens=self.tokens, stream=self.token_stream, ).find() - self.tokens = DeepAnalyzer( + deep_analyzer = DeepAnalyzer( regions=self.regions, deep_inspection=self.settings.deep_token_inspection, post_filter=post_filter, - ).get_final_tokens() + ) + self.tokens = deep_analyzer.get_final_tokens() + self.silent_regions = deep_analyzer.silent_regions return self.tokens def add_to_token_stream(self, tokens: List[Token]) -> None: diff --git a/deepsecrets/core/ui/floating_header.py b/deepsecrets/core/ui/floating_header.py new file mode 100644 index 0000000..b91d73d --- /dev/null +++ b/deepsecrets/core/ui/floating_header.py @@ -0,0 +1,25 @@ +from datetime import datetime +import time +from rich.console import Group +from rich.rule import Rule + + +class FloatingHeaderWidget: + start_time: datetime + text: str + + def __init__(self, start_time: datetime, text: str): + self.start_time = start_time + self.text = text + + def __rich__(self) -> Group: + elapsed = time.time() - self.start_time.timestamp() + + hours, rem = divmod(elapsed, 3600) + minutes, seconds = divmod(rem, 60) + + return Group( + Rule(self.text, characters='—'), + Rule(f'[dim white]⏱️ {int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}[/dim white]', characters=' '), + "", + ) diff --git a/deepsecrets/core/ui/progress_bar.py b/deepsecrets/core/ui/progress_bar.py new file mode 100644 index 0000000..2c4f268 --- /dev/null +++ b/deepsecrets/core/ui/progress_bar.py @@ -0,0 +1,30 @@ +from datetime import datetime +from typing import Iterable + +from rich.console import RenderableType +from rich.progress import Progress + +from deepsecrets.core.ui.floating_header import FloatingHeaderWidget + + +class DSApplicationProgess(Progress): + def __init__(self, *args, **kwargs): + self.header = FloatingHeaderWidget( + start_time=kwargs.get('startup_time', datetime.now()), + text='Running analysis', + ) + try: + kwargs.pop('startup_time') + except Exception: + pass + + super().__init__(*args, **kwargs) + + def set_start_time(self, start_time: datetime): + self.header.start_time = start_time + + def get_renderables(self) -> Iterable[RenderableType]: + progress_table = self.make_tasks_table(self.tasks) + + yield self.header + yield progress_table diff --git a/deepsecrets/core/ui/time_remaining_column.py b/deepsecrets/core/ui/time_remaining_column.py new file mode 100644 index 0000000..f01cfe3 --- /dev/null +++ b/deepsecrets/core/ui/time_remaining_column.py @@ -0,0 +1,39 @@ +from rich.progress import Progress, TimeRemainingColumn +from rich.text import Text + + +class SyncedTimeRemainingColumn(TimeRemainingColumn): + + progress_instance: Progress + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.progress_instance = None + + def _calculate_remaining(self, task): + max_remaining = 0.0 + + for sub_task in self.progress_instance.tasks: + if sub_task.id != task.id and sub_task.time_remaining is not None: + if sub_task.time_remaining > max_remaining: + max_remaining = sub_task.time_remaining + + internal_remaining = task.time_remaining + + if internal_remaining is not None and internal_remaining < max_remaining: + target_time = max_remaining + else: + return super().render(task) + + if target_time > 0: + mins, secs = divmod(int(target_time), 60) + hours, mins = divmod(mins, 60) + return Text(f"{hours}:{mins:02d}:{secs:02d}", style="progress.remaining") + + return Text("-:--:--", style="progress.remaining") + + def render(self, task) -> Text: + if self.progress_instance is None or 'overall' not in task.description.lower(): + return super().render(task) + + return self._calculate_remaining(task) diff --git a/deepsecrets/core/utils/file_analyzer.py b/deepsecrets/core/utils/file_analyzer.py index edef077..c15c588 100644 --- a/deepsecrets/core/utils/file_analyzer.py +++ b/deepsecrets/core/utils/file_analyzer.py @@ -23,6 +23,7 @@ class FileAnalyzer: file: File engine_tokenizers: List[EngineWithTokenizer] tokens: Dict[Tokenizer, List[Token]] + silent_regions: List progress: FileProgress task_reporter: Any task_id: Optional[int] @@ -32,6 +33,7 @@ def __init__(self, file: File): self.file = file self.tokens = {} self.progress = FileProgress() + self.silent_regions = [] self.lifecycle = FileLifecycleHooks(reporter=None, task_id=None, progress=self.progress) self.task_reporter = None self.task_id = None @@ -62,6 +64,9 @@ def process(self) -> List[Finding]: self.lifecycle.on_finish() return results + def _add_silent_regions(self, regions: List): + self.silent_regions.extend(regions) + def _run_engine(self, et: EngineWithTokenizer) -> List[Finding]: results: List[Finding] = [] processed_values: Dict[int, bool] = {} @@ -69,6 +74,8 @@ def _run_engine(self, et: EngineWithTokenizer) -> List[Finding]: if et.tokenizer not in self.tokens: et.tokenizer.add_lifecycle_hooks(self.lifecycle) self.tokens[et.tokenizer] = et.tokenizer.tokenize(self.file) + self._add_silent_regions(et.tokenizer.get_silent_regions()) + self.lifecycle.on_tokenization_finished( name=et.tokenizer.__class__.__name__, token_count=len( diff --git a/deepsecrets/core/utils/guess_filetype.py b/deepsecrets/core/utils/guess_filetype.py index 79e9706..97f46a3 100644 --- a/deepsecrets/core/utils/guess_filetype.py +++ b/deepsecrets/core/utils/guess_filetype.py @@ -10,7 +10,11 @@ class FileTypeGuesser: def __init__(self) -> None: - self.host_swaps = {'Rd': 'R'} + self.hot_swaps = { + 'Rd': 'R', + 'cshtml': 'html', + 'xml': 'html', # TODO: Check https://github.com/pygments/pygments/issues/1785 + } self.probes = { 'json': self._is_json, @@ -24,7 +28,7 @@ def __init__(self) -> None: def guess(self, name: str, content: str, extension: Optional[str]) -> Optional[str]: - swap = self.host_swaps.get(extension) + swap = self.hot_swaps.get(extension) if swap is not None: return swap diff --git a/deepsecrets/core/utils/lexer_finder.py b/deepsecrets/core/utils/lexer_finder.py index b89fa3b..214f7c5 100644 --- a/deepsecrets/core/utils/lexer_finder.py +++ b/deepsecrets/core/utils/lexer_finder.py @@ -67,7 +67,7 @@ def find(self, file: File): return lexer def _determine_extension(self): - meta_extensions = ['txt', 'conf', 'Rd'] + meta_extensions = ['txt', 'conf', 'Rd', 'cshtml', 'xml'] if self.file.extension is None or self.file.extension in meta_extensions: return self._try_guess_extension() diff --git a/deepsecrets/core/utils/progress.py b/deepsecrets/core/utils/progress.py index a870183..cbac8ec 100644 --- a/deepsecrets/core/utils/progress.py +++ b/deepsecrets/core/utils/progress.py @@ -86,9 +86,9 @@ def set_file_size(self, file_size: int): def report(self, child_report: Optional[dict] = None): # percentages - # Lexer: 95% | FullContent: 5% - # Tokenization: 80%. Tokenization: 5% - # Search: 20% Search: 95% + # Lexer: 75% | FullContent: 5% | CheapVarSearch: 20% + # Tokenization: 80% Tokenization: 5%. | Tokenization: 70% + # Search: 20% Search: 95% | Search: 30% percentage = 0 for name, tokenizer_info in self.tokenizers.items(): diff --git a/deepsecrets/rules/regexes.json b/deepsecrets/rules/regexes.json index ccf5ccf..6ffd66c 100644 --- a/deepsecrets/rules/regexes.json +++ b/deepsecrets/rules/regexes.json @@ -3,41 +3,41 @@ "id": "S0", "name": "Slack Token", "confidence": 9, - "pattern": "xox(?:a|b|p|o|s|r)-(?:\\d+-)+[a-z0-9]+" + "pattern": "(?=.{29,256}$)xox[abpors]-(?:[A-Za-z0-9]+-){3,4}[A-Za-z0-9]+" }, { "id": "S1", "name": "RSA private key", "confidence": 9, - "pattern": "-BEGIN[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-" + "pattern": "-BEGIN[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-" }, { "id": "S2", "name": "SSH (OPENSSH) private key", "confidence": 9, - "pattern": "-BEGIN[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" + "pattern": "-BEGIN[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S3", "name": "SSH (DSA) private key", "confidence": 9, - "pattern": "-BEGIN[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" + "pattern": "-BEGIN[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S4", "name": "SSH (EC) private key", "confidence": 9, - "pattern": "-BEGIN[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,}?-----END[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" + "pattern": "-BEGIN[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S5", "name": "PGP private key block", "confidence": 9, - "pattern": "-BEGIN[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-----([^-][\\s\\S]{50,3000})[^-]-----END[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-", + "pattern": "-BEGIN[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-----[^-]([\\s\\S]{50,15000}?)[^-]-----END[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-", "match_rules": { "1": { "pattern": ".*", - "negative_pattern": "[-{}()<>]" + "negative_pattern": "{|}|<|>|(\\)\\))" } } }, @@ -45,8 +45,8 @@ "id": "S7", "name": "Facebook Oauth", "confidence": 9, - "pattern": "facebook.{0,100}?['\"][0-9a-f]{32}['\"]" - + "pattern": "facebook.{0,100}?['\"][0-9a-f]{32}['\"]", + "enabled": false }, { "id": "S8", @@ -64,7 +64,7 @@ "id": "S12", "name": "Heroku API Key", "confidence": 9, - "pattern": "heroku.*?[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}" + "pattern": "(?i)heroku.*?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" }, { "id": "S17", @@ -119,11 +119,11 @@ "id": "S26", "name": "Custom private key", "confidence": 9, - "pattern": "-BEGIN[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-----([\\s\\S]{50,3500})?-----END[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-", + "pattern": "-BEGIN[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-----[^-]([\\s\\S]{50,15000}?)[^-]-----END[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-", "match_rules": { "1": { "pattern": ".*", - "negative_pattern": "[-]" + "negative_pattern": "{|}|<|>|(\\)\\))" } } }, @@ -192,7 +192,13 @@ "description": "https://docs.stripe.com/keys", "name": "Stripe Secret", "confidence": 9, - "pattern": "\\b(?:pk|sk|rk)_(?:test|live)_[0-9a-zA-Z]{24,45}\\b" + "pattern": "\\b(?:pk|sk|rk)_(?:test|live)_([0-9a-zA-Z]{24,45})\\b", + "match_rules": { + "1": { + "pattern": ".*", + "negative_pattern": "^(.)\\1{10,}" + } + } }, { "id": "S37", @@ -218,6 +224,14 @@ "name": "LocationIQ Key", "confidence": 9, "pattern": "\\bpk\\.[a-zA-Z0-9]{30,32}\\b" + }, + { + "id": "S41", + "name": "SQL Server Connection String", + "confidence": 9, + "pattern": "sqlserver://(?:.*?);.*?(?:password)=(.*?)(?:;|\\s|$)", + "target_group": 1 } + ] diff --git a/deepsecrets/rules/value_scoring_model.json b/deepsecrets/rules/value_scoring_model.json new file mode 100644 index 0000000..aa80cbb --- /dev/null +++ b/deepsecrets/rules/value_scoring_model.json @@ -0,0 +1 @@ +{"num_words": 370000, "fp_rate": 0.01, "bit_size": 3546471, "num_hashes": 6, "max_word_len": 45, "total_trigrams": 3825345, "bit_array_hex": "3643603859fd3a83ab062fd823dea9f6f6e055654e570a36901fe47b2a40d199393a81aa252f62e25fb82379883dfb76119d9a4db7ec91747a33f9db76758e4f687f2be273c64b3defa2df001e1d0e523e455cc07b7aca106793bd997d16f7be91460bdace5bb05b425170f3240068661d49e7c4c86e012406ee0a0fa8e397605ab088f1d4f3e50314d54f33c2bef4b18a6ef29433c626edb4bdc9c3bf09ceb7877cf133854d3bde93e7742814286d66a475d42fd43d7c70102b80a9c86574f772ebbba30ff51fdbbc1a8bf1bf427a5f0e107f10307a7264af0c06e204adf25a0999f0e49dde4d9978c97b19fcff4967d54e8dd63de91d536c98a11eaea99b07669c9c233a3e993a565046bf7143a07386f2683e5b4b2a99dbd36fdbf7d6b3dc53c492c3d7d3622da93435d9529725948165aaeaa917b67fc2168dc5d82fe8a3fdadd9dd5ac61d8aea90ac7d67fb84c8b04d86c4fe02c0b41f9558183e8a2d5461e7c0c40d731289cea9564b23f31be8aaced9be4921007fa776072b27dc59b86191993dd7a388a23a89db602f1eb4b9dac744b71367795cc7d82b2ef27fff6d53526216f53a18b96d55f4d4477933ef825421fb14fc441b739ac307457460871cceac18af015b07fb46391e7104b83d694e7ab65c83520befff22194c46d88af4e369030ec2e9c519583a5f36ec0bf4098b2769f5263e02c727b86f99fd094a130d53eda63f30209b87d2f4c0ea8ef68a34d2f1ec75a623d4956b1d688bed460c9fc13b525b7fa62648ca779f0b4b1578991873f0d34ae64e97de52d61628ffecc9da7966c0234d803d3268d4e00cf06b114e2ce42062cccbea0a12a058a01e3383cea0773b9326805dbc5c85314d7a30a40c58b93e17bd1fc7efeda5abb049f5a6eb86b36aa449c656b5d9bc9cc4439da6deb30cbbb67bb3f9d4c39e78049d9ea5dc014801fad03b37696b5a4aca9939b4259616b6024f459793a876d732ea948654ce664f42afe8ebdb68ef05112f72ab12798e214703d209212feed0344abec69321d31d25ea7157784d98bab0dc1eb9b22309209a4d9c6effef52f0456ecf13c3ee8b6a40c12bf9b263c43a21d12b11fe0fb5bb84296f681ce41559af5c427db3541f4fe74696e00fb9d94c046d945ea0cefe22056c0f0f260fd9417e116ab622bfebaf6b49169844aeb7374b9b6ad9f15bb50fbf7aa2726952eb340f148aad9ccf82cb4190f82a35686ffeeff1ca1799217dcf08cd652efdb3cff63c1e78654c3c2ef40c96cd6b4a6d37988eaba666580e793f73bbcf562405d99f5d6a167855e8364e799261578b1f75c1a2c5cf5a6395225377975fe3b2ba416277f8013afafc0e216a57e4f620ec68828941eec5f205516edebfdf6ca14839a526093a71f50e0744c00261d0b481d804f872c019a5f27a3b2c3c510c3bb98f530f042ebe99c152753d19ac255fe408a32aefc84fbfe717b9e1fe7cdcf0b646915f56ca4267a2da29ee6cb5eb41e3276ffaf6b51093f232f476b5499a12348b53bb64446db162f23a58a11d98c47164c14025baab29a5d3e9a71c50c21a748a104520dd1301755813ae6929f7c6d8526d0c07b09ff9e17af7ae80a1b3d14edbf7e34783ac6b96a4d04975bc223dc756d3fe6900ae3a5b68dc31fa30e8e6870028ab4d19af0e1174aad252100049e74d23487c8b2a5a2b4e41a5a8f88759b27c501d9497bf01b74ff9bb80b5a51902bfb2c04b8f31a953ed4e640614bfaac23a5424b0f5813bb8cce776460da6ff97a8422d88efcfb63bcbf3215a6badd81b164246b89d17f3d5f1fdaf138b6725da6ef9ab6c6835cc19d88c3097ca875b333c66c5bfde0bc3b071f91fac7a0a662433ed41be71a2efaff84bf46ff17d07c952fb28f5fb7603a7ad54309bf6e7a7b001e26dbe7708edfd72b64a5c0c471c065c604c4e5f809b13c3bf6a466f025d1e8a2dd831d99ee39c08d25757f452fa440dd66f9ce25a7f15082e3478fb102564fa619c5d5579b0939a7e3b99f36bf25628ff468344860ccb5b043d8415ad069a98e24ac59db6f619afce7424a558f4ef5e3d14739277560164df59b98644b3f8782c823d663abe3f0f244fceca7d578a659b017926aac0e602d954aff2be8d8aa657673d51634c4a14a7152df54ab63210d1fbb496237f201220e89100d13150d1112eea9a4098415904d2f43087b031be185752b643807ab7a9179c8e75c0a4fddb237339c190e6f158bb045721ab69884347d8fe1e6220be09f65cec063c397dbcacb7e0e7ccff394549070c1b78de8b5beeb49479ed089d134b09c1175f2611594c95b656f90d05e2d9759b738a29f5a658dad36324e22086c54f6b95e4090e0ccb0c9e9ff9ff159b28198132ec33a2e5d06d25f9b63d249566f22af91b192454deb5d53715cd1a1da2f2e126306a86928f3a441489f4044e47ce8c3cda953c78d12f6720cb3ea6af806e961af66a34db8b4af7ca93c34748d323396c5d12bf9a5b206610452a4c1d00d45d1fcd470b4795430d6867af06670a1f6460c825cf47feefba86a7c1a167055d2976a7f5e6d46825104883b4a5472d6bacf398e5808cd6f32eec8ff4cc619b708ea7025c8ca8fd15a3903457c966b3d7f66b4ef5dc143479a0fac1d40373d4ce0cc9f13a582ed6e3ae18f62b42a6f024b9c09a2e17d9a203daf7edc4a0f4a849bb63517da68f6ed55c6b54d676cccebde1bfcd1374cb1649d41c90fb6d2084abc07e65f6f1e2a7bd588c8316cb3f045b4404abd4554c75e7be7c92ad11eb4931ce7833ad0b6605f10833c7cac59400de137b9bc1aa7cb6f68a4c2f76795618b9a3e9b1d5a6422fa17895952f045a404175e5f0efea977eb18681aaaa874c090ca7326738b7a095a72a99baf1d57830edc6a47dd3e433ed8d28aadcf638222be79849e3b702b7a9148a5ade4e057fb57026f4357d8dcdc5bd3ffa27129919ea83357864261a86f55d7578b2aa9d56bcb3dffab9cc14920e577b0960857140880fe3fed62d420608a63daef82ab92d2b08757b1591f177af7780f72dfe8d75a8b47019d360760d7bd04203fb2e81db10244aebbb45dd912f5a2c125038e45b581715cd6af770a08cf726ffca9cd4328a0959e26c4bb943d4edfd4f3965bf37d33cba1a722fba06eb8005a96870242379d2cd6a4f9521150b379bfe8ebffe8be87290b18daa635374d6c764e3ae609fa1f6eee16d5a70408b3c2ccd530ede5ffbfe737b8bc2ba75576e666466cd00243aba82e89c69bac97ebac32eb6625209b1c29c77e953cabf9cf36dd7cbe9d3dd9b7952cff17a6dec936954c829692e24c34822a03f3ba9d11ede662ab803d94ded83954233ed88c146b5f393630f56869047f670feca08cad4eecdec9249f0316d7770af6acc1279e6881fa369a9181b52f3172e0daf43aaa9d4b4a6d77c8d2e39df45befba6b6024972d3634d211aec962dc2c7ddd65dbb9ff3da061715021c7c39d723bd0d920f458492a2f4dcf8d8cf5a6276ded87c90c7539747269893e5e0123c1794751caec98eced7cbdd8ec45aa8472d38c88ceb6dc2d7deb96d9a6a839b11f001b3c1875aef845709338c7d8ca83c25c03e48fba7e7069b5fc8f83630abd48a6a58730660dd7759fd65c1d76216677ee74ce8ef31095475220a50013d945ab1df9c5a3e94c07dc3584fd29c2cb3c51dd3d807ae53fe963ad5dc04229d0f4809e44225d792aea1ff04c783675aaecfb447a60cf2b0599e4339867aed2222f8b1f1e357e5ee630bba028c1b1f227a6a31f43b0bf6f6e3ee3cdf4a2cb1dba57a09b71ec1d0674ea6a6f246fc8fa00ba8bf32dd7a9385e67d2318671acec28a9f7bb5c2d9f48017d6a4dd46bedcc5342edd502df03eca43dfeec96100cd609a039cfee4bbe28192dfb9c8553a764204aaf47f01eca8ed670c845330a681fbbf3fc8d77fcf069c943b7d209c8ccf5c21e1ad909a63b1d87a9de35c363b0a775e303cc53d1eb87966310f503b4dca74bbaf15c7efc05817478cff1f55551b9869d856e28540938cf793d1edf7370487dd797fd3f0bad062c4e13d410118003c54d1be3d4cd5f5cebb9beb1b6d21a2e971bb46638f2bd4f09397b4fdc2d048e4b898761b02d08991d6e5d995a3e9d7888f8c5a9869554131ba377558eedbaffc022f1af8615bc1aaf3d16ce0675af57cde67bb95f16fea36759b88a36d865ebcd45a477346e79e5191260672f6cfc851abd4efedb5306c9f675ab15869d3c2c8ad862b247244354d892548990f6f27732e756f2dd0e155bad4d39785ec45b27de37c6eccdcc275929ff237f4489f34979f98b45bbf7778bf836c87e7fa710a5584e66f55af9bd4ce0380c8043e36204c1aa208105b40b6e380240f4eddfedb4f7b7935ea4a7940adb7d451e435df6ee5249985403c2134b334e624b1016a388965d0605f8d36f5845ffb48f67199b1fdfebc4a2fb63fee534bc199844b714fc96f00d8358ca47f9ca915aa87a835df03f55daa5d08bfdcaac40f91303e00bbd6bf303ec8e6d53a7d52c81d870eb98f65d65231a0ed46f3dbafa2b11d4a03879b6c56afedd94ac1aacf6b91caf86aac0b37c63f6b14207df661b289d93a94b9e7a99346b6de34ab966b40ab2a4e0c5d60161259d128887cface0dde6fa43355b2b59fad522f74c6859e1b7f874433e486bb54d781d8718c11c7fcae1fcfc308fc049f28698fd87df0194afb2a07099d75a91a2f77d132b43b5ed01f9dde87945ed85df5b6964402b15903eb73430ad77791378a19e588c91c43c1c541cfab07670f3bcd6dc9dde55f888b921e76ccead55e735dd0b2b394c7cf3c58fe43b554f2408fb2e1c10e33b83c37d122fbd7379598bf9e32477b51193267ad85bca66ac9e0008b18e105eb2a55c4cc900a58fca99338dd5e845a7929ed79dc9c6958245cef32a3d6602ca9e6c2bda9bd5c865c77d3964b0052cf1145d14e94ea134005003d89ba292161f6a043b90150636a10f7971a231a341c6cb61180907f0f1d87199a8fe5e8e90531f9720e7248ea8e35599864d2dffe017e2619207fce5a9a630d0e1360b07027c3ccb4dd3389d24864ba5db255dfe4b4fda97e846bf112923cb2279016d7e2293e851754e9af2233f921e8e3c69e2d7174d092a96b7a2a5e1990201672f013c523d5b55940b94a36865163a52e04aba685b40857181ab0f96e91a859b2ed4ddb35797c4d67f299c96976ccf162663a17351b453a92c2a4199438b0f7c4cff1888b24a8ca9ae129fe8333cfdce27ccc98165b203c45a48799256177353d8d6ad4fc0cf69d9614fd5aa93a51e3468dd2ec0d6475708c1e512e7213e4996eeef6ec1b48a25a87993139caa0cad2290e892297e2f74a4ba0dca9e939a3ecb30e00f4df3067fb56f311f519a790d919fbe81ffce6bc83bb15f79f80f3a507104a2e2827ea5adf12be7e6b349b3a8e963b985bbb05abf2b1072830b880e3ced5cda96e24ce1772784067923a129fe60201570fc5c00395a184746f4f30093f39c869e0aa2602ce9d8abd1c7d39be84e5583338fee85cc8df8d07c7183691d044815fb5dac5c5f9cbb97897426e80d3563001cfd285637eb3a540f6a67d5002f3264c034435387818d64d589c289406f99f0715da57a3db0d9e5db811947f8b40706a1927b3d22729f8818f31898a070af4ab67a6975d51d4c8a00b5a902fd89d1f64300f7be6e4448f074132e4d6ac86b071e652441446f2cb96d5b1240f7d381e75e41120f8793b532165ff3a72d83b635a35731fc19b7805c74ff0d15d8b324aaea3dee13ac63cc978b0d66f4fa6c56fa72a2ac45f182590b1d66ac3360535d34b8c3ed8c09aad5e75860b84b168e208f7f08df757dc366a018bcb80eb22f93f7554ef9f0575f74d7d2f8fdabf56afc62f9122f0f4c88690fbe77a5b6b9501308f5bc555ac62db68437cdc3c0292b93b72ebbffc369dc7d6408db60f557b26a0b098473017f7da47ef20372346086052d4d973c83f4a130d9572c6c067b19bf51af3a2bc76216a24fa3b670aa53de9c011409ee9f2a72743235aa5665c61ed3f14418bd36abb33c93461cec62a2f7402f56eaf7c5b5c3942c997977e237233bfa81558d33a2f00b59521118ca3ea1aefdefee7563e4f4eab5f549903996a5fe26a0715ca2a4938b6732c188fdc3390a6552fb84b2897099b776b97e246eaf83bc11d7cec49a17337de2c3af9f8a85bfb1c90d001ab360c63d4d604db0d9fbb246d6a412746ccc7f39033d9a129fe50cd2e79066e337f93cfb24bf09221b34a4617713e57ac31f9fcf907c2143ce82370ff8f4afc1516dff3a63fe4ef1c49d32eaacd9bd694332e599d6eb5953b3852d6285a74ecfa27c26e20ff8161e06228a1c429c625e276cba8ef08f047a8a58eec7863b2955ed179287baecc5e6c3ced97e97db0fd4d9377ccfcf9169236386dcc2620fc2c74ebd8e7ab8090f75b5c51b40c2628235876b01ce4ea8e43ef78b5d4ac9f6df6e0bbbd76bc75683fbb252b4e87f6a3145d0dbcc816cc1847f05f2f2e7bb09a3c76411bdf79126c70c475ea58e911421fb8cf2f97c6ee7686463b10f691fc1b357015ca4bdb4522e1a9b8739079f542f439b9baec1da8aef0332147dab7c126955d54e8e5c5fecd1cbf37025618b2ac46e7c04c30cfab102b9abc817f58ac4240cc655ce7c56155aed96fcc62cddaff96730120b7a191ccd5d0bf48c250b37d6fe2eacfc157976635c137e6af8299505e22b57184dff58c1c8e626c352f11a59780c721af312dcbca21d34c7f96e482fd2582b7386a5a757812d6e70b4f0d94a188007820ff1a7ca59bfbbfc0a94342449c77f5483cec5c7f9f5f67a7d0710cfe5973cd10daeadb68f79db9009667f7ab8fb07b98ddbc853c3f77d77f7d1f98acc7aa859028e276bdcb22eebbab152b69f5ef276984a589ee5ec0accfc5cab40b9b0ad65f68c2b7aa18fb20b299e9d2577c3bbfa239bf29188189b14fd8aa80f55470f1f89cd13847cb45f05a366896b7fb3349d677ce9157d9e7120e240406006568cf187214643d7705adeb86c1e2c59c5e6fa8aa5ac5dfe45ff5cfd2899e4cba0cec66afd703c3d2c63b4c959cafdd86a12559bd3f5ba70e9ea9ae344f02d2f151da7989d8943138206d0f3648abf7358f204108fe30d724f47b7eee0d55d6c557482e28ea647d812e829f113d96ae66d669ad4377eb34bf6774f506aa5d60a359b986b24e8cb2e94a57bcd0d7ed1b8a41d8cc7d5af22f48dec623b6a17b4d3dcc093ea66774e6790f149d055613c7fe81a24947d256157d3505910bc571a73345be886d895715eb3094c4fe02081cdfa75a033cb3a0c901f44aef9d31c0f6fe1ecd794b93024d96aad79481195d49d87030d0e6877f6264996537cc35912e3ba782542e4e7442b2c65c99ec95c87303d0bf4feb162fb63ae9eb01c176bb7f6a6c3c058cdad6c9c67a50b4c1aee45dc4636ac1b0d6cb85eb77f1a98eb70bbc353621996052c967dfcec2bcf1f61a09ca38b841883dd3644898d5cc5fe26ea8a4f6e90713c816a7408a3e431740f064389be799e6af16eee6ae50e063d693d2140a17179401aaeb239cc16b6dc654fe1eadf800c9d3e1e545bdfad4a030db20f3b5e1904ca5e1bbafe38d86335ea52b1a4da5c4fedfe376791b92c920e4ea42d02f65ff778c2f902e8a750f6b495ef45d1ced6743c76599c9020f8f04f38c131b70fa2c91075633eca4b1ef25305c7607d9c3f4b42a4e62fb619badabb7b593674a4e0e19af6d085a519bc5428ed7929e26550865b61440a019cf4f447ee8dc58607bec9c4586a9e3e64f08797b5aa1239d268418f74ac4d0b3d04dba98bf5cd7191cc1a607f277f3ced1b4d65fcf4778d37d42a9071298c27a796ec4b07323498215c07f6a746b41af050fba0da1c2135f5ba4c5db01fd66dde64c8ae930caff25b15d46fff5079fc17f86aa67f4a380b4d6cbfd82efd4f49efa3d58cb09f339e1e768ee7f50d3ab61a0dcafab2580eb32332dff6bd1e6b496bcbd77cc45b02725278223ecbd7afa547a5f02d37cf2e3b9fe5585a875e11168b373b1ff6b783769c1d43e1de501820a50309bde15da5740697901c165fca4328268e70a03b2fb5c111aa0261c35a7f39fa955d7ed728d4773cf833b7f9bab47fc4c862b92bf3f52a58bf39721a4a76d0ef25d23cca404518dd20ec810ac282b1fcc6357a6405bfcc4a7266f578b33407d0075ff1a0efea07baeadac4c8ffea5e17b5d82aef0097a22c4e198ef3e3d3d7a76865ed4edac9e2b5c3b403d0735fab29b1920b0c44319f11f7d724c4e3bdb464a98ee772d5f6d4e139a6e3b209dd0cc9f1419a6b0595aeb5f82bebcc6edc0363c0e9a8e39406d75cc5c94e2d7e1125b3c6045658a87fac8f19c5cf37555c900415279b12caa2fba0bee359444b4db59603fe455fa32d3b7439fb1199c9d60c1f896cba3d28cde2d1e75d335b2ce519b70a7b2034fc66bdb32a2739b2008066879df87d8280ab15d190d71213bd1a5da9d8590a69ab7d1b7af7ea94920551e24dbafb06a5db2ca33eefe2a46d0f912abd366bb3fc15e9d34cb9e790e93bd122a0cdf91b0ebd5b11d2f8cdbedca98336b5da1d563f2b5fb5853f32368aa18ed6d5da32613c0900cd575ae18777f9c6cba621e98b181d5b53d6fbb46fb4f38ece6cb36e502bfda5a2e4228196227331bcde1dd85791674f1882054c0f9aebc3b992dd22d9663261028f91c0f29dcb3370f23cf1fd7286d8061c549d6347a8682e8be5ebe5801a9ef846515ded3bbb1c03e7f9368f3bb26cd78f950300bd2336746c100dfbad8d3082b6982279aa99b826060d456cbbc04306d9659bc0b89f79013269f9de3a94ef081091d22fcb2ca5a76b91e080ae86422400c31fd8e61713e7fc04143ec206f640ac3adb56119155a9ef722f02b5bcc26ad33e8b415427dd301f2946d920affd048a897a694b24939103f30732c176781fdc18e5d3e889d2a0ccd2d9d642635f79a49351948642c214b4e53c554e460e0b3929feee2cb234dd2224f13ea96fe3bf449426f704dbade3896668ad5421c9ddec7d41a2718641574c94006c3b561512de22f28749e46a3d52b4ae804ea5579b228a89860b67fec9df1e3b5c6c9a253cad423ae664d199bc5f1997584407b09ef507c79591dfc0f68475b80634256187144aab87761496833095ed674d1ad5cb3c511f3e290e272f2386724e0db0b6d7261026f71b2276253f7741c84ed0e34c3301044e5069cc8d5c83de3ffede335f2359db8b6c3f421831e30e147d6852e3e28d4cfe7b06531611aa74d5b9a8e9ca4417f55ad259d572261df3bfbf12aa29d46eab22881ad596ec347807bad3b2e9cce8d0d51b4a61a6e36e9c7c3ef2ca1d9dc4baf434495766de6b71f4a2614464989c1ecd0a66de1d1ca55d893191ad1d2393c472eed5b464ad26945bfc8b3291a70a5e7a4b5067c736bb5777899d13b09fb377fcd12b6edb03481ac75aba1b48ad87391a39217389cb322b0dffc2da882978d2d867465a39147d0226b7e1f1ff62fc209e53cbbcc78e96341e3166d1766b4988f6fd3fdef75e7038a16ddb7436661e0b5ceecb17546586b55be60e5d6f1fbe02a761fe60280d241561d32c47ea86d6ed6ee6bd3645f05901d383f86a4f36a60dc703eb5aa9f0bae1ba59eedb4b5561cf2c6df7dc730b2ce78fdd533c005af0b7467ccff9ccc8ae2a88aa52ab8cf0e550059208fcc739d38ba7fceecdbd07ef10e76212a2ec23be277f9fa79c86e69a5abc4a13ec803fd99b5bc7acdea2eabccf22468e4a8abe09dc2375f3132a783fa8a8f5130a08b4ffacb1c75d4333cd7c782ac03beb3fdcf8b7a7811e6225155eda33e4862ae4300c5fc9c7c31fe87c2ea8c9f543ed838100a7e6da024125e59c111bc0a3fc67940c192641289f939889f7f5e7e9f78d3e940cdced3fe38249d20c9fc6ca3859408c4e9627e0a8e30dfce4bf1716f9e1f264ac20b2b80c3e5d5d5314cc5ecaa93fd4ebd6c76c9526228314c854cdbf852bc5c96e985a65672dbc705147491db99df8f825e28a04f7c1ab21ef509c785b4ad086065475c118c41eb303f21223e6f32bec8772daf3eb1fd6db8d1f15e2cb89970fb7ecfc1a24112ba03f5a94f715e5f60a64d90ad34f4c8e8d5e9f90ace26506dd7c90d4f9682cb044cd2281cce5acbc04b6c46ee2d1d9522ba283a91934379522eda71c93dc823d63c36334f979b61860bf806bca8ac86627a86b54d8709a12892ebe2383ee39ebf16b902e982976b8ae52ae74fbc4da759f4974a52e9c663ec3786cae9a79a365e228b1a9fe7fe0a73ef0a4d73a6c1368191d7d7a61b6192cacd99786873830321ff4aba20d4e91758a7207d8c29d130975e555add54a6f0cc64d39a922d854abffb447526efdb57dc24316efb21ce38af918e1e0be0b0d5b9dee28535c42ea69420e330b158c078ffba06ad8af285a6af27523367bf6a07290e0cf467948c36223a4f5e560cd73f5353fe236501056a396879aa2794de214caffb32c88fababeda78899c3c982a5d525064f44d4e82e8940f560ba1938521c25db790e31f2cf5ad5a0f284609cef1731b91a8235dec418c3cf91d6613c823ea74584928d5cd0996f62c87cc26452c3677b2e30f6c36aff05cd0bd57882a6b34672db7d7e0fb7261a22e0a70efadfeac9d883bcb43d589e102db3870f95080c80f046c098cd6922143efe89dc9bf3a1e13261426e9ce07585425f9ee19c357413d1ef3f5734063c3e591a2072f23cf72aed15bacae5473b460f7b75a9a8cb4f78ed988320c6e7183bfb4d13885ebca96419df0f84647088549c88be02e8b5c67fe101c1a88f90955aef9b3490fd74213fff888896ad4eef1bf22572d42f9e3fd8974b8b37bfee7030e5aa5565b1d92e852e2da204a6699e3e04305c5726323db689d41c25968343409679b0bbee2b7cc65731dc648669a598171ef23ea22ae43ff7e1aeec08e2ce13e5151c9ed0639ca2c486bbdc2769d62b068f40c4c702ffb7eac24aefc652bb617c492ee708350b4604846a8caf82bf4ec70a12f2365aec72e677596c7a7e580ca8704787abb0f4e30e7a988f493c475e81e2a0a0dcad099a62bd994e43c756f3b588cda14b3f01422223b5d2e5ae8d62df010e465e3fb780e032e79d34881e6c484d1c078c925f64b6314c1f21b1f0587fb9e34e6c806d08ce528bd9494d5e6898ddfdb0306b292f63bdf91f9de59e213d3741ac69bcd8ce14419ca4600564fcb9af620441bd9a5c0cd937ffa46bf27657f22e7ec282fb6c5e1262d7661e27b06d7d7bbd535fe5f7a00e2dedbf17cd7c88d9d823532d4d4569fedac3eb1826779f3db25163ed9770dfba146722e448b9a3755fe3594f5ec9de5472108a6b087e1d9763a324518bdaf14aa793d87fed7d185b87386c581e8ce398446b291b30275549fec6e9f6593aac7be49aef8beb9eedacd5726cf762229c26337ebb0f757418885afb5ca7dbd496ca547ea6b5aab4c657c086a7c20c4f94cf6ef1a00438a3ce4e068913361d984b12937fcd4bd28879717fd4b6442772ed7de844aee73e55c3308c1623d870b0523933e71332b51bc434d38e97e63097f2fe6ff4dfd8dbd116982a33d95a38906e5456f5483800501e7dcd36d3657a4d0afcb4f86074fede700c6724c5011e5c76b2b5b8189bacee2f6c8967d670f81948cf9979a9d47fa708db975367e4361e2a67429879cc69028545d3c71f2e498121cf582903c8ee7712595c0a215c66ff5a1b947ef1b4ad47deb15557b9c89f631eeb5a9e1bc29ae3e03d7091de25202f6d6121ee85a321a4f3a4c39359a9ae5d5170a11ecaf859bc5fb88df3a4a489e174593dce231a243d42a19e00078a2d67f64f20b4d5b0fadf257a829fef0c83b33a2e7ac0457c78a70c33a42affdbfb26b978f5acd150655642f6889f6a35ed527eabdafd60cac46d6608a3966482e2ae0911e90c5d197ae7a687d7cc91862858ef0e085f4fdcef5b797f80dabcc1630bb63b5aa32136ece549fc9d983c773d7c9e00a3e794478a917f91b3cbf41c678bf9b3f4abe3c7904b754d56e4f2141efe6be51e6f74174954d836e5aaa978236e5c127fe136d0faf1b1fcc3997f5ce67b4258994b30df91e62395c2658737adae8123c918abb98d3b548231fc1284d1c5c80d634286b93ed2ec5f52c321fa38199fb8f9bab35334f79ff9a45d1dd78204424118334fc511da9b64e9a85797fac5a408e453bc8772244e9eabc5a372bb98884a3aecce050ddf1d626e340cfb8acdfb2d92108ae5005eed14eae4e1c65d4cd5a1f58cbff279d0438f63dc174ac3ea85d0fe7005ad3c5e419b7338c2b7aa428a4a3575133e2bbab9ab690e012e2248f58e0de29714c38ee181cab96b0bca1cfe6ca949cd8a96a9ebe1fc839d5030cf95f0c070129d10cec7a380ebbe2fee1e7fb7dfa2b74ae981798debe6e9bcb115743b71e6c24e8a9ffb50ff6964293c2f55e93b8ddfd0c7d3df2fc34f43aed4e9b5dbe7462a899438773b5b3010cabcccbba2398bd5c7439e77f814432dc777efda8de3a02bf0cc3b9e703cc5b76c7eafb4b348c5af46c22c832cc2a729e575caa56ce108c71294ee3e06cbc16f1665f584bcc9878c39cdf5170e532a172f6579f9d3e7e44af7f031adad70115e2646dfc0592488bd58ffb1fed15164e24a16e49b5341c61e12261f3991d28ef87f52fc7806f082c0580803f0c3191e7bc9f2b5ff431f01f19aa4b697cac29c9c079a47f793dcdd71eb9c1d30583df02073a6b53b494a088a5b0526e287e4f41fbeda694508bd4c72ba8cda99caaf1cd45c4b8d93c221aeb704896bc52a2518ae31c483089b3401856a079857e9856aa474a559204baa3701811f6bc67c6c5a46a0ce7473b7f61a65ee42f915fb4c181c30234790ab359cbed7f285b9566ec785f83483403f5f9f70a5d35888f97c6e3251a79b0928c7b8559034ce4a9fa01250a7cd40f6078e91dc256e3656dd116f1baf9459b9bb34b8d83141d9bf41384655f4c63ffb891098a131499791c937453ab9081fa787c8d840c1db689d7cd417bec357d56252e819fe8f14d0a26d52d744c7122a25c1892e55d3ecb38f5be4ffe176eb4b9a012a512d94f348c3e1abc18f90dab357491ee577f0180d51b91657efb3f1ecfb1a488c8d57460eb4803e443741a3e8c93ea2616db8ffbc629fdd1db9cbbeb8fa1772df995303be2b56a327e93588edef179a6bff170283661c371d4d7a995c5889cb81b7c4e2bda0dc5ca225751a566ae9068fe9343186b09683c5d273baa1f8b676a14c04279caee0321aedb3f3280ff3df11c67a6f7bdec15dd899b7ae31ade75bfd77273e2fa1e9d8cb085bc8b329735ed6a668a9f9af59b774578cb21c6cf3d2cc7f2e92ffa73e3a2f7d0fb4215ba832d458669718b6b11b3049df25384d4a55123aabe952aca90dd77a4496720e01f056a6d6218a67cf5523ae4cfce599984e03f015dcfa4fccfef15d323e32afff96a07446e66e8d24c91fc5c83db15ae61b507beadf8df2049cbe45af3b7c710ea3d5fb600a6f223ca0378341f61a54f368b41b5c0c8c98cd2d85086f4575945ce1dca759c032e7720f31ae83aaee52c7237b0ab53dd2caf3d4a85a14f7f322b96d5225ea967dd8e9b43b82e74d7e4e84a0e0ec9817053b4fda1284cb27844f82e5122e6cc477588b2b421925e9893e6f4907638af2e54749e8e544ab1491138ea817a48f7d10c7794bfb57c529b774993f85ae295e0f618b21f2b051ad808dcfb853743866b440b6a759edf43eeae1ba321b8de9afaa7675c4a0f8d88d932f7c45d9617a992d9d51c1b6fb0f44a22161f94e357a79f488df012ce1de60224d03b7b4d66f6edf6311190574d4b286879c55cb873f1f3deb49695d713ccc848fa7e68c70fcd8a033adbde70e9bb0016a64a002bcc63b820ed42da53bd0da73af4c32a63ec4b71b87bfc68f19a136739137650b18e7f5127b93ee7001aa6fe5ddc43debd42121a0eee50ac5c46c99776b420bd589d66aeca510f143c32f4bb6a3ea35fd0c5f4ccd512145e2e9c313e01fa9ca99bdbda7322673078fc24840c9b53d80099a338e7216c52301451bce02b57b1350cfb9dfa44405d318fbc7bd3e526e529de394f920b1dab64be611063e721dd46c34a4ea4776e29ef83787d5344aa87c9fbba9edda9a7b113eb6dffd8a13a4d1b5aa8c7988ac7205dad504999c335fafe0212720da8921078be4625b1e6e5eb78fd0464b489145d3cb91d6b7cf480a4d6f9c232198e4ec370bbc24a90cd0e70834aebbc51545e6aabc239651cd06e341cf6cc880c3bf83e78b53387bc7cacdd96075618a2e17784a7ec976fd53fbda7324a6caad49a475c0a87a21cba8a863d387820956c3f760efef3618e7a51167d581bf8d0ace2b2dbcf0612c6e4b0d6ff7e54f8fe5417565c7d0a6f5a34ed20578318369c78053750d97a62a8a6d5d7511354fe34212883a8b7da12fa5c962553437707eda7bed238b5b1c74049e252c8e54e53f532637e9e858cd2f77b32384ce705343f5cbbe3071165f55b46f6b6e26312c7bebfac29f5ac05a78ef29217cc04a39226bdc614bdde8c5d8c359c64b27769adf135041254e9d749548addcc885a86ab671a26062c3ad78044a1b8d89d56a03aa4147a9b23e118e48dd2af3bb8d01611eb6e5e9b9700f9386606d7c2609a2858f982d455cc43dd2a832939f489258d48ad301a567f95d564537c04cf64d20459def9b4eee35070ef89ef9647048642c036ed91fa00bb45f2db4f2e91814420e2f43805488548db016db18ab1aa9324c500cfef52a2af846f22a14d5acde65c1b1fdf07a7a61a154e69e4328d4bb3c5e148e1a5f577e3eb22b7f6437366489a9b299cb06de1456ae58771f0c26253c246f25f5a36ad5d2caba93051c9746fa80434da229da5b1267d4ddcd91a7432b561e7f8da8ed7955f3826df76713b1e43d390046ab29947c3664dc408637b67538e5b2c53bd0cf2c928a9b04fb68e77fb1e00472975ee55459d13ae3402880a36a6d8a85cc06cf18e3586464a609928e35da10be07849870ff02dbeb93d99f5095ac2c2c1c1cb12eda7c9ca255bbd73e787d0336914326e325c5dad56a81a90b04ee454524a9e4ed5fa767d2cc21848fb874586dd521194db880ac010e148928020c8981de04db3fdbb3ab73ee8202295c4e6be7f618b8ebfc891a9044f39cd9ddcfa65c2f87987d9cfee6c302aab0c06cbbf0c791239894144f1acf6105da22019ed446017cea0b51748ee4386fe53eb5e8487a6be7162a31a510282b99b20722de3673dfbed642301f3f3fceb26b5841cf1ae5ab86585765cf455bcdba7bffb76ff82ad9db9aa7e4cd132edd2653bf8fbd1624a26eef991add6e12f1f4077dfd20475e18e1c04d93aa2686305b4f57e4c85ef2a4f41885d26c5efd6b57e74e80e268d84296298674d01294b1fbf8b8704b55b1e296961efb37e0f8abf5d78a54744a53ed60eabee4d2a184a193fcd0ecf020699bfcd618d74b6bab96ce2a964a1bc1f274d84a360f8571deda3b149236e3024e0855ac21fc658074306b8a4816687e27001f358d254fc095561b27215c4c7c547891d652a5aa5ff1ac11ad6dcd6248fa8fdd3871fc82fe6c64f8d826a041ccedb5b77a2a48160414ac3e0b1b29e50caecdbec706838b34d81b32638e2790e9d5bcfa97403390b41c2407dc97611b2a5bbab7ebb38071ed4b99585075edb6c2ee1178ab33e9690a0d277971f080cc3a1bb224765cdaf0620cc66bb5f897e512678dc3f0cbe6e673081c969837c58de083ee8b46676e66e178dbce21570bb5eb1efa09d5a8f7c74e2781b7357f795d31d337ffc5f39c558b128df314a1cc16bc0f3f6f7b8eb28645d614106ec0d5e2fe7f25569d545caf90c281fd728c03b78b0afa786ce19e6522555c13846443e283fb91b87e34be37e7f47a98ca19a462eff4b6118d5a380c41777177524ccad90f2374e6f162c6e65eb24f65a3c323c837cc79cb2ee1b9c8420c36ff6adcbabae78e9b999077990d8dd36696e455ea292ae85c4b20af1a38ac8d72df6a3a4eefcde643cf522c916ccabc93fbae05404bcb38cbd54790cb34fdb1e63ce402571d150674ef4480dcfb1b2f1dc2162aba6718d21f1455b5436818569603fbfc903bf2749b9f7505e47875ca063a61a0d4c9c011586e1c6399bd537fb2e555c2e5350cad77b4cece236b8cb692ca24410490ef36cd5dc012e29473d225453145fbf96b526d6402b26b747fb16d559756a8feade43f85fdc18de3c3bdda2889836fadda4a7c629b3f82abff1db54af2ea430c60d7f82c598540c899d3f1d9489e69fa493b1773ee0ca734b93752efe42b8acb06b21c9b798605cd8d0b064a627428c7ccdab9c85e5e5c7579fab57f97b48d12004c7c4859a32dc173e33b5ddf171fd61279b31df4d160c7a379d1e477070b4d10e25305d171a91c2c13d57f48ce6f7128e41db8058f235006cdb53b46ec7bba4bd9fe46913fe0230bd39fefc5465b107b015a059a8f6f22ff2f194911f82d31dc2f9d849506fb2bf0dc71f7c72f535c944ba3a4d648d493515b10e6807fad75f78e95ad38c86b7cf6ea6dbb95b50ba8239a307b701045d19d121e193f348fc6dd1ff892402ba49e64ab68ba458583d274b19df6cc2c95ff4cbcfd7df803a626df265f70e7de25bab3c5ad62d61672f041d69a278ec8f02886c1c2b19c7653a91529b9048d29f7fd7a5d51330817ea0a4dbfa8d1dc0c6e5f831b60136fe7e33507ffc44057f6f942249531d13ec6b3b6fc8fba690e2a3a6d2b9a0e7998af66e90f7c76a49b266812dd90d2f743855c1aab4dc74fc19deae5f78755fea55f5d1efd2253eccc8de9f7feaca730003bf40772c548042e9f87fa2b7b42827faf3b8a0a5ba39b821cb17d36d42f4cb433d340965ba087384cada1718dca9b480350046e6e70cdd9cb3c00e9cbfd8ed0e5b8cebeb7bfdc91b3bb835dba4f7c6eb43e13ac7297f83db7aed3dfda18657c98da3f47b73713945ecb1eb2ca3d2a05688b36fd13615f488d3898801ffc9a8f9fe0b09ca6737fbbe0af2530004286665f8f54fda9118197cdcd6da304575edcb90cd7bbe08fac20a36dd7a7c5205f340eba35ed7738b0422a8e8d341c8e9ec48f650cd7c7fb08dc6ab77824e24db6c3852c6ad1ce52f2ecea2fee80104c4edd4d8f6996b5553c83203a58f95b40feea58fc3a2e3b80c1b5882a7f6aae1ad1efb62b5718aed0245d051202875eade4500d8d4fc3d21735d1654fa5e508d7599133c6114aa4101d860f6c80498d300dc6618594c2239af45f731e6d4f2e25fc56473dc2d9e5af6f8aba9dcc219a21bde47690d4c1b4e41e4ce500680223f29ab7679aa4ff7e31c0ec09b325d3a49b8a901bd182b1c6532ddc869398e548a3f1fe578e22f57ef70d5344bec6a377ca45b6b802032f6b7337f2052f4a7b4cabf0cd9c4f8541716d3698e48ca1a2c32b9152e11f84da9de6645e5f049682e7335030b8174d9608b51bd4d57d37ced59b849597b92067efc55110acc32606426a9cac1e3b66a0e8e444efcef0aa8e43d3a1f7a64ae5ea2c431b2eba03c51d9dd7ce189b447eb3893eb6f126416ce8431fca1a175377060a35db6e3e92250e8c1bbd004ae825f5d85340346301886705cdfb74aa8f6584b0bf1199533efa28a4cd80ea321c94c3b93562f039fa836f2bb6fd0553ba805d96c5bba2b6536efa930d315a0b4c1243560061f2057c31aa73877a8626e0b1dac2bac01f4ade25f05a2f90fef0ce211ecafe8d0d3a8cd66afc4b3eb6b8c954577230808d2241c73b699bceef678a824891be1f7b9892c24b5ac10432d9ab8361fa8a31551c0aae3ef76fd76650287b224d45e2f1e31bb36b599c04b9b2559fbd170baf6c0244460a1bb49b47fe5fa811ce33557ed71fac739e5578bf833d20dba4257a71ad746c69206d7cacca1ee7d36485c95b2696d0c8b7e82cbf5c47b4318534f4dbc06ec69ef43322a2f75a4a8327bee2064d6655987a9f8e975360f39e5b78b7b5f2f7ef9b2b01cc39a2af118dffce46b72c14a76b159ab8e1dc9cf5b6952c89876da3b476a410d2d27dc9684a2a4c378d5f10b4049730eb42672df5554ed34b556e26c47a85b8a2bee6c30b7745232850b834650442dfe44029f398199b3f53b1ba3d6b01c2e3bb7f3319dbfe604eff79b86a1ce73c54d7bbe69aacf81ea03d0b62be2cbb1232e65d29faf3425b384ccf82ed61c6d745772e792363ce53945152b23eee275b0a4fbf9975ad3d8711ad7d37c4d15e1e5c150826c68f9c53cc20f7aa92aaa19793901502672b33f8ea12bb1d3f7eaff338e308e19f00f07aa8722bf88e8a671b16ee1d155f75ec3b3a1adae646bfcdfbeffe589792624dd351ce11d382507a60b7c34f9db2976612018c4e52e6ae375fe87ea5b70dba85735e5597a13595e3f63675ce0236bbbdeaac91d61845ebb80a7a51032369f7c80f7ed9e599aae8151960e65e5420978f3f2a82eebdd73c1ab7e254bb2a88aaa237e1d52699f224124abab039cdb80842bc808a0b4d73a0ea8aa20fbc80b26c25fac01b14e7bf2fcd0d2e59247b4a9d949815ca1bc077e14fbb942e61ceb8f50136710b012b22f839e4cfd8ea1e980291c4700d4482a5e63da10ebd0ec52a35e46c28357b834e73bc541c77d58248655e9592cfec1d6305d371e3ecfc28c07dab6eef812bae315040a7f4ac63aa5644b744a9f188a6ac386a04d08b18bfbd463a55e8940db3b5b8f3821875367649f98fc4cbe52adfc2a43077745e50a80a675302af37d8eb35590bbbdbf1b9d50dc3a38f4d1f12d323fae1ca4fc98eba1c39e580bc7f6ac81f53fab72aa90cd297a67f138e91ff70c238b8a5c9ddaa6aff3a1305087befc84d28159f5badf153b85a35d562c61fd3facd4cca2371e88e61148a8eeca2d36dd36260e5632a03500160ab7171762900545e18980209be509a60652726fc4ebddd26d049e80d689025bba2956c2beebd8138c848c115edb2fc89ebe7f91eee6bbcf3fa7925e6a73a8d48b5858287f341d14898572e467e954973e0cb8f3e505f87978eac5d2661dd37af247119e9df6254921a7a2a266153d1d80d0ac5e87fbdb58ead72ddf7d6cca9c5c3defc06c4348dcec358b7a4d49dc71ab8ac228f926945295f9121fdaf194dca84bad262ea169b191d996fe3d57255b2879d6b4d7a86f9b78be7722e4a47f979695223abe0cfea57fb7ee53276ce24f43c6e39af2466c9002d7e16f6638129faf3fa7831782194990aef0a3a82ab0b8eb9fcdce42b617966c16fd11d2984c690592045f7deed9c3dd1e32c3cc089f61f5f25cce606cbc019316f34d5a539b08289b55492d6c484f0a77fab136378014409518155ab9f8cce447b32f565f60c3b31086667ee42fdfde3a45981e46c760832b0cc417059f278d05fe800e6956133d907ee19c71fd558ab335700a9197e0a1fdba4866e6e33f6c6d1b5258dadfab00399008de2f023c17d6dd4a8d24a2e7127eccb1deedbed87edea455838c6c74c711fcfe21e3fadceaec3c293fd327aba19a1c5e90d343d10f30de7e17f47c9574da02f94e61b531d7b51fe62b0c09091de69bb377440dfe115f0c5548655bd289fbd43b110e14ac2968b7230054c8f4220a51424b39b28e3c13852590ac9257d7799bdf9102c4c2a28f796ca7632a022e1b24426cd519f1824479311b36c78167b05f30ebf42e6a1f0bb09bba46c7c64f2f27038442e19ba27dd4377be793185790923568d57777f7f0fca95ec82c7d7eba9869814d36041e28f3847b0d4c894fa1d888ba0637a261e2e95a6fa5fff44e0f802aef9ede54a2ba3649fc1cbf084d09313beceaebfafe004bf2e19d1864fdd396894e51893b62a17a36ec432d3401e52549f62dc1f9b9e346fcac72ee38dc48ae315e1288d8c93245d4cd497630be8880f45775c88cee35fff143246196545b79fca31b05ac054f81f63ab73e288ff3764314dd9d788583e2eaf4fffd35451de6a48de9b1bf96317afc9e430da17840b54d06c7d636bdf664d80d55c2060ca41a35c423878d20ffcf628e585969ceb431194b89cb00aa9878a8f5dddafd7f4e07846e9720706fe24c86b06299e505ac3b740b3b8e2cac592bd869b3c58d2cbbbabea79be1716e7771880cfb5b30d5052b2f9551ada67d3dfe49d59ea803d6ea2b4d3a42440276af26025ce935c6a0c2c4fc7bdb69a66f65753a4ca41e35bdac2b57461e8910a0491ccb6b719a84a6f5dae0c90bbc2ba13c1f06f369619523182679ff97f464d711af1102cfd4033ded00e2c476d3b3321dc475c29957a60320fa3bd82ef5520e767ad5bee0f537ae522aa774c7a03ce4d95f63c5d8e7ff6166f9d7dc7370ed25390c6be781605b299ef6f76ce7ef674decb9ed93ef58ce46138e4e2d9e883564fc2b8b3842a264297b7528ce7cf1329d51a66fde83e74bb08125fad60d818f411f8d820c9ed14628d43b4094ca25017fd6d416f5367bf2d3307d58e0b7ed2e97408d1b6372af47699886d0efdda55a5d15b5a5c7201b78d1c0774a8254f57e177dd9995de7d08954d72cd751a9ed9d51916a367fb8cdd634c84c9bc5ca1e4df008912bf559ae2c8aa340a5f926d9aaa67e9fbec7acd6edf5861f3fcad843bd6d2c2bb74b4266a3a80fb2e03ed095e539e398feb6d322cb90b24075e3745dc5f591ffc32a21f9453785858e6987262b2c3663bfe1c662ed31a17b188777b1ccb3077b6c0d82c641571b7906334482dd187e4df744542ed90c4dd9a233e698436f649dfb7069863f602546ba41f83a5fedef192c433322231d696e24506e548d2549b7656aff4aa38b1ded0f2dcd3ead9e169c6e7ea25db5702bfe56117111251f8f94fd9642daf54d974f78a33f486bc93efba00eaf5824d40add463a60433b44b5ad553b87ac505aea5df6a6ae7d872d5f2f17420947eef2b55d2fbd07491e1a63fc77cfd934ebd6b0fc6b545464d961871cba3af68aa33dcde27b7f3aa0218fe8a7c55086dd39fbe6fb112dd2c46b9a55f34da3dd37f565749f4652264fe36995ce7f09d1a5e25b860e16197a76187e91731b25f201579fe9f6f505e1ee6754aff57fb65925cd5928f6383d1e52bbe90239265319300af8a2d57aa29ec1c0cc2b0bce5855cecc97a71187566d01b3a29c6e32f4b41c35cc72a93f86b15e3efcf16c90522a559bb506548aaeeb0a5196e5f2138b64d27cf27e23ae9c0ada4cd8f66be9d712d1d4500a312128e77690155bab0c5e12fc63332574836ab955a409f17103bafea619e7bb2bd24ee71d0579c567cbcd97e4bfe393fa5f9e7348d5d4f435475dc7d1a84abfffbb9c1cdf60276f2a742160588e5b1589e6460b9e64678798f4be3ec890a2895f6e0f67c4fcdd6ce6fa9b9475e5c86297d41814dd754d0fbe23dceed605f40d363c795db23cad7f5c53cbfd25b5906b02eaee85aa88d247c4c785481d1febec3f594d05fa4952555675109a09dc8fde51f8e0bf97dc59beea8be53b5c5191d8f6d7ac78c495ec93f3e8f343adb11d40df420e14ad1151191b25451521e53b3112a8875b319c9386b1d907752c3123fed2944299a61ea67c158a3bb23801c8f5330be4d7c94933c6ba38fe65b899e07e87f26ecbe20c5c6ddb0ca77c27ddcd8989566414039ea707869415c68ca4798877a4454b1aae5c7f2a978811ca5c669d5147969613f84a46fbb750c50818813ec1156cbaaf7d05c26fbbd0af00f5f3ec3248f638aeab6a2607aede167be87efefc93860c98436d653a0c6e28898118d3085d52d4edf3d750c0af0d1060f2703f54b174a3be148f8c27ee20870dc16964bd56fcff05934af37d209b9b54173a25bb2984595ea321d24a75889ee021208c7b9ced08b1c7dbcdcca8b446a5ef84a7de2499bca6a819872624258b478de813b396d2b2aa467bb16c1fc4f8b9bb1f07246a082f0d8c3696c0333f6a554ce1eccc0479b05a1fc83ef9577d28409a2b644f41151ce238597df34a6ba6b56be0e105c7b27562cfd0ba1c25d328fe28f8edfabd87503bf2365d9048dc6f36fd2b679a1b15289a6e6e06a66f8f98f318db3325f4bf715bdf6112f5bd8b9ff308a8165aa49ff0a576a9781c76982a03a8d7ccbb79964cd6cf20e1720c5b6b5898cc7acdbfdf6202ac35c27e7bf0974852e5a5495cb25e4a8cf760c39367c8d4d5a20da0b92c364488ae062670bc925b3b0fdfa489034b422052d67d6fc570d003e16f74dbf26e2294dd8caaaa16e4cae07fc6641d29971a51cfcf111fe07771d448ffce9ca2678a275c7d158e58b7afc85bd123462530b6c149b2065646a7739b23c7d0ed5bfb087249805a35a86b98cecebb3f9c74a1da1509a2443283b2b7e6857cccaf47faecb52261d819b5449cbe35af7617cf3659221e327120d08dff0fb9a2bab8a2dafa2f32293d013f02ad82010a8373895b0bda8a0c9ec150605e17eb7c2e31214e6f952236978760216389561a750aebb2167d618c63d117eb0c437254a1d3981714617738540929549abf7df5a82c38c68a6fd2afde0c9b5e8f97e960885ac3bea624eed1b826c65f7f7b01313e09fde7addbc48b122aeda544d556c687615b17cb2771a3692e729dda65dfe4490b088df4d1041c6723bafe20009561f04048ea0c086c9bc369f04ab721bde121d5f11f58f0cc8919d6d753d234c37b82d7a8c04297b54a74e55fbd34a7d3b58c96a97e21e5c126ca660dbddb4522a436fec4404086421a5f66bb075bc263a91501f7a99cabed7efdb1563198e2464f1aa82993f6e3732ee2782e9a9334cc9b8a4d915ca7a8343563f908384c6883a16035c10934d1bdeef732df526ff687d922f2a8f6fbe050e165dc65851ab2c2f57007c678bdc647a8bf275e0c6ae7d195a15a81591bd50a80bb74a0e4df18763db4ece1d0dbf2eb916587175e276728bd95663d1a977179f23d9b54df7859c196ca4573417d42ccf6320f4a279b2a7a95d0294bea28c994004d3d68b31f44ede581daada0e8b9787b677ce029d4d512fc579c78f038363b2fec85760a54b07c8f7402e8209a42035ba79e3b89cd299bc6ec120468881af55c4f21dc587ed6b80ce281d1f9f88f35271d8d8e79325c6f84938b44d8726e8f0b0e286f157ec313577a9aa9f07dcec564c8ec629beaf4d8f5625bea41ae05081c5b5b8af0d0d2586898a09391cd603ead53a87309f2c95fac83d5c79820d044d9c6a74d081324a46d737076570a85ec08a78ea30f726ab882c7fa80fc6753ffd68d3ecd1de25c2f712ca674e6f95bf23173c928ffd10d66887edcc97a07f1e870a547b253873fc159f1edcfe4d796591feae73270f66e2c0ef69fe5d87e56a3d361707fa0e283869445397d2ba07a4eb788b306031a8d92f19809464d8e3b89080e6a8bcdf3a6dfad3f9c1e39c809e8cdb56be1a1b53d1e5134bf30aea79af4bd806c5ca87137307ca99f5e6640bde69e911dbf98826d3bd9c97470ad0c33f20a4c258611e74cc7346487234ded7f73537078f980646326fa4788f067e1fe1bb7a0c3415e03087d95292c251cdfa3aebbba40e20d994480ab18af933afa8f4109ea78de0e12fbd08424d4e2956a1255e9d7f503308b25e00f0e5dfb41e6ce81cda2997704cf0f20b4119f4ba807cf37d80a5079d4382ccf0eb26d45eaa1ff8d6509744b91cd4f3d31eb94461faef3511e6b9d722764b3f12b31eb0bfa11b06ba2381a61bf27ac0fc37874dcde5ffd20be571080dc99bc3600f251c141fcf9ae9c4cb6ca7f2f443c87a43683afca22f58d36ed8e38edbbfa06319317d3386c88c913e86cad26e460d5aeaf32460e4a7004aeed85a5a8f521d7f7ce9a0df796f79095b06d84fe45588e760bafa6b676f57120e1f022b2f2a6708533241faacb39f353c03b8efa5a11e15e93737bfb122d240e1e04f292149af4a0d477e51c48e28a5ff5510f3ec19033d33d6974bac479cf5a2be3e584fd6d17ed60754ef99d10f389ff71f5174f6e341e06fbfd8e9cfe71f89abcec86505c39d5b3cb86c1a989cb88cd7afbf856a76d6928813b867e9fff5533e77243a8fcc3258d56083347c0feb0d245e67f3aaa1a0facf3622108e54faf9772f8526a9fa6c7b202c2575b15b924437095dc097a7dd29e5106d13b785f0f0ac7d9ed835b4680de0437a5eb9c687946fc4fa4a3a9cbdb60551b62e3c91591cdce69f07311c3ec965bd92881b0ed115be36a5660337ce070bda5b1c554fa6435b6b36cb9032d02b1361689664c7fd58a1ff6c174361765eb8dd94cb63035809b47f97b171c98cb2844c517177f2e2c8a27ad15edb06e19a49e36d2534280720cb3ac453258e96e7bb83cf37dd4ba778c2df2c1d22924a03d275ccedd95d3c5af4275e874907cf3ba24ecf31d39cbf09b255c003f05f341b9b1380cef2e684bcd6c602130b00bbdaf6264a191deae61384ef33202ce0b23de167eef1669a0b34773ef80aa351be5c25f35a19b932bf3defdf6937c48513a8d62ff69d6ab63dc7d19e7aa4b44ee26d322cf087898a42359489e15b34d1773e055a1e0f83d58c83fc8c7310ef308eb5bf455b223952da98e952163b2a7b4ff4e170757d5a4463dd804f8aaa1d8c04bc9aeb8df726cd7b2800ab9c0bb23afe3e60ce46a4b83ec40c31c06f2ddae178de736d1d57244320e21ec4bceaf0197991603568ba81ecd77aa8e7445926f3acb4fc82e15eae0e5c2d14050afbd4dee488a19c7be115d468a250e490a2f0ec48e7cff4f8a1723665242cd78772194c6afea1abb86cf9d530c63b03c74e39971ffb48508eb4f5dee0e0228cb4cbe7ceb335f439d6a030d24610eea604db4274ad161152411315891fb1ced9acf26800a16ee4c7962ff11df4190d0fbd42b81a7daee53b9df41d189e4e0e324b4cbfb6e35a67c8d9e56c27a300d218931c93cc4eddfa2ac561194a1cfc94ebfbd38f047a3609249c3a69c7d84f2fcebbedcc0791ee852abaeb3fd259508fa9992304459e67e0a282aa84079c1ea57e21bc6f2a49192f6c1d810d9ce50d57c62d51473670f3d0b19edbe0f35f7f072d11621b71698a209dad2831985513f65c51be9ca26e8869fed7dc91d21a94ef400b4fb36df879b7375421655aeb6a4ad7b1c89d0cac13c82c061bf86e8a2dfbc60955aef6c8ff0b4aa313ac8e316e60a35d577c263dadf29b3d35543f01846046bfc082fdcdb8c2fd89fc4114c8a08ae59a9dce7eed94cb83300e323a4b8981d553bf113066acc2a7d715483ac22496b6bf063b6406a78e6da9c9fb0e367a763138ab8f2cdab5993e5530a9d5f78b427dc277c95aa76412aa8412a446126c8db72f80bffd688f4a937db61e25e60edddf973905bdf5e921ecdf740ca97a409e2311cdf6f942906751de577dd952752a78ede45eb98645dc0bd7f6e2afb81ea40d03575155ffb9ec57a078b77e574c253bb6346d010d8aa6b1cca46cec5b0b8dcc8c2a924419c4b3617fb463c0759d95a6fed5f6d77ee750f9206f992ed639e7e19b979e63aec13a7c75fa2b46505d41b285b9088f82bac082d36149cfb4282111f744d303e540e3b79e49867fd06ed7a6a200771a72173f393254ccbd3cfd13fd93f38d85a1fee5b87997d2d88c10b46b495646776addfa7a8b804200290489bd9f4bfc1ea12a382085f56683dc4bfefe997be819dc63d674303d24a663cf45e8663c71d337e5f1c7439efc9d2afa878a9955117b5c2a7a8cb4029462afd74254753173926d3b5fe7b1bef4f536b9b4d499380428d7c99bb7c9571bee659d2738ffae608d0ee3fa1cce1ba06e5fed9be7431755f258ac0d650aca8813531245aed4152f8007d241b3f6275eabad6d5ca594da158c259e38bd683406b93f815db7fb589b23be8d57adaf59b7f4032a0588a384c0911e895b93a95d97e382c9d97012a920cae7712c9c1d724f64db5995e1229c3504c24028191c52a282d108f310d1c75a6a21318725a23bf8a0dbb6b89792d1efb407d353933018c4bd2f71b9dae84590938b6d8e607eaa4d7b79ebd521bdd07e29a3115ef9be5559b23f2085c9df17af6ed479bf16a80ef444a1015372c029952b9ad50ff164688a5adbc1ac7db043084a08a5a231576567f74c1ca5fa3809668ac0d0986d07bd3e7bf230be82f701a9b3b903a30c2338d8557fd3079ce602bcbbf2d588aee3cf8d0bde549cd1ffb9a3cdf4e3b23e83d417a122138542b4d98fe2aea0598e7aeed3dd7bd2f6ddff5683e0f160b53797997c882b3e1197d84ea15d81f9a3b38199b38c9efd3f1e7e4e15334a3eda118381871b92b8eb5b45a104f114f980f36feb983af89f676ae6292bac16824b4953ae7b9ae221a6a093ab2683041e525636c3798a1503c587a6bcec8a389a3a6b507cb630aad2fcf18b347cfc02c7281aadb9c98f7cdcfc7988c278810ddbb6894815f7d857a3b54ffd1cb53d373adbf7a2f6967fdae6512d056fc4baaa7015f03e15746e28eb7c44956924c49ef6ffc48a9a9291efc3c55d48ba5398fd9ae50a98668be85593838eb5c5248a22d701d797132009530913d07676808edfa55b5d51133b9bf053642d5ee7ea9989c4397669742784a52cb4808b74496789eb240469579a970d672c45da9cb34f83af5e187878878c386a5f6ed5dc76b92ad1db1c9c3708e05593bda581eb0f7f505b5225eaf3a022fddab466cef49f8bf49e56ea755dc8a4f7dd42cd0475ab65060ff71f72bcbea09e155d980e847197dfcd23e4a5e517ecb0621db0ccf8b5f7291bcc0d286d6852043c6f7db71f2b94374e77df345c1e4a56497df8b5f52639186677982516037b509e4fe8c088aa8af3e1c5fbc7f68bbd77463c57b873291f5cfd742f9de1df476c7b035c379224b3e1976645e852d0282773dfdff1595671874d914d56a6afe88f5547c49f1e76df89802bee63e523dbf7856d6f35964a2dc50b753a0bcdc896dec8d61e77a8d8bc7443c938d238642c07ca1e20a1151d6b36142f984a551653562be4525cf05f91a819ec3b5f8b39b65b89201e7b612658ebbaaadc942085331344bee879ec681a70d39e31087ac80c19fbee797bd20533f330a501c777a8b351ee28d65da59d0a28e6ad0aeca083a73433478b9b54f4f3b03ad37baed74cd97bbbd2ffffb78f6d3da8e4093558d61f2cde6ecc2c59cf402aff83fcca1e8269ce5526db8009b4c4af90afb0127a1316be72e60aafbd86cc55d73a1ea21f56585013c2ce4a8a9874a9d30563000ef97ace6db5d79fca73f02f5dacd05b30bb1283c0b9cd931563fddd184a09f973542723764a3a4c38bfa3406ca8e0c91bacd2e03d1a504716f68f569e428182c91826a44e651026dfd35f8da5df1b9aa8d0f03dfb86e7578e322ebeeefb3f2883721f861622f4ba5fcf8d278136bca89f46d322847f252f49adb613b74f1747d960cc1575ca24caf68f2b71ce010e79db9ed7c422d14d5a4e16ee445c71e21e942c605ba2012ad1148a12885d3c8557389917142a052205cd07dda84bc410207a45dce97e70a085518536067684e616e7e59af9e2770f21fb5c47d6429860b705a39e2f727d45a9bd7fbced0d005da79ac5a7d5b34a92d02110849d9a7db4af863b6eced0590302ffe247e19cf6c85c838e235f88dfc69941fa68052f26e82553f41798481e65a7e02d19d8e5789a6467ee9d12999f127df6ec6646f56b7a60131aafab01224c14240feff808c110a114800d127621bde8d8902e2ed69184f8755d8580c8e167b39d0c3e0ef5abe5cf61ba25b74ed32900c25291aaa8f5b817750c81451bdda0f3fd126532a7ad896eebd8456a6554bcae6f7488722799de1fbb9dc78109a3acc9cb063fd2afdeb36bda22ef991be888e42971ec296f65a9a1775513b241488bc53f174b0fb57372a8ca84e7d8e7446a662ed95e363a1bdb8efdde3e120a7300017ea530ad39cdeed42e83e0b266c984c4c0e897d9b3c7b7063692d6b28cbe663c73aa7b25f4159b053a618004810c043f9daaac8f6878f5336dd8cdde39d86e6342699f1813d0cfa2d4122146fd403c05c64fe5b47a19caf5773676df3044e75ea43d7e83c88305b79d5cabad255b132cde404f5d29ad073e8bd9ce342b4f6807f0c387ef6e33c452bea038311723fa8a7ae480f4802c2d97790431d438d584b93bbdbc2c8d30c9a7b8371b6ed15db094587f709b42d4ef8629f7532e914e8c7affb90a44908338df406ce0fa3ba525d63505cf0ef8b2f109fd6c9f9011499c0c922ab946a64503e7d6dd83c755cbc8c71d6316cf18dd1cdfe53d483dae4a4c0397a0ac036c99cf1a772fb0d6500b2e1d448daf35ccecb66df3595b015eeba1f9d61ca679daac397319d6ae6e7c61d7b2cc57ef08c6e3f574757704fb8d5059f1871ea942e0953d9e6f492e98baf326a5c88b730f2221e71bdf3bc6f79ec1703aec0f951196be608a18d806a097ad337f3b4e073dc8b0b3de56a19320915025da81beb146abb7e0f5ffcc1ccdf17b5d0f6e211b44a1cae2a583c7db6723c5785a0e3c59227738b2eb96dd7aab7edc8244159d68f290df3b47401ea127e157b2a3d105a755d30d8f1ac2e816f68a03369d184c65127493c920ee0f8731a0c60dfff85997a0b033a5b523956a3ae6f004625118ecebf1f1be0dee6ad65086fd4ceed90b4963a0f25dc51a566ef37bd719f2cf345e11048022ec99f75b593f574f4fa6dd8d70702f11118141bf65d65c8412d2423b49660c91a72ecabd164106087eeb10746d5de2aaf4624c3e7dcb0b9cdb2992974c96d607a1c34bee20e7ac8268d3ac855c7d7b172191d75fd88235cffa40eec24d0ea8611f1f2f12945f08e4c9cd4b8b745bac9dce5171d734d942871372010fafaa685bc08d3554fe993930999a791f25bfe1e49bc9f7ec4efa99dd0387f975856401a8efdd4a46e9e7b99214e349243a7844c6f959f73464a09f5268c36642e8cad430a910df04130d5bb06729a473b187ba9c526d232a9843612196ad79dc8ffea458ca54ceb986267b3f97f51d16e38d01849a4df68ddbdce60a5d2d8be48a4076140d08bd8b4592f8e85c0554b776809d32bf1df224302653bbcad04abe7bb9492863da3a379d4524838726a17eb4708034908d12698f7804299f78cc597000257cf511feee6f1fbd28c7b43dbce32d93f7cc74ee276a40ff51316ba85b946dc08ad71d85143a999b3fa71b10541840c272cc5c4a18a259b6ba6044dc5c2001f33e61510e88b2d8319d88aa52e063a4846e894ddf24f8387cef925ab522e80aa7f0de5c70e449b62893b0364e5379e11dfa109463e84e0f197a9cd4f0f028b5df2f595d11b0a07916e059fbc5fc9644f827eebe9634e6d0621b86609a8c63bf5853cfad1ea0c6dc6a5dbdb98726e924c9737e7323e3ada56c5f7cf9a52922ef6bef0b3d17aa281d573f1ee7316c802138e213a2ffef33a6d1ccac71f3b34f611327a92fb9d4a856931c6beb952860fc8f69ad0a3cda179e2cac2d96d15afb7b151d3a57dbe3e9b761af2e91ee5ac8f545dfa7f02d08f70df9ec3e5a49925da1d8bae94bb5f52b49f959df8914be739c918d8b6a273fe4b0799daddce9128f5acc5533e5465fd6a32a2acf229408b0f4db6a875d1f3a616484cb57ad77b2761382032cb6da74c5f6df8641ad82de841e889652d4eb869951a3ac1abb3473d3b3f199b9ea894824dc1b01fb99862b013934e76a1e4508708dbc8c9d57b86e9a21236d8bba437ada57ef2dace375864d528093396df50821f2fc532675d1c7b483e371480126feb2b1e8cb0e37c9c32c29ebdec51f59b4a7ed3c3b29233b58f481f8baa6467cb031ec27cbb9cbd18e82a8f3f2879ced71ff866a99a947942d07df731418f06fac785b44d874b8f5954dbfaa8ae2882bc8683ce768ec58b57a57c85f38c3d202545dadfd2d61cb9f4db3f659819643bf0ed0b8b178d9e99d720bb0f47609a0f49ff3393c1fc8f99ed53cd6b6c55997afca2c551827f23858dc57972c4460b2d9e747ee6159b8cd2ab92a26f0acbdcd951b21b2375732e26fcc914d3b4138cbf28968daa38c825edf0f331ab1ae74adaa72d0af841a62be031e6bdcadf37be3eb7ae11616eaa2760d8ec602d1698a9699a90a93f717ff7b92209514d3c037e3541b52cc6a3cb7141cebd3819d746d9692de2d0bd7603982612c0d35eecb5ee1e5a540f36403fbcb482994b5ab35ea0874472e19ec5f2c1a1c65de879f99cfb48c780758fbd9fae0cfe308d0e0e8b7362e708d618062b5dfea0e7954934e15eab6816a0f7b75e9bae38f5aead1817f9cc6582243abd97c7377f5b474ec7082d35d6bee7ad5a8fef612bcc60ed79b1d289309536696ab00b0d0d6676ac57e837b43d214cb8555387c1e308aae325c653793a215cff837b1e76ac490fd457998c69e5ef9da147e5a692730cc508cc0de2377044545e820d6252b30d1e40c7ef6cc555b545f50ef19b7f25db3faef58f16b6a17966f0c1c33e1aa7e6bc6c6174cae7ebfcb3601620d019f676b26a9a9bf66831de404e015aaa02d9f124344e35a760b04a955e50f6210ceaa900aee42e8ae33740d0ec3580a512756dc6d1c68513692a5b633b47481b615396ff5ed0aa52c5657e55d3d637b5723facfb227f6e38a1bb16e409001ba07d5781bd65eb91911bf5bc0cf627767a421c47ca70dbf25de689c368bcf6795c0c10c58bfe61f52e1f6dd94fcd64e1897b652c074d9d48b39510036b4e726a6afbd74c8a575913b5869a0613a46a2c477c3fba79839dbd21f3ad74830a0a5118a981222a6f6eaedb55fb195bf864e594ddaed8751f7ac6d6df69af40d73ecc878bb1f017193cab91a7e73c702e6df7112da6114cf65c0b164d95ba78330b0e5522565e5fddfcb62f5e3c2fddd3a33babda7a3046d47b1958a2ac8977815c0252548dabe0d44acb840d6a1941105b7dd133f51296b48c157ca4cf51c4833666880f427b7f65597f5c17c60656b3809837e98912a48db4be9f813634445df8d18291484c7e1817c637d85da55590370d6b4a527b96562ae9d28f39a7688e19ad41ba96a4b521d19731e59ce9dc622bb79d01652447433f1591050ef92eaac9554df6cb608eb66a61a658a08a8172587a9f1ab8cc6a572e8e5e9773b2a9f5c4836f36a492d8ff7509d726b988a1a3f25669968b4e57ebc489ec35b7fe0f85b6c15d648a6f69c15d8c5cc6ce7f6581543fafc9806b2bddf2ff295baa33ad6b9ad77d5968a366bbf85ed03065b5ee7f57c1aedfd8e3b16da28dcd6e4059dc201f1769e1cebb4d610e754d921a213c6fe8b509ea163faecad400558d1e19ddaf9a8310da9e903aad734b447b7d950383ee31e9fa3b19d5295749ed744bd226490a322bd0043561852c55d82d67fc73f0659a7417d861bc1af0a1a8447e4476c231c6ded086de6242559fb179f3c884601cae79dfaa45b178bec38a7f953e2a765ef39a9201195b96e19b3cce1052af100121368d38ab19776ee4096b3939cdce4e967c7c9df569e35b8b442def5eab69040bfe3c3d5150c69a95966e75789c41da3f8ab94dae724e992145ad735840ebd872b9db76c2c28dd999e76205ce75cb7052c978da5e52ddcb6c6f044debac3eb0233d802f5e1e371e230092acb99bc06d0eab7138f58415ff7a1ec19336745898fa46c780846835660ef3919492ddce87501e3bf58a529814b9122301823145ef702ab66a7099ab33d4c57b63aee2a004c63f7adb2d2db38f38c0b54c09b2805a2a1cfeca2c05f3c6ab97aacb96e8f7f96a3dfe8074cab2c45960ddeedf8db25fc6317a1d2dc8973abffe041f31b1d7ee2757f8df42b8d4043fca37d3a87a25bc3c43f7ded1ff7fb3f1a3b602ee0c2375afe8c3908d0f7c2761004d70a654ea5e0e7d5aef82cbfdc55401ba43fe8314c7fa37788f998838e217c68cca45ab19d29b02ef11b4866a23496c8ce3dc590aadd58a8f70899bc48779ee21beaf35be1b516af18e5e54e46c7874f907b239ca1790431588d646fc56789f216db409cd39d8f9f840243f4e882da64f016fff4ad6dd91fbace555dca06e902c7942aee9b535c8992b28a950ed2803edf8d7f0e372d1e07736f961d43eb438ab4aa1115f557c6d769776291045a78a500d2d84f85d1f061f8e527e3ffc58d0c361619ac7bf6a6bc264c7ad2f747fa8ebbdaf1616fd71a505c6ee13ee47efd9543aa3f979374a9372bb4474e18fc8e3fc33e7e87b7de2334b8ec0e937bca046fb809d1929c812ab336b1c3953e984ef601ef0e9f92913bc0a01ccae1eebd72cb79e4a91693bc8d67d227bc2b82e3a4d8dd75dff679dc1d8af9ec75ef7f11808737a7cd5c67f3ba67db69d3640c568112d15a326037d6d5ad6f1684e3b81c078b0f7c38bed276fe601f99001906102240748c810fe6a63239dd0f73e4ec6536cbb10ad204d99b33bb0c9e600922e3057322010d47fda060a6f53227ead174185b59f5fda355535948e5a2096cf4cef212e8543423c5192de4419465700dff141f59e326af8a8f2358751dae7725be7daa389d774b17ba503bd803492b0a741ecdf8eca974399e0a6efa610ff2248aabf740bc777597fb911308af2d014d027ac522cfe484eabfd3eb01375f12b90ca74bac6962ab920069a1788390590ca20837459e86af6b6646cfa940f31fbd6aadeee164738b45bb705cfe317db05378c5737fb2f1a319d32768a1740f14df64e9b4bde21c5aed8011c5e4ec59e8326bfca81aee519570fe8b8748b532bec9ed45a36c9e6bb788c7e990558dfab95e8788ac3e118ae4817af75c0f87bd729159766db15fbc473e478160298474fbaa7f6d64a7371e3f6d2371fed07741044d3b5ebac9d4822e7d1871bd9ebe7cc24189b9ee47e1afcebd4000467c4bf9d32d7e7b0c858029a2f8e90d141ef3b2bc2359897b5b541c9d545cdd914e3dd1765e3242b5121e5137525ec98eb55e3e93131d554bd4f958c76871f5a58954a6d55b77498b4bb63f12f7cecc26d09dddfb717bb5b21cc6af0ea7b89b4aad62c3064319ac6586395eebbccb0bbc270f7769dab7d24d36664ec127cabadad52252b243eb54ce5af670deedae9afe0573ec9187a98758e99055dde22c843a0daa767721f0c51b91e06bc91aa792c58882134fdc49cc2f397678f3fae63b7f6ea838e2fe3d2cb09bbd6318e404c1f474349797efd0b1d5f097980bd9ad33cb70585714ab8b5856a42fee3d00d6686bbce255a248a19360ec2daa0aadb611b7b0c4edbd577601adabad134feba17a89393f4f79a0612fd0dce2dc51e961a04d9111ee1cd33b76a6b3d2aa3f22b38782e75859fb0480d269e0dfac82fe75dc5453293bec29bc19c3720f7f9ed5cc5eb16c9e32ffccff9f2ae4f3f98f1b59bdd3f3afcbc8512af6b8c9258e843f648e4ad3e1fbb8f673b3f4bcfbcac865ea5e3391be0a54bf23b6ecece09aa5d9c4c3ca2963b9ff9ac5e60db258bc1208c1d92345362fea423b06cff8c6aefae97a092251bb0d70de11ff16f8068d24ccafee545f9b5ddc631f68166da881d86eee032912793c71ff43c74c24e5d868b104eb7e91679e51e4fab64c512fc637dbd0ee88d49d94e791733d29edae0b22f21ff832a67f64be695ff9e2230150108af3b9f83e97983aebc9f6ded26e33efc31af5ddae5eb76d86ff2c8cffd5ec95383f1b9d9cdd20e9525dae114a32d97606628a6bd1fb1196975551b15cefa1c75da86874022d39d5cd9524c9f21caa5a46a8264a56e8b7afe54f25590d75d66ad691fd4b41f9533f71c1f4bfc99c621195525bcd028acb31664c6a5b3f27311f7b7a38949b918de77cf24f2c22ffda65c55556b22278ef8f611fa660ec14f8c2be1a4bedb45018656d012e537a15e163c9c7d0bb6cbdb2f9cef690eca0bde0fc9eea96b69a22986346c8f2a90293034b986ccfdd278657cf55ecde91136e9ac9322aa9cc7655577580b5915b4747e3073ab6e69f2a54ab4e7a6b625e21a919551aeb0d137a02396dd72d2efe7d3f28f086ad35b73b208fa8a126040540b95c5863c2475edf43cdd444580155c20e76eb8fcae08f4c33549158ebd8ef98a771434c6bce60883fe4e9b682bfaac8b6d1ab4399b25f48d4320e7acc0aeed7b3b6ec0c203fe9e72e98f6ad7d0ffb379cef448444e3f2269f904782c615c0c67ca56ff2a5c225c11e27cc63f9d95a0dca8db046adf3037387f7392d59f36825b209a9297e1869f243b586479ae97667aceee19aaed692dbced3818db25448cfcc928020def352a33e0c38a1c64929db8dd98f2fde6d266f9d72460d14afd99f12857118b8c37bc3b1bdac12bfefb2e20ccbc4070be8144044adc543136fc83db67e9399e1476bfa6f19df9ea0bf79b9df8837e702a13ffdb7d9178997734178e69c13a8619f3c39690ddeb2ba98c5eec30ce911880abfb460f35eb2eb667b35abd987af101fcf7c4791290783c8addc51700a7ef7d79d633c047a2032011760f8dc9786e6c9af509c26cff58448aad81cdbb7cc44627e434b99780219853bedb434c12dbfbdcc1c11bbfb2c41aa9cda6d8adfaad8ea2f3964738d87a654f1c89068a744e532b4a5b519a099b746f94a30bd0ec8403e80865d93b6d3b3d8772b00add75fea1b3dd3e7a1c08363797c614401d85d8b3d999002363b52d0662d00acf72f88c693549fd9a23f8deafb068fc68feec855a8c8a12e4c27a0ccd3912e95fbcd0fb0631c6d9dabd86575c7b87f70b27f8d633d13f0310d44fb1c17383303c7bc11c2c0d76113f7bf2130538c218d5bf1b7f8793c183c9f525e7d41bc7d90d306a1806fff2277abc8673dc80115cde29d93b880cd653b9f21d0330874e9ad39b0de0d4fae6719e67a3cfd331ad4a492186d4159490efae90eb6ac2156717f5eaa2ccf55bb4df6c262e897ee2b96b7b8347787cb16a25f98e3d4c5179e1a68027b90b29f5e5a91cfeefc8f0ee3bba45bedfe655f562f6ad93bf13580cfedb9bd17ae32d63881d845c76e3f1362eb627f0a3684f3ca72396e70fce04cf2ebc1397a1e45f29b7f05514a0535374ed2103c7ef9f50afb09f3b25a1b0a53ced836dfa1f7ee1b50ab31c2be1245b5b15a32f5c7d086f2cdf439e51b415b7b3ab34ab3d37dcae3ea376db89eb81693db861bdb80caedbbfc0f2984cf43e5b9d2486d53aeec2fb3d8d11d33b3d7fcb26dce9f38c2453a2a4283f9e3752b0af0b9cbd9d12561f34b982ef11b4f8e91bc48579bbf15b780c1a3bc8de6165de35d87acf523cb153c3312de39e48fc64b59b79c659bf2232500ca4f3381f048cd8c35a2d180c4615ba77c2ca74582d90045c42906c44b2aabc5075555f1e2268b0f9e53d98067eee3b57e1c1656dc7542c00a7cf7ea1bb60215feef6578a76035097d3837698a2117e39646847e89e269eaf3438f941e5ccd76967427b20107b2c94fc59c3c92c357b032df50300db227eca32ab0d7dbbca3b9a8c19500f1c1e882020856083bbfbbd8385274cf91e963cecf2d18560a4cf977398c3b2277b13dbb93b8e723beb7dfe3c00220cc8fac3eed2f271456c25c284ad52561811c99637818c1331983df3635ba28d04cc392c6a6a73560a02c8b518a34c104ad95b04bf36efd1b9867621aed078284c6ae9b222076190172cad6249c044a35dbc0caea63d4fa09a7714dac56e4a9d4bdada5b82e88718dc2e8c1540cd79d977fb6da846101d24fd4b2b690f97e55e25da68cdb7340ec97b312767a330ac991bff58253eea97f273c56c7f1c703b063e56d19728e3e6e2e3b361febac3de5f47d5e384b213a4ef9a5fb62ada51ec80caa2967c6859c63d66d2c4c8372d5da97dfc3c8aa6acb2432edbf9b9e44c550bfac64a4db703ed3799e13e7043e6d04cd819d72c22db1dfff700eb00d4f857a8043919bee6b017636bf63dbee68b65af58fbe496f978a362d061adebd0471444c692fe6fcb3a22fef1d44ecac8fc24eaef688cfbeef92920caa13d90e3524b1bdbd1e072f71b930867dd95791bbe4db7ca20a0a23fbec895309d9fc9818690c92e495d3c8be908c5f1aef624da5368092fb8b76619e97657a7460c0fefa83545ef6dcb31a3c6dd9c05477735f739870703259a01f066527fac32d287f17957e5197be2d67ae8f188a58559c2c063793823bec6329dc12e727cda9eac7eae19702ce3ffd5310db4c6b8f39a92e4e87fd9defa790cba87fb030dc42c6ae87dd6d68d961d221408d8b728f6fc9f75e2e9a19e8ee242e3d723489c3146459f04b8d1dabcf6f8fe8951a504113231ca43e87743a1754fda16a712ce51d0a9ca81f3b3e5f902e58a878731daaa1d129d629f1119647e59dc1859e76f3404a7b9e24dd228aa9d99c8dfdbc4c370526783c6a83f0bc0aceda8b651cbd0f57e0edfaec244a85f87c90e4e7a633ef91c1aca414323679b63d441506b907fad80fdfea53284a73da62989bc64035e4ca2ee5b4439bce16f08029f7a69e2fbaedd9b998a5b67599823de64634a5611241cae8b54744ba0ea9dd14c52c362b198ea9224ba88fd4b33fbbe113f9377a5fa2d08a1d994358e742702de4c63743c03bd1eb099f5172187da1ca633760ec2e101930c751efd2f2a172d1cc221b6e84ee89675d0bf4c5d37b13d777fea915c83295dc9d0e48bed939f3dbc8c6196b9c0bd7260c1e6b30427974dcd8441e50170b56079ad8a6ebb395986e07338e9d0d6fbdbbc648093c83bff6a0b229a14305b39053535d4303a0c73d5d4c3aa11476e177fff11d1d4f0f4b488ebca45b45bfacca81f1aa35f90d3f21e0c4abc3723b0a19bea26cb131abbbfa55b943bd241ea6c3bf621256a0915a40d5ef9a55ed71206b93939960acba9d81a5e13e3f565019cb99fbaa29ba926a0b762632bd62d0ea445ca7879fce3fa5a0fe4f5c51028d1d2349449f6cb4babfdbe5808739157644c66c2365b480f3594dae62ec0abb18a705859567bdfed98a155d9659ae5e6dabdab832545ada15aa30d3d4ea221f8e56906c55fd73685cf4dd7f87acb72abeda4081e043e0d2c61e23785264a52b7bc070529818777c3b775d7880148fadeb341afd368d1bc9fbd02b0717a49be969c28f10dc78adb87a33e85ad00d49eb58da69a6a7a4d156b68dd34437a8abdf48ed0fe51ebef922b51d30b3642208754d267f36330ffc6b014b33e641eb583dbd1193868826f5dc399a041f447fae46550db71431c77d56c6b3e0958140eb45ecf01fd7832d7854d1e0bf32dcf009ca30c2675c6d5d7882d7ac4c4c29d5f54a70382598933bdc1d29f343bf07efdc7e5c664babc1067459a1ac53495d76f716cef13485776fe10fcdacbc73fc76c33eff38f7cbe39cc9ee0e0d848c62cf6e9c887dd027c93796fad1f8791835a9a4a363c7080252bba0059516e5bf45535d32c0eb1dc3a86210d8aeb4a781c0f837f783c576957b0d4d8b74af1caca0822d4f8353eca2b080da00e83ee37ce5d48afa5139aeb7b143db8416b2e40725a317c11ccfc751987820bf9169007104ed6a86effcba2e5a8ba1f19b541e44b3b96a60a98aa332d8d863673df1b52ddee085b668dc103479a9752ed298905ffbb2d20cf398588951f545eeed33573fdc7a1a375a5622507d75cf7684b058ed42a309d05ebcf7655644632e844a784d8892bcf8453c1a3d692f2d8cebaaf2786fcbba35fb1d421c431b081e128e47a3ef278692b1ed782e2531685d0f9dbe215a2b1e0994c51a2116ff6105e70bfc270dcd53da00e53936b7751100703893911c21cb9ea1276d77c1a181d51b5fbcf15f3f6bd9269bb2d2a6c901465bd0917479482078664df552e8b3c28fc7e179b5fb0568169e8279f5b76380e0514f0dc3e27f5269d6bea57b246c2f49a1b80d9316736fed497d28afbcedbc06306610b88fadc7df42dc9795a21a74d44eefc7977a6cde02c5dc6d5b4da9e93ef04283dd5fdf654de8adbbbb1a73e3b3973d534e30915b552e7a802c9fc4b93d75bd141550eeb5762257cb61e5b97545ceacd074afe89249be9a53871f36c3fecffa75d0b625b5cddf27a063ffe42c57768bd157f72cbdf7351697e4ac98dcbc2132a8e56e7c41eac69088ff7f3e8d4c37763d722327163be6fa4d4972802f83efdba63dfdfb778a5b5abddce081a16bfba140bd3b91f6631ff718ea27dacdf97b8f33621000ec482d5d5ae7bece0362486159aacaf6babd3da867284e8ed35a0f39fe580771932dca219a9b2c786e0ede79454e9caa8186167c7d5a8484efb0fa8ee8b09ea5848b5a4702e1d8c37f93cfa88006450c4c650f70c1f7ac035d057ffcc8d67f8f26398a36bac553bf93d41d42cd0d9ecec684e2a442dfdaf9277955b6c804d4002b1fe1add4eb811e6537bfe0d73ea91089dde334bc1323d576f9f9d70996a2dd7fd3bc9de1dfa3157879ec7e50016d3b162af71936d224cb59248040fd536d3f4e95458fb90b8fd1f6dd4e4595f851b06ac45962e51eb56fd1b0f65de0421fd46f44eb4a5bb1c94ce35ffad17343d1ebadb9411fb766d09011024a2a0c639e25ae509babbff1ccb5c01075ab4fed5200d02423b4bc2a91ccf0383ed491aab4558ced90d3ccc89e86654596b603e318568865e6b250705a59ddc58815eca8c15c70198d5c8b612aa2945d991bfdd856ef3a9d8a6de2c6411cb4cd0ba0e0e91f5756065b3fb875bbfece430740eca8e40718c86f48b081f399a93d6f4f4c449d817414e1ac5d6e66b36b34484d8e248d09ad3b308333cb80fce1154752c244da43747b02df9bba07c32b11555073c7f869ebcd9b588fe81ee628f18dcef0857e478e95679e50bfedc6c816f351f27974ac5101f58c5dc23f33420b662ef4d52322658107ba1c3d5953eaee4d75bcf0d11d681c71d87c2b9a1bc8f957be0782032e10e045c69aaab10aba8b21f168d450f52542156fe159ae5816f5e346026b121f12412484f098733ecc34e50344b39a3780013029de137f34593769f983e81e88e5a20242d8cd84903700b2938f6c3c436d3a5f954f6faba65f81bd2205c3110db7cd9c590fd6962a2da2483ded6cf6f98382a7375317bb97d842d35a307fadbaba2fac9b9f9b613cad0eaa5294c62a00f2758b4a41fd435f6f109bb2ac6e1d905cc8a36fb3ccb61beded3f2720e71467e7fb4ecf1cf64cebd5d9fd95f246a5a3dc99333e9a3072774b9ba477c3dbeb05d9bcd88728f50b342fb0dff4a4a2c5c2c3bfff4993038b6bf8ad172003ebb3086428066c75f361031505e3e7c9eb95b7dc570dedb82e26c144a08102111d17750ad3af45278193fe6ae60eb51bb0e7fa61414fb3ad1c9941c69affbd49f13327976d8e2854ff1cbc655ee0d364b84f7f331e35c77505877a4238b62ed81f469053496f2b6224fa864f631694d118d43c7b7c5f26a6da30a69c6fe8979d1017081ba326ef495ddfbf7407143ef463d4eb32bf9c94608f2425d7c19f5de7feb8e98e5039d4349df080128b21a0a97e70679ef873377fe7b4c0705e5ba64fa548c13e20cd61d23efa4a135cc3fd23be8a1d9b2544cd79e724b0f2c3d93b0706bb9e7fc97ce43b1d3c6c3e6e1f34aaedb5192ef912d16b8d412dfc5ff5b0424a5590db53d1cd3be5e44682c3e4f8733e81a151e7553c6004c6f745184c098d3d38862c8ecfd3c2dc31388a17efd68f5ad9819497dcd7990bfdadd9bbcb0cd8143d6ef39e539b7f8c1fecc4628bbc00ea44945049d93d91f4add9ff01969fcc4e4c907ccc79c6d5f9472b831833054402a1e7fc1de8465e3f4f07a609c70c8340c31959c038618a04b6a7dd96212e4e76b6ed8d814d21c47a1a1774d0b64ba98a8d70eedddf94de90e0a8be30ec8015b87b818d024d99a396ea5795235fa6f87df86b34127ac3a0a37f0d4b5173912fffe817e4a26ec7eb7085a7ead5ef7ffc5f285db4d8988aa544578df0ba04e2338170231e145e7587d5dcec07c624bc1f60ec1585a9a9d34433fc697a049f2f7a74638674d47804fb260af09d92821d5f9d0908906ee365eda00b6c7a033e40560f1d3872f957894fb22684941fe9ace1a8dac670d33d56cf2643c49ffc96083ab6f6b841833511a80808ce9866a4eb69d755ae53a14a6de9fdfd154d8051e2ed40573e9b5b7263445e4efe1da97277b90e7c737e4328b86705925bb1c1bbde2da6df08a70d9847ac456387ee20c304c8002bfd8bd3a3c307b1dc3be959b4b10e6f21432938c187d7d0ff54aaafb254540885df9b06e1cba5856d8f88d6ddd996234ca8478aeb4b55560fdcaa004afa686d9ba7b94e3d129de662da9b14bcb2361698335542cb6cc93143d34aabef69fdeae456d4784c79d5c6a6ed514362e1bcd91ed72547f35b213442d654b38e872d1f5bccbeb11655f8227b83e63d87e5948489a6b4c503b9a6637586aab7154911c116136007643a4badcb4a935739d1f915dda8bdd6642e3829f29010ffe9566b363ebc3523de343627ce3bc15ca4e495c9915d97a8256d654f49c3ff6c586ad36d28530de104e2225df252069903fb7071ac8fc3c4eaf89f325dedb0d984e9c4612a52ef45866b1aa338b50594a49bd0fe8d4a4cd9996a2b511141bc252aa0963a9ca6959941d85fd3983810c932d6961818e0158e1f9e6830e7aed1d54aa44296078322dca051c8dc3cc642df454e67846243d5ae56bba5ce521c0920a036489ac3414c98d3c5b040ed88f1bb6d3f6aac8ec54c6c72f8c1d846294ac330c0ff23cfd82a3265a9b3317ef89e5517934be9de29d18febd83f169fed18ecb6dcec674957901dfe47dca300fadf04fed52224217f128cbfc07708327363819a568a1d99354362fd7f931e95fa70844cb2abc33980a09233cc510bdc133326232fc2bb9cb8dfc2249bd64f2d3d60693c70aca50c763b526bf895a0aa43e876c915dc85f9e1bf9e375733ee374531f30965f1c686324de31a537cc40201cf0c613a5b89af7624e0baa44f03b4cae4c89c5a8bca1ec6514681c9acad2d56e7b7092b1d1f547996dfae2128feb5d82b1c29638c6ecc440d69f04204be61414882b87c609dc66b3678dbe71567f81d3dc3cfa8c0c0be50b059739edd9247d6074910987cb13508573ca564231c7624110e21a0e922fe92bfa7e2bd5a4b58156aba67260a2951bf8d02303e95b7cc0c82b33613e2a55b0c3ad5ee3271665452e977121804d46978eb29db23dbc409881a53e3bf45256be249f14ebf0107c802c24bea71ff80f59ab585f79e38aaef06a42e5e23c3c6228e3bae7546def41543ab3de22b17ae17a0f8f9075c5429b15a7d820bbb234eed5027879151eebe1d0a210b3bc29288a5df50ef0ccae80008a827a50769985931ce06733f277790bc0bdd3dbb39df76eed4aedf7b23d766ce73198c6b710a1ecbaa98fd16c2bbb4deb49d7fe132c0fb2c323eb15f0627524dba347ed779c8ce35ced341cd4f3cff3b5e97f0081673f29f7fad051a1833d4bd7b77b836e9245da834c052800a0df1f91bf6b60e9f46be4f372b017a48b4ecf9b0513abf6697cef8ef9c7e05d60bfd92c56e81f113d945bb5536b1a53e22f28228d9ccf3c588efcdc6c12ce11b062a7fee160de3bf3b409fe4d31b434683c71f87fdace5d019bb07b3811cfbff602310ed14701ff627c26bb4836b3139329de6e970d261b0599140738245d68d885c377c3a0c5f2c0cffe2eab7fc39f02a48674c961bee0ac311fc6adb902a38676acf61a5fa5062542c20490fedbe33942f66b4d788b63b725d8504bea9a06c4f210656d5412560bff382f4f55a57b18300004260787c5e0045f3b2447d38f98a16c8ca4aea3178908c65baf1b615856700d56be6f0d25a2e46744d0f1e9e2b632498d74ed2424360fad6f55c66f88c2d28d2238f378bc51a53bed997f940375ca623d4faf1a30c9f6a950c7551b4a6a43f6674b97a3cf94067ebd3af680bc0bc87e1f96d8d2a168a2077be94e8ff648931977e08de5b26f26027b020ece9ca8b65fa8fad4dcecdfaf06ae0f19a319ca28f0151329d80a04f3269ba383e0549b5e900bd32309ab6468099d3aff2ea34b0253fc9483d1ff07db2963691f5a9e4f6267df45d8360959674d73b322f69c96dbef80ed18a8394e47477350e7a7c3094fea5feecfb601b48032ad3832b36129bdd5affad516fee8f983c2bea992dac13ae82dc08d94b9e3c7a6adfb9e542d48edade97ba35f1a3b20bed0d65e6916d12d885fd76bfa089e46ef578580db851d4c0ab06ca10129d5a9bc47e20f9c16a482f19d1b3e4b4a165cf09dbaee514aef6a50cce10ffee6b939441cf08db3b4d9c3d23b2945212aecf93a31d0074f9fbfe72816709b9e03d4ae0f4c9f3d4b23813f3e6b83ef857a4e701b442b827230d969d3ae008d3c55476857c1d7e7efd54540b20519bb3ba5f7ab550128cb015a711e6eafe0a7425bca194df40e9599024da1ed4db9c006ffda67827caeb013d2afbdc51d896a42747cb54c0f275104cff825d5d6b1a35a9cb5fa69d9f39944b9e8416403a2ebcbf038b268fd0884ed62f5e778b6b23cfa11299990d975efd5e4e46bfbb4a01e318974aec840f561b3759eec7ade55dc66b63102c9402e849ac142278d47f57fd718253bbb6cf12fefd7468714d9268456dbbdb343e81f83e48cbe9821992fd2e6d7d06dacb36b4dd13a37d325e1299dfe9298411771f42bdb34e264a526e173b83dbe1fc17b9e43bff893faac4133c43ba580b1f596986bc59b72ebc1f4c1bbbcb3d059e3a774aeda803624b63340a363444b4bad2ca2c89f9892730ce93158c04efc7ffa9ed9d358e797b5f3bd34d713202debcb65ce97681cd151c26bf4c6d4deb325e1fb77cffceeb39a3dbba3b0c033a9f9d15a23623df235cc5c9706187c2ce3639a60b2940d48f5b452b16158d9507aef35be9ca7c05d00708336cb6e8adc0b1710cbd1afb2b24811277480c17b83972f69f7557df2fd1d7dd28ec07d208f7d3ade62d1b50a423f2b76ebf8a37a380e1a6bbed7bcaaf9b8e588a35f4859ca893229d41d6811b1547cf97700d68b8fb6fe9c1835d097d117a18d98a5fbfb0021252f7e90f76e53de746212b966f439c2c4f80c4e388e75630256c742843210ad057fb2144523f4dcee94533ce7a9ab68b9828dd0f75269bd3e830ee9cdf05d4f43840545b4ca922ad50685d8ff09ba7252e728a40b7fdbd9c160e13b5c238f786a0d7655fd92cbdac6dc4f4dd0d2a59a091b471e07ab2d8d566655ae57987f5870b49a1399691214e9bb06421ee909d9bb26c473f20065371d3f366a5bc0044008aa8570f15ee4f61d69f4c24296e1dde377976b74e72828067d79c627a2bfa00f705d9bba21758a17da9f3fd7025345359089d0f050fad49b3265e4fa7a02f4a4fc4b3e2a6a69f8edbea586691db4a63a25b6e8cf3aeb520e6a50ceef92d496052b57c09611158342ed10f3c43923fd54e7a605f66aec94b51ff76daa48b4d613ded436b69541783677513df97a40e57d3ef1699ed8b43ed2d60cc33a130706d97fb60d4a260b172a19bde11ba2e3b8cf25488d264d3799e3658ea52ddd305a924d9a31fdc8a804be4eb2e6367a5627e0b41667c52c6d84de8ef185ed9a2593e7aba3e2c63f50dd95a266039376fbd2828f52ef6453b6b3b4a33ee3e8f360ce302cc5baa6bc146252052301e5d470fd9961712f18a08c9218f91af60136eb01acd9009e2231221539ba44ddb36173f3126399c66a545e3ba112f0fe7893df56226a08ce3d64a3d3540380257e0c995600f8b3e7eb0156b5093ca9ec7cba39c8e49cb7c89b86cf289fadf430d01d5d00de1f20133622c3e8f0bce4df1dc00996a38e79533a498fc4782bc47505d7ae32ff33d5d276c7e9cfed67ee10508ffc0b7c8f56a39548275e56b4eab262fd469773158b05c6068cb6412747c3629414129009f1fe8d480ad82a4dbfffd42108c956041d1bc3bed6253bfb5086bff422b8802735f76ff0e0eb824b34548216e04bba38c7a5413af06833fcc3b23339014adad5102fbeb1547cdd997a2c436eb757cf2255621adb0f02fb52000a23e6aed0ec335ea3f7842ce2ac23805dda03c4877922f7b44471134238a5eb3c650af0f2c01c2c92b68d4b5620d01b4591600f4b08df4b2f7a701571133b6f6c71b1fc066d2c0614009b30ed8238eca9a0daf7555eb11716b4dc10cf6f1695af7d62a5b6ac56e38601b7931ef583d5b711a8952969f447ae98f970fec5bfed8d405979a3cddbfc513c2b32878ecca47f624cf1e9d89fe84f64a7e3eef0e958da1399418d60b93a42fd1f0176c594778c58674040fb9080c181eb817a8980109f3b7d1949ad920606f99c05d965286e12ea5b442ff5c9f4fa4ce9f9242a1e23f944410e81d6647799c61588c1dfe04ee648dbbbfdb5b4df67ffd2f7950eba9948ef6a3e79c1b895d6ea28dd5f56f90f6729f525368d49e3fd16553e3f9ae0a49c3517afa9b085ee4ba1ff67fde3085dacfc72085fb2801da0faef06e95505be65b28299232b5d51a62eabd48f8dfdc38c0105a2b2bb4b316084d230ba4ed6f4b0e2058191419af26d37689deebe245fd3f6879b7b045d0a2eccd6ef869eaec7691b5ac5705811e689c5249298e04de12a89a451c7ed1bbf989d70c23051c7e417df69448b83dd9919d79f9ddc59e71280ea08c4c6896eb997192ac12355f537a42796a696d443c4eb113cc1f77ba376f49c871b752dba2293f1caf863207d7a8c4ee7747b234191c297f3b0bf9f865269f030f24188c985e6ffbcb171fd5363b51f3cc5a7173001b6dcf550e59f0491537ae145ae64dbed552160d6b22aa4c955667f6f0e3aafa33a4f234128adbcc90bb726fbfb3ecc0b4835aea2e7112f8e1417b6b9d40290545410f74216334ba3b02d064d77d48001508d75c5567ff43d8912a96556883a2b23de1590ba1cbf3b6f9bb04584937e86cc6fd5f4a6ec1d9ca886994ac249d7c28b59130d0ae5892e247395fd3b6499b237afb8fe975cf835de9f1d888d191ac8a36bea51fae08db0f9661183cc420b30900ef51edc5095a48dd42ed7b7adc8f0d0226c50bbb651a56a9b11151d589d6251063e65b24d5039f96513c24fc101d239fb8abfd08164d834b279b4b3fe9f51580fdc70cf1cd8a27ba760ee9179fe533993ae950dde04c893a1fbc070fe11be39dfe3a1540bc8ef8a430ff4447da3b82d6582feff8abe52c00654175aac97bab6edebeee249e81b2d508e9ab651e928bd646d0f2d55a05443db3acb1d9d34092bf828ffdb725077dcf6f3c7a4afbaf30aa95fc7224531cf69d00c66f08f22b0749bfba5ca4ff5427be85825ccd8f4b9fc927b85cb33a845fa4a840667e86ffb79500a64e610d4acc124fda23f8c4510dcf6ce3a1327ff542a3de7db4f65114f7e0ba3684c1112567a31b3f4a2e6437d5a011ec6851467c198461f1f2dd277a145a5a6121a8659de22b21b5e7d10da5e82cc2b0e84854ad5a2de5e5939a253151e012420ff0ce36d3487676a0df53970ad5389b3323e56ba42a720bc889f07af67d68d2f3ee771c6045ef76ad32c4a1b310e013fc2da891836d37ac93c64059803ef493dfbd6166ddfaccf613790c7d4b7fb48e2222f36302ec1757e7d5d3344f6feef6c69c15db24b2674f741ef1b9a801409846b1a3ba9e4aa96327d95149eab4f32ecf53b288f1437092ea5569995d6492027c1908c8746b0f5c0e7f6cb6874c99192da1f09bfccafe6b67f7dd64db1dac5d94b7cd5f8dc7bf129e2452ccef40b0a2277867d98c0b487653e6a212e2cd7f50609cbb5ee8c68cd17eab49c348a472370757c8a6ba8105f1bb33c1c81c28d64336811da1bfaff191962dd3e806720713b54352c8ba085a0fd598faef85f9ffe5c2c89b8af88f7db12601071a7e4cc24f1f490129f20e8894fcd31d5625a45d3d984242758f40cf824c6115e84aa7e74d0c29a664cd21844c5eafe02ed0c3a31fb72b2c719c297475b781a55dec82728b9b4df2416ca2c4f6636e8d12d9966778bdd321bcee993593a9632d82c663ba2d9a3cacced76e8233ecea6b130630d1cd3fecbe0f02f5bede430eacb611a8d46bf778e4c05b2a176a00c58a637c0b8a750ac18456d8d0d215c4c9f2548c4d8fcd2193eee1eb5c9d6f743ae732ca169ec8ff2e3863f425c7e4ecdcc5f3829a24c2861a8947406dfe870b6d8dbe377aaee53ca43b6f7a139af91d4948a50c4fe65bc3a09a6ae5c5f5c8510af41f5e8b5eef35834a2b17329c41b26e3f658bce1de9a3115aac04e1937f74ab7c78408647072cfe1809dc73dc402e451d04bc35a0ff4f64681e7ca9788db0707341fdc0b870923ea930db83146d9bc88811013a4d89eaa276cbadbf6c75c5be25170b45e6499c7246f2770cce16771d0921cf45b1729eecd08b3208c3e5e06898c3f8048dc039bbb95e7639461a0e016c5b9af2b37dbcb16f3c94760de7e12bd3901c0cdfb2d42578f35ce56492530dbbc292cb663b71bbbe644e446a38a9cb9b32b51a3b61e1784d3d07b02aa639a321ba790de54d659eba59d3abeac1df94f70201c2e3301c3e0d039d04632950238609209baf916f1c905430ccb363f05849bd6a72e8627e570190fc4de1ddd5d7bdb8c4e906fd19dc098016af45e2edb90296e758430af28e0faf1671ea28e5fa607340fa399db5fcd612bb496aac4eca3f572cad04f50f1e68961dbd42d20ce77e4682a6c5646634eefa03f3d0606fb258b73b13f002cb9d8c7376ccfff39bf339c65a1dab207d4145f3ca327d5f8be96f0f450ac123ef5ee80161226939a10ef72b02d2ac4397d21a2107af0e6f36c86f8a728dd73d8941b1efef5803c315b12948025dbdb567e6380f8ddbbfd0c9ebf5b8068e502085c8c4cfbdba45814dfc4e7f194dd4025eec49d6d951a70aa43010153a3456728524a58053ed2a09d1a259ea5bc86bfdc04353d3bd135792dfc3d512052ebfa0cdb25236461e4c85d5af8ff38ccc7f06a17e0a96fcb75a2f7364b0453a264ece8f6095685c3142195e3b38a621fca4c84586a9792604dc284b7248e98058a1bf87fadef8dec0d857a18ad155e52dd8d58758cd46668685f2bdef7c06f2531333f0ad9613785f1a30254fe273bc18b4a1c7c42a6915895084e5372d5f53db4b6e7f1a3b39959f02349100b17cab0df479ee8ebd23ff01cab641ffacd1441dc87e977638e12118ce59c1ea6c87d6afb0458660352e6d8c67680c99049fb26d754f4cb46a84a71e2db4c7124f49d58283b13a5d29b80eac3b9e95f6b6fcaf025a8475fd2800b26e41ffa04ef26af70e852f3bc1f766ceedf8efce82d9d3373197d87ec18536e0d0d2d9dc5569dbc15c249e01097b659a8370b0868b20b0185752baea73e48a36026db2a1f3825166819949b1b91a3e300c36423214afce7dec988c2dacf9ee32c30ed485b3366442a2637f8f7b04af58d17e3f91afc2487068fc1a40f3661b917fa6692d47a1b30e36b435e58e395d00f92e1b8e198fc98b42e54d2db03fc0c304a4703112f19c54190052e3d9d64ad58b8ee28dfa594305d45e542ff36b57856a2f48045771a816ffadfb41979d319d48d6d7149880ab7e212c30918ba7b686dc5294208d1a88cb3d2b28c013551b6de72c14baba6672d7b2801f8ff600e985d82c34613ff7a404f608195f5276c2287a6e88b99ef12e70544d95d47978faad95202f1990b228ccf8b406032cc20ef27a01fe63fe9738bd974170c022cd19911c8a7e13ab92de7be05934115610c7bb9a7974c0d68f65f817e56e5d987359f9c52367340f3670e650bdea4c9dc8a7e3d42a83b8e0b8abbb006c1f4968f63fbae1e9dd2bf8b6fac11a262a709bbbe5f3e604a557b6dce3ddca00622c7636652b62bad2665bb84f039af4698a2e93aeadcf8afe133b0caf6d16cb8d96998398eb7082ff63a2c576f871cc541b9a4c62e8ce16cbf229033599c79dc5c301eada25d489d2afc879b85978dd2458c75ab51956bb0c62ed04cf10ecf3f2def6d579d28cb876e1be1c69bbf267c93c4d2bc043e423f5f13fca9ab2723cd1b88db796a09b5f2dd8d4f6fa9598e27eaaf8267eb7c1b6bd3bd0c7aac917a9c3f2e4f37796bcaef431856fdc522a1c44f3bb8dece718b46d09cc68a83dfefeb3ffd0717c232161138ddf08f6a98c9b19a644a82f72d27abbfd7fb668456953285b61bf237fcfe552770bc67db9e6d02ad40bac4773d92bae8f88b010b5827bad7f2a19a71049fdf7ad0cf40ec1137f9a25d9a27110c9af71fd1de8bcbce3b21fec0c0bec24daf0ed18f667354d0d664b0a076ac4695a599e62ab84168b1b7b503ba48c36d7c3d40dee2d60e9c8247525b060681b1785471ef7b87be4bfa4274ab8cdb261bfccffd806c67f9337a4077c85fd1e79a7724aa15e53f1f0b2702d309a9ff76f8452c9528a1003af59a97e9b3406b453d1f9c1b8ab479127741d610e6ec9e145b2b5c226330cccff8ea1171fb5edc76ae669406c9a01e0f5abca3d0849d85b942e5052c601bf4a32c16fdb75dc02be9fa56b23b34f58cc2a42d27ad496d5db66b9f879636330375530301b6f97c0c86aebccf9b73d49aa398a47936a0ccf1f4ecf0bf70d657aba1d2d33118e178c3d10fc244f8257a4fced68292291cbc9e766950fad7bf8b8190103b89ea9d8eba76a33998ada3f4e5d41a69868766ef045a67a3f3e045407642d77d2835c2b47d84bcf950a803267a5784e63724f98d3d2de9b3556dab1f7850b0ea573f8c77f71d5127fda2b6690cb19741cc75476b3f22a15fa249b675015b7d5070a34971a92b672efacbbb797aefc1be9cda2041f90cc5bb823f2b11fa47db290d350f08b573c1fa098f4235c0b333336bb057e5889781417e90a70b39eeab9546749c9cf4f9207f3a789525beb17a55841f655e4646ba35cd30bf0ab3faddb0f461147cc4e1bb1db9773cc922bed54589b611e4c2df562fc36cf0d0ce429465b71203ec239d6074a933d7f5604fe25bc0fe875144ce9b6b142b575695453ded3220ea61bb11e23b1aa557ab01c69069cd0e48a2fdbf087b4ecbac13c7778a6f391c276d669e327ce37ea8de8f676a71251f2ddb86097ed1935cfa2cc2fc9ea46c46ed437a24df74a3e1f8ff241cd9af0f2c362c3b9e1b838268b766b85e32e62c5cd845a57ad38bced67bebca158a9d97a4042499139da8cd0e77f97bb5c453d5d448240b186526635b98c1ec96fb5fc99a4ade6a695c41aac2c9f427bf36b9077695c6eb3c7182293c459f969d01d50183c8d9876eb1c017315ebcb819265c8aa232cdc369e66ea3beb775936773a3405769b0e10b1a0ed50cc42894fb3a0a9e945475515f89488e2311d39704df6040fc0fb9ec01cbc75b5fb24763c6fa817cbdbf476c54a38c6725a4e7e7623b489c8f588548cc2e7cdc2cb6b04a0897c1ea7eb67029974c080666fcc1ea93d4db9587c0f6897f6eab13a17d3072e124cd796369dd435ccf2fc92751db238b2ceccc3ead819d59f8d1a9f865b746347124af9be07dfcbfc442c9b5b74cfe9a30a8101b52a77138cd2f75bf2f68757d4a32aa1e0d35bab05bf0055e9414139ae07b9c73385bd704eefdde11f45bc2a3bb23ec3607d15057e00d30b45037506b1fec7680cf1e8b19a4a3f45ac361ff05dde04b1232f2f869e27fa078bce867c854166b6e4e0c00319822e3113dbee6ae0cd9aed1469b6ff5c493714535e3e7254e36a1e9a5f89f25068479880174f6206ba9b12218835630cc2962f771a8051958c872096503ed3453da2f6ae4f27ce5f4072b07aaa011a18d11ffa08ff9bb01579994bde945838b61c61c239204404f814f1919845eb38727ebf4a9fad4dcf7cbc763164978458569459611556b8be3227085556a5f46fc4ad9a4ec202eb4661e59e5f0c64fde8c7cd25d83d8130655fe316bc5f423c603fed3c31decf625f4899b015d9a952e0d871ec469fd97ee37276eed485bc8faec8acd8fc5d0774e61e1c4f911b54374a5fe72f65f455f67a36bcca68e55287ac4737cd75ad35cdbf53b740a75f907d3040506127ca3539d49a95b2e40f8ff805e30ec3e058ef20ae3a181cd6416e765c2851523ce60babeb6208c4fff9e794e70c4959cbd27e084b819957538f072ac1023d51fee7576f325db31770e2529ec8f096f1062aca6107e61948456f6058483e6c68c600836e602ab07db27a554629b7d723a98602c852868b6cc50cb5899034ac8408fdbc7fba132db856d465935be71ae707ca32c2fbb6a15eff47412b4b30bb359099f47a3dfbcd47f95624285b42f16afe73ee2e863678cb7d7624709ec3f2360a485d4ef51f71a972a6ced9a4bfa8eddb37837e4cc9b7cccba5eb710e84b2e54632b74e441d16b13b36158e8414a9ef158f3075b4964b3c54f922999156e9740f0ff409c7705ae18291c6f5d9bdfb8e324fdc1c3572097541b27982dc51e232e6b11d88dc2afe3c7c55df1a2299c5cab8c0ef0cf34f5dd34605396a6a767d0c354bc793c4039bf0bb113123e207879a0b375d9f7b430f6e38252b728a79f5cccc44000f20f559368691935970be2978dca551b16e518da1da0f261d1ad9dbeaf27413410eaad5822fc6462136398323c3064a2e74f41e87213470c274af4bd3a3da8086bd4994a4b7a5903693201f77be56a3884a6f3bed75b3d7aeb6a4cbd64a4299565dd60f2b8f505452be83df4fcc42c333c7123733c2ea4f309fec475c76fe96f55b7b1705eafe48bc7f50ebeb9b3735a74e055814c6aa3ffcc1a8675f3357a2e3b8eda54e2d4ceeeb1b915e0244dde2545380bc9807701e386513b0d56bcbba865f8b8997333fc6b26a1f1b330285226a9f2297995bead49ce1e080944bc228568bb4cffdc1bec9b3102474317d45684f71d6f09721514da5813c8e5d03ac5a5567a793f500fa270564a21486b9b2aa123b21a33187c5a1f6653d4df33e82adcb74fc75637d946c12eb0dd788402e3af14cd37a6e868a7fb60dcae334a8df7e74d7b7e1bc429477bb817890ce587296b3dce2392487f875c43a3edca28e23bdaed6d242c8534e0b1a96fcf4de5b7d7ac31ab177bcb7e4e5d4d294d8fc1a434f1dbf93dd984c664fa561239afac20a5e658ab3dcf92b97e3cbf426aff33502fd365c766c5b4e3759c4709320a625abc23e3b2d4fe9a9011627969016b1311a880c8ef7c35a31c544938c3502d3a8fc8e59652f4117fc78c2d051af5f7b60f68796f6aee7afdfdfc4e50681997a7837c47a4689e80a3b77e3c99dda2654c649f3215ecbedb22b5e7e692a4ed91215c4a5e71c2a1086564fd3ae2c5c95f96103db6fc97d74bcbe8e2bf3c8f62f2eb0aa6afeb582693e97ad85292e682aaa5ca6842f52c20b5ffd6717566f151f3ef40cd81a5a2fde7723bbe1c14e8d39b83a35a23c992f17d62d04d88a52d5d48f5cda86cf288bff6dc6f22985aa06129a250a00e8ae3c96b70bf6975358681ebd90bce40cba1f77752e9192adc94fbe563aa787438b4592fe68b97bb2dc3ad25df5f1adf02e6d5d2f3805da9ce03c787eef405d72f12442be7bedc655627f843e3e2773053f40c17ad92e33e2a15205f1a63f105ce7d94973e7cd5294862d4fd12fc878a5a84d77f024ec271b5b92d950036a5957d6314e8f22b08b582d84311b776dc8a69da9385ac33c9cafb88518154ab318d3c2f36e093c3f264de10678d8f55c8af42caf3dbc5065e52f6c2ea1166b9f5c3422cd360c7a074b2c646682c331e80fc6c7b67fd75b6252c48accc3ca3e9e5982453ab088a42f5eb929d1957de21c393c51b0a4e21b7d4ed612055e1dd65b6d85b7cb1fa7b1ebb1de4c26b4c561789a4c241bbf15dea7769992fff261b86477c9f1bebdb9c86de4784ab707cffa7d3a5aa883262ff0da0f18e9bf606fbabcd909bb18505d77904c0fefa5d524f45d09556916b7671496698e5cec8c5d67b6bb878700b1baecbaf04f3bd74fbc2a2bfd603249a3e3de93c90ef6ea4065c79a8eef39e45eab171dd95ef8f02655fa6630bd7842385c4dcab035725414c7be9f3faa62ce8f6f13e6eed11d999e48c60d7020399fa316fd4e5959ad241856cf1ca229046e7a31b16fd6cbe140d94f615d757a95a02ab4e0db555e5d28075a2ce421097e87c8a84475c3b486520b21564e5096adfabc9f410aac06e758a07524747badff60a3e7c5b3d274fc62d95f69c947c2c87d0bcd808a7834890ce8c99c3271eebc123cc36d7c92a1dd8a24214e18d6df2f696030707cef373026bc82d3a9317e31ae50ac82a1fa1b6cf9aa9c8359a2021501567c9092b4db5f34b16d87d3a7f1ad5cbe5e848f57e15dd0b4177135d9014875c499ece5939b9e1254cfefaec3bc21b3b3c6f94ee7bfeef1990270d4317393ee93226aa23bc7fb2ffc37c848faa2d56cba8a6b504ab5922e496a863489d3497bee3e33752ffb7ce63908e84749a7d35754e80b95cf9bda0677ff7b73eabc62cb0c6129d84f9a042ea57ac5abcac59a7f23eb0744994033d6f1baed87aed62f3060698a2d4c1b6f50ea4156fbaf7d3d204e47d91976d9b43f54dfae963b69eaf3986f3b1aee9bb74b92fd013f5cdd305cc373f0657d41e2afb4676df304fd19dc7eb991e47df542d374c78e4d1fe02690e26e1cbf516c720d2ed85ca509522d0f8529f7bf7198288c915a44dee873e9472b2edac113dba7bf6d336852d37a3267bb8a53e42ae4001f8351cab6ab9e8daf8df3cd643c632e61ebad56d0746314ff5ccdf28afa312b5926b8669cfd44b6d1830bd85d1ee44366e28dc15afbc7d14edf7f69e543722c23a7677bb39b954d26041b63cefc32ca3570fabe048063bccc99ec8f4aef370a8fe324dba218ae52b449a6f6173eab3b3a3274995fb72f463ff05bccbaa4f3f24b0ec885acca21faab1f540ed5aa1763deb036cd65bea70b1ec2b8b59a1be5ba8441ac97a2377b852f410f968b8e632086e41ce535e3b5d38adb88e556fe2f97e16c5dce612de3b93c50aa95feb58e63888f77f5fce8b04fcfb5694f155c1f3737c2a9e70e90f01736441bba9d4357221f26e0e01fc4ac86edf185c1884a48109ca15e29c60610f380994861da610b936f56c19dbdc022d7c960badd048410b1d1a4a1e60b6c62ee07e50fb8e004172099b725590fe4934c4a191fe558e830be53e2c171ce03abd62f6e97e5aaac49023906efba2abd5df65af883d772047710847f885276cd7a8c32cf1baaaa997fa0f14dd472feb1df0c4490b1454129449678efae82cd9a0835066c4d33ec22ba88c44814421078a3d7481c3a85189e35671c948f177507ca5772745427a3e5ed149d4c0c482ff8feebe088cfa88341b06ef7d2f199c2026319f830afc552d48f36e3ac6458104184fcaf79e6ea669ad960584dfde7854018d137f3946bc66e912e9bb3a81426660ebe93ca8dae6039d9b7264b174e7ee5eefd7ffd0fbe4e71e47c36627742b5e947581c39c6e2bedfcd7964d4c767369dd44ff5876f7ac5771f2edcc62fdb1e8b2921eedc8700d53d3e854e6a3efec7db335cecba68e91a228c4298bedce57262328356629ad2c90a532bfcc587c8865388226ccf7237ae8161057f5ae39c31fc1bd88621c68a1d7ab3f0915d788e38045cdf93acd7893a47ca93d6f5f26f42fe98bdd4fd8f359f88b4b661413ce52fc8aba2f60a774b6700096b0d9c07e7dd96fd4d5b5c23c43276b71798ea493b6bca90df36d88838dfdba66de6742fd4fd3df0f4578e61e03fb3de84ac1a1fd2e61327b66abd0cfe8ff9a7a4314a431f6df6f511d8341f37f0b7181654fbfe7cf21fd7b637eaa59f383ddda8b347dffb3c501747490bc20d6055187763e1cbb91ad5a02153fe0027fcd8454ff644e7d13e5c735fc0ffc827ca4f6b3222caf11650fc8d7c880b68c0979162330a2f29af219353620b34b0dcc12c3a3119990a0d1ae362b127aa7c502959fe9fbef1bb620244e9ba5719b7a00367eadd10d9634972d3a72ff67d9213c1bb94f71b1315d253302205f42c5f45fb30f44fe02198bc5852696c496721821848d8c70a09bf29abc6c2b19df01fb73e2dfe4108b0b90c36680cffb788e10951d7fafea485635bb3c3fcf7212e1e01a95ca2f0e4201c7ab70860fc5c8afb8b32804fdda47ef2cfcd10862d33d3222d9e2ec9984615ca754699a48fa922a4f3b0a077b695904c38bb8fc018866b7bb8b2d9d4137ce797b3b4c5c96f858364fa08d88fd4338b38ef368cf2045eb45fb8caa8623709e74eac45b95a9e712799c235b14f16765e8681d9c2b6043a173b097e6772b9c57405bfd26a2eabf00f7af21aad187bdab229686ab1bbd975f0815d25a16fbf10ec4c3d5e9dadf084fda648151356191c3d03314aafeb0bb63049f32d8bb5f65557f8b4cc7ead343c10fba0f1c0dff83f45beb3e3975d3c2485fe585f38b0733fa66f119de7edd644f5076a494b586309af6e7527d0f9ef75a5dc0e54c4c445223d2516eb0fc3354afe9bc2ffe962e2ad530c0d6f12382336dd2dcd1889dcec8e309cfeaad0ce0d46f1962b075425ba2bc3e4fbcb91c713db9f35c8bb9adbed60d5860fc9557fffa054cce198aca673df2e6489c48d6fb83107a35c0f18f7e62942263e82858fe8c06a5646bbd02738b000b9f20329de417d2d5fea548410f4af8ac3b2b73d42741f55d8c36195287b444a1fe8d60e5e33616b39ed845e9bf53bff95af4236e29bde6f34acb42fe9ae688be62dc17da305e1354614e73591e06051d2806065f041bb1e8a1ce5dcf945fde6b41482e9e29a36a0a430cdc28e8ae59df62f4553f2c9145adf07e741a82128ba0aa0b2854d135855428272a5190e45587f521f2f79bbbe37ece7cd885c18af640391913cb4073644f24507a1e6de8d4502308faa98b13c69450cd8dc7b6a9b6c085543d4e65e68035ad4cab5220141d41b72497dad8b901453a4e7013ca373b620680dadb5dd96aab18c71820174159ce5c559f98ebd713ec130659e8db0c583d0e8721eff1cd85ad9dfbaa1b80ea0781ddccb4d7c3ab51d47126dc02d9c1a4d59b6995fac0fb0dd37659553b5cd9ecc6b258d3d60e1c73a34bbc8238a409b733c01aeb56890e5f79faf3c6b76ade0e581b02aa126fa70545a9d91457ba1d1727a0673c077b19c557b1a46f38df82b4b82c784edfa8911a7854ee7dfb9804b352f379fe89613251d788c2dee7c4b35f39e4741450415f396c4d747ae2a64eadbc7d08eef1c4509ae97ce1f0705af1fa3bae06f3d721178b6b030028bcd58b10e0d7a89774029ef021459a5d7256d6d19f98eb5c7ebd6e383da5c45110b267ce86bf764c12bc0c0122dc562d1d17bca7845905d70fb59118980ed0985115f5eed82de72a5c0e346397fd20db807c7052cf9d8b7444ef4d3086fe0c568bfa1e844a1199eb788ab8ff5fe1131ce190269b07fc24a4b45be30ab7452d102ae560ca760a31b9fd91759a4ad9ec6f648162505876ac8dec0cb68a2f89231a9e6ec050b6c611e5c1352ea18ee1d056d4f836ba4f2b4429589bdb0caabb41feb125f9fd488a3c59641565c83dc09df4a05ba8186bd1f087c6ac182adad0dedf8caec60751b293e7c2ed2dfbed2ae823279d484ae1b9f8b454ef76ec176c0c3711a9764ae2e89f0cfc6ddedd4baf23cfac25eb33bcc4acb535bf116d566aa39828dd4e1bcc291489d407329ecaa7b9e5916c0fd5b6d14dd35326b953082f683cac368fd9b5ede3827e65036cb2485489850bd95674ce10ce7c38683f091e89e7b90c32b15f72f826cd06baa1fec5773f67f5e895383cec5fdb2bf206216ed4cc42217f588b6dbcdef56f4f8f0b7501d2f977dfe960aac05da162f19e5238cca730ec0ece77d6d0f5b5174b44d0ba9249b5888f1f5fa22c969c2fd16ec8f420521d3d1857aaaa88e25176d3f97e4b66676bd9be752b61f071a65dd114b1d2401c723b099b44cda3665c1ecc521f3e42f9bd72ea9f0b4821b8a62f23ce1493d2279ed89ba5031e1746fa061be28160779e87a1ac4aca7493bb38488128c50f33041b6005321db9e0dfb35b59cc23d33fde42775f72fc040232adbac4fad3bfce30de155f792d0d9d8ad13153550a9d9de9ad6ac703f95357829074155708fbe8bdfd1fa3342421e13e2894e5640d66da32adfe30b26ad3d05fcdc9d991811beedc2e6d62820bf7a39163902be0cfebd08bb4ebd171130cb4c49c3252a8c3442bf806f9cc3f80cef76cc1fd55dc0468ef021b1ec0ec2e7ab52d8966ae2e8f4bcc25c862c893e27bb86e58dee064244d2a7df5f43631260a0e8bef9a6181e8d62a65d124f590f944be016e3f96f4f5a78a3ab5e76febe4efa2b64c0c2aac3f7d51b6fcc217e59fcae083d31c6482d756129503ece94701c2389489c763b6a2658554e0e35c816898f7110a4ad58317eb6e4f618686eeeeae6054e0d3a295711358c45df7677fc8bf3091e00395672cb8549cad8750dc20a580b7fa79b03a9e5b0a8dcd37ac357348f77d70e2d78bd7fd1e85339c88ee202147f4af537a2f9144c31ff612acd0ba98e335e3b6e88e534a6ccd44554197253ffd7613d8ecb264ce0104d143f6c5cbed0a8b21b1f28dbaa8958868600f0e2f868ec9bec4e60f997358c8a2c5e6ea5baf234be907fdca97376064caa24bd28317ad5ff84da5cf44b09690a04c06f3166e00e17158383980b2c67138ca65543b7960ad0ed300d4726e0fcb47b2e53ba73249217daacacb8a530583e7058967b6cf01a98b910a37e6b9318c07e57d3442a6c9066376aa9df8bac38f54b1c06802877b90aff734ea384dcb27f1b9cf6f1c578790c8f2ddba71c8bbc9790996201c4e620e9378e6ca50947e6cfbb3b26ed76978107d3bd36a7c38d54ab4c31b6ce234301caadd019b8bb9b5a4a8d357199ae0e9dc2eb426db2afc4edd4a4dbdfebdb37865695dad4daea4048a7c83e834928a8f0e0939e13e5605897f5007afe45f7b7669f09d9e2a4524963b099978a4912fb0e6a64d8ffd3bbd3f4a07bb60470e5ceda45a705f1ef2d3dbf3afbfba517d353d9c1a25acabc4449c5aca9275bffb0c4421db5ee36feb473c33158c5613003acccdeb7aa47fdffaed69d40f4f0b20f649cc72480d8cbfbea94749c91b21595be2d28ef13de07247489d71463575bd4614cc80bb17117575c7544982bc6025cdea1a0be4f649e3751e221343e69f598ba37b64757ac797de9f24cf239b53316ff071c6a247914cd8d5d608185177a61fd486dedc95356b661a844b98e88629c6783287f34196fa6f7106259204f65a0e1bd73c1113fa7b9cd76226c4a9dca3f279dd10eb9e3da3835b0d3ee3ca7fa780705d9e37d99c0f43d8fe9a919d486fcbfe5f48e11f964cc0c0c27a9dda2e951b65a04cdca46aab7a42d146d7eb881b086ef4ff0fc97808864a7cef9e01a5824baebe28e4a451d22b5069d22b44b483f1df7c59d800ec7c93f9eab82d951b2df8e429a5c98e1541cf56fb998ca9ece240a42e253919f9619155ed37d0d2a7631546c2d4589ffba06988d0a3f21322dc4ba8b6b00ee6035feb2268c897aea4a2769975639ca1f05dbc87ceb393121b67067eaf272589c47c05fd02d85c94dc94316f9f824af16321441a81b4ef37d618f8dbe42354d51b5f0dd72cf2fe6cc3da3b2c8568a053267a18af7db3ce39ae32e94463cc214debcd14ec080b4f80e967c0dcfd880fc4dec2776b53ec66b56be7222101768eb72eb1bb6e37ed5a931404657a63c44f50079c9c1e5c8f79b9535cfcead7b18c7dfb8ec843b65de4b0311317746b4d4115745a315207ac81c3a1d90b95a11905e383b0245e544ddd053c3c9ea42492a3e2d81909e0b0f5fd067e21b17566044d74c654728e78f2911d7ad720f52a382153a0d619f8c4ed2bf8e0603d632dfbe6832de18e5144d771f7a3b4499662ce36de72c77e03a6b6a7c5d15c94725362e35bf162033abe8d4a24ede47c37a1ea5cacce38d43a2a21d2f97b225bc73cd7477e4c1999d9bda014f5e9c3c151ea4bf798c43b307477d0c395cfe49fd6143f80612b20fb61d4eeeb096eeb7cee8fea0a38d8a48725c8a953f4821ed22c48db5c4c495afcf09027dd3efaca636916c63b95a2d99a1dfc625e6e347e3aeab7b3bcc108a48f17d0521c6e245e7ea22b57c818aeb87a86b6ee6472a46577e590d36cbdd34ab6e8ffdd0d05ef3ceefa5e68a1ee350cbed539d12cb5cf84b12a7c50f6b0fa7ec168cc4e25f0a9cd31fc12382ddb86f76e277ba103c32aa9a9d7500f37fb87be17e126541358af4f44ce2fae5236cca75e81c44f15caf34d781d9944b86ff06509ed1a39a708a155ee1f90659272968dc4f8aabb0abda19bbd3dbf5f957809478a3f48186c5e70da7d9e92a55157001c3f5781ee59740aede384e378fbe77dc5975d70f8833c21c3ba4097a3eeb291edd2c2a8c7cd9519398fcf93a24329f3fa04461d2ca4e4183e4a17d4985b94df4589d3358cc769f6d104640b91d1b79ec6cb6d83781f2d3e14a3ab67d9d31ac47d5d702ce40342cf3cffa63437331b32d83d850ca629f09614baab4570d609e28abff512e2f716f05e215ac6d49cfe40917a4edee13c7a92551c302ee78856c846ab50e5b45e73da078092ee7856be8d6b6b757029657631441651852a5298ee6ad0a0da8cc25914aaabd025e9ca1cb3a4062672fb877a39fdfce0437b922c17770e0427a47eec1d34eb2c6ffaafd1bca46182ec7acae64c127e80c2a5d27f80cffdfd775c765a0f2f29a18adebe7536018191faac39966a898dae58c1cce9664ffe4fef0a55002840c3ececb738428ff87f68f09baf5f78ecb5f3024c8f82042d2137ea7e6ae2700018b20ac3aca09f529a4160c01499257b2959bb0b31b4fb31990b1d5ff26c7469fce0674741c69013e9be90775850d28ae7cf5c265f0619a9ad9c56ca9e7555c2e143c1a4c377f264eae23321a12aea15551f7471637cb81c5d849dea4933aebf324ac6eb1ebe2fb54232cec9f459fb1ebb48280f6849218b854a17e65d1d448f2df092da9bc53b94700dd0538b6e36b03e75f0a552e5eee31c535996c692acb04ef41e7b58e9118fb1f72a5da867f6dcb7d48827f80c307a7db09ba99f0464a52c8defee3e3532b178c3030bf9b5f0f9f5c7df21ceffdce0f4cec6cc809c8bef46beb6f99c482d80c18d7ab962552013231e4fd65a5f8fbbfd697df1464f61c15dfb374adde2ee8b2ad35cff3bff26b7f69ba94f9206e10bbe354b96579f2b4cccb45892bc8feffae0ac71abde37456e7b5565d66e03f511caf83d706fa365c175cba5b0a538b91cfa3ca7e7fd02dfc21763f1b22be44c94a83d9139d220022ef20d1b13b3b6042d1bb349bb36e0457ca42840e70b458b24e82c2c59abd41acd044f0bafbdb00cec34223017277be6b4b0f7bdaa66ce6d0d3fd63d27dc2bb78e6f1915a60ecc6b5d627668caea350735b95568f8e940f40ca5c67fc62259057c58da8933b957eade6f6985521ab7a5a53d1598b68e9c7487b9cf19ac163395184257d4d9a9eb4577aae031cfe08c11959e21455315f4410286c3066b332cebeb0418a442f837c5d336f48152a08002fe84ab37549c55a9b0dcbd355cfb12d7c9f85df6e7c24ff358549b73f9f45e00f58246370a0354d4ea47b805442ac3666642484511a359cfeb6534b1898f48d7b21bd2dc0e41eb5a84526b807b2a15b5f2901d16e4cd691818680626a1b5144d41be280ca1f4511f91f089f0e6cf32c0a3107a8b0cca4968685bc6d52dcc000677f59ce548687f0fd1668e2281a5c003ffb3041d6e7c7b01b50b0434f83dccf38a879149b6bca381306f38be9cda33a80ebc2cf5bd9ec6c048bc6d248bbd9f729a27723f5e50e7f6b80655f03aeb82eaeca44024b69de15c780857d03beee1984fc168d85acaa6a84bb62af1c9822b15d6c1305b5fa0e85cbe8c820508e3bca229c7292ca731e521700dff833c1644be8ac63283c45f54533d6bd82f8756ed86d282476071e64724343982a0ede56bca1d25887eca7f1e11cbb75bab1905274e6c40cdb4303ca412f5a859f4cb9edaebc5118f4b8c6795e5bdedac97539f4334e00db46af1f7514b8e39ccb87d5724189d2ddedd1d6b3f4d597d956ca74463c3f6b049728b4654689e4b169c03763398bffff0406fef423cf9d4e8d10233d226ce72cc3509f00ea35bcceedf45f4e7819ae5137d2e987e7f6846cf5058de006ead3e8f2966f8ff7b0c11e14644a566ee92e386ff7935cba2dce91a012e5db35209929c6eceb352f0f989192b8b41a8cc6aba6e14d04db8db26d5398be2427b8e5e65311621ef4b8aef57001b7b00914aec68f1bbdb458344d935f31efd9ad5efe9700b906e6830f734ef9f357f6accc82377e833b78c5b8fc4e6c610c1ab7808a75f65c88a17d05c1d3b59f7387c9e0a585af711fc8ae34c7920405d0931f9a0f772a2555c9e7922fc1f46a57930b6b6261fdeca758de33edb4877f58e44cffa3853cbdc46d9b73fe8e6d623bfb210e6402cd94bef6b68d610abe0548b331fc052ac688255c7e84255f37056f6cfb86b738ecfe284db753284633fbf9d56680052775dbe46fcaec1ccc4eebfd57d4b7d8b61f3bb237a798d79851d1a498a366487e53ce1be8f7e22b795391857d691922c49bad4bbbf0d1dfe9067af9b1d26c823826a05c5fdcad92a5925fefb35d918ee3f6f2b9277aa05aadd29b09dd3d3ad1ea75707e0f6c1750d1e59c8bfe9ed3d8c1327e4547307379794ee470c825ab1d3b55f87c26fff2f1ea16fbcb8b3d369d39efc53c052dcd72ecec8cc9b49c55d7dde738c9acccd6be776f8e1d08ad3debcd3a3cc347285aab4be46eb74ca56983295db945172175f34b9a9df776b5cec0617b04ace7a4c5e139e556c4b1ff901ddaded4ee7dddb53a8675819ef42a3c1331b82b1f7598728aa31495f9e8cac4b70089433c5591cb096fcd5c81900c681dbafe432be79bc5a2e7924a3695ba76c97194845ed259a895e8f43076ec082b450bd3a86e57cce42d8c4309788e85c6f1327666942a4f96c38564cda53c5c85c46c325775ae4f7425ed8cc25e117d0c8ff0323d7ef71906a52a116ee0067cad2044e4d422845aa8c905e59a83bb42fc6c56c4830dab57bed31d06f47a8b3e8d04130ffd53e5b8594143a030885ceb1f07799f7f675b6a610e39e5af9adef70f2bae94d976e40d6c274294f49934459ed78a43038541dd8dd412d001ab3a480ac75856156704e504d9ccb8f4370388dd3bc59e6d694ca07029baf877653b708504e00d0ab5bd76c82b2cc7b01c76ca1b10162224035f76f8b364748d10bed7e7fb58aa797b2d3c44266682c0884f4565d988820bb3d7c4fcb5958a971cc873ad902ff7e1006d0b1d3a73aaac421bb61ad5882d6a9a43a67ca64efb8df1baff2e57f569d4e4f41b55706717372c08d4e646203678c49c7086ed3fe81af260172e62b6b336979ef4d0f2fbe63fc5edbf385be6ff7da71ea11c37347a5205ed5127ed80b2749591fe853e4c9cb05c14f377db73d337ef733e79b030c724b5aa459d16d2a71dca38757c628a8f560e7ab0dc5975547fff5047b01abfe05600e049a51a3a3682c8a3713a31954c78fe67dfd0f0e22c45e6eeb697fd402aeaa62f23aa76352240c367814f94517b257b95d9a11576e91a4871b3030957864bf5f62de8439f374066f8b56b11c7cf2fcb09e5d3e978eb00c6893e3d71c82f178e6ac00706a00130fb0924975f70d0b2cc4326c3eece448f53c157dbc95ccb4c6296dcbf94bb1937bf0d21c3c7adadeb19b1abb39a033e3520b9fe0ca8abae881d8bf2e43bd7e536ff7197eaf053717a357ae47701a3cc1b8c57cea819ca671c0df4e29505df58a0b13792d93cef076a748a4d0670f4bf15401c3b1c1ce57f482f9d2151e10c155f37d603c56e2a84cc90b05a7424f60d2b5d51e6d16f5f2390f4fba047b00cc59d2eeb7e21bb5a76037f1bf7124f9dc90f0114083447a2d8872b69b6a6fbc21cb35ec5c58575dc58edf6d0f079383a76e2e9ac1c67871c3a894da63b64a4d314e2bedf98c787be43a6cb43212effc343e9de6c34df4ae882ab17243bc5b33fb096eae8e29a7b90bb249879ca970ef28895693a477e39a6df2133f9c39e59d574cc2fecd4dbb09020205bceefcfa3e891855fffa6c769a598ed7810bcf3ee8197808eadf2ba1586f50707bba10976acbb5462806347a3c3a64454c75884e6dfe504e5297ed86dfa4ecdf40d6b3ca03cd6e11c2e9cfa95b80c0922b9ac4c87494c4a5e7bda6de6640871950ff019ca1f8fd0bc31984d2b7f79b9ae1c4c6f40635506b243aafbaec51ba8c00f127605d56881b11be0a0a5b18d1f465d78c93bd1c332a3139068e474ed826645407b608290989353b39f1e0e9e0e1a20c15a3d321a2ca6b967405b5427b9960a823c2e8396622ac600392c4a1878c4af923ebb8f084aae832c61cd42f9f4758d5279cfb891fd30df837cb3187a843f72d14122f8c43179447b88d954087e701f89daedd2dbdebe05358b9ef5babf34a3d0495e5e3e6c297793851c014fd1b6518d43469cbe5cb95b2c93007f0d52198808e133821311e08a18189ad187765e6e56ac94c839af9773a7f200973b451dd2b05f3fff01c3249a4eaef84592d92c1ec387d123b657d49330de313e5e3c5d65d0035ea0902b9668c4c9ef3a85f9642734da173ff5917f48c555113ec9b0d9cb5e9d70b5ef951f85bfc8c0c2cd40fe9cbbacb4a9d45e87afb27e4b1e8b7b344584786ff29b4d58ea6770cc0de178c5d3b665af8bad56def7ed153d964cd25f9c57faee3088794ebce18464dcc4d467cb1d9a0a8d3ae2ed481feea671d8a7ce42b5f46d6b5b33187b0e97afffd98c50e2b6e1b39fc7cdb4d0102d832a13c9bc7a92a53d8fef0321d62f7a12b2be595dedbcf7830f5c2f6fa92bd5d8b2158472726429297bd1ce5233eb4470d5c52c2e79281b7fbe700e777ef313f9010750e91bc815aa99f1a6cef7cd2b13b033936af42b7ffee6241e3c515f3243615cd4af0c6fc92544043f0abced0fe867d2cfe47b479b4dfe5badd2f93b413300aff2ded4ac9fc9703021cb75bc4dc09da094dd8ea53ceb4a5361b522f216f4a4965841b09717dafd4454b0f5767a898f724799f266b0bc4f54e7aebc20a8addf233f66c526a39a680d0b2140aac3e0e8f3956a2b3cbbbb2aa82370477f9d0811824db8731c087404e63ce8051efa50f9927adc794df15c8b6fda177f318ef935fe1adf8e59bab83c0d9f00d27d542d775ba9cbe9b2996b9b4a6580d5e0a11066bb7cb4d07a6256ab19814cdf82175987703048811ac40ae13d09bde449612fe186342f22d69a305a2816a42e7f1a4e1bb5bd3dbcd5c833f60b62320f6065af4dbb08cb08766f0e7f9dcc5733cd8032825b390166ae565f27ccf61e3c108038cca1262189c437d3a73dbe5730e719a77b2e20089597d3b4f9f3127ba69550a9a6a32e783d24e8ae2ae387126d064870a7929e5b93bc93a4b56ffce6ca2ca628d262b68ab9fa14b5c871ccdbbc670c2e5dd70a652351cfd07dae164ffb5fdf0b6ce225c7ae59855ae2d3e48e42ea2991081e4db7ba4769bc21a541d660bc3bf059c144130d536a3a799a86f8a0caf7a0a04c8bfeb5a6de786db171c077b5240bf56443ad2386196a09f5091f00de68c04dfc63330d787c62063acd21b8a5bee71d2dadd4f688962cde5a0ef0b3f7ba9e16e83b8d8e52c10144706158d9e00dc1d5652743598eab93fd729aa182bcc0ccee069be9c29ce484de1b6c4b3e39f730b8bc685c161f7a363fbc2799dc3d48d23a68060115d9cc02a04c73d31d66a3b9b734ef2751e4c584e3926a14ffa49c5b77d992fbb311c914d38e2f193a9f80805ed9366b45842463f91fe6fbbbf1bf9654174b18476ed5eab1f02f681b5a1232addb2ecbcf0379e20cc8883a2c02f6f13449f8f5f258ff7b7784592b624aad0878486fd003235fc87d86bec9a6ff53fa1cb8d7ae87ab63df5ce1ca4eba6f59a9b065891e2d6d428110278d6185b9ba2176974fab1a3f28f2aaade7109d92c4a3d50dd1fcc9ec5d437650530484c695874674ca340b2004ea26c891012798f68a910b64b5dae00bda698864de83168061f0e55a701264524e39c5fa793f45458ce7d82faefde775d4c99de3b76248ab86c030c7eaf01986f7a3f908124a886a4ab68e511e8afeb8d172475412ce78ec558b2c4962ef234940dbe1f54dc850b1d5d31849c335da1cab822665366221bfd94fadd7bba02cebf04ed880b35d5ddfdce81e01c9bc950795ad4ee2b5a27cf8dd9761b33006f971c2344d7fcfaf0c1f5de432e3b5eae47db89a2354ce8119553d520a158b8b37874da4f1376dbddf761bd55b6e3dbc69652ef42dc8f1b8a65198b9a58e5d5707f9cb1b8d476c8d1f26187abd33c2af2ae600c774d4d47f475e93054a331334c7569ded9c3ae8fb1f1ff203150e019afbe202560877afa84986a8cb5d3f5f17b15e7a2647d1e5bff12db480a330bb89e96047db57b88ad560890ade2db7d34a1e30120571d1e7210f30b85f7e0ab5dfcc5cfd7035c2cc0511d2d92c1798a0239e82e00f60bc3171478e6431878926ee51d9a14bb0eae8eac6737757fbe8d2c97abf11b5b2baa72a0933a99bfa7827971ef8a1f97573c3f8d016010eb5a325cf1d5596e0ad73ac7c0cd67cf139a0643eeebf6b69d1c0cd8f6f201655fea703ecb88f767a0d2331ae4253b68b65db12e47dba5c016872f10da3316b715c599f0ec581f4ab40d2aaf784c75ee19c09e34b315498947691402e1492223ed1944e99712fab73ecaa85b867597f33456887dfa913c47c0097ddd7d93e02900bfc1cd6a986fd14df1e50ff42fd5fc9d354cea01adc56cf7ded1982977cfcbd37659a80c97dccb2c079b83c97ce8a040d18876c8064a89a6a1b6d2f63b0fd19c2e3693c36906f7027acaeaddd65508ea9b45c15a63b514455dff03f6e6854999c00c43ca159eb66fb5997a489a6c1f96959ead30c34fb9d9fc800657a3e8723a8eabf10a07377f0abf407989dbbab72d96319b119ac05da72b5135369a14e266b077145c09586fe5944df21cd5e5136c6da1e8b9c8f72a7ea00ec969016ef045afa971e0f30eb74d186338d4a970d4317a2eddfebbc368b41270e6c7186984021ca4b773f25a3f7be531426f12f56a8c1f47eaf7f4d0d1e53b38eda471dcd259f40680d63f704f6c3418fb1e5f11cf54d1d53679ea238a273e24fafd6c1b744f32fe2213e172244b54a70778692d32194c3d18eb0114308f7e89faf8e27aa6174969d5dd5983cabbd5516b6ecc98bab5ac5b5039f9032ded00a2c6cf06f79f81c74c57cae8ae65fa2458b567edb53de4e4b7031e42036d7fffbb19c93b9b1caa006834f9aeb46940fe98c9972f34bea8dfd0e4bc35b3467803a81eb6810e030a4f749fcd8609ff42607408d5b62dc03d2f21c1629024a2a6871561dee282f41eec02612ebd73bf22b00566f882669bb17801538a5df9dadee04e45e5e420f0ce5f455a8701c5d81a2b21cff23b32d5385f4f0f5e0741131608c85b8ffe20dc58a60adce66b95424768294ab680df559520e57bdef742d7f60d20d5a6fbb35b63da3a4cded8011a638072743c20221900e5ae9d44c884bcae3695265418d2a0d07edb434e10d975cbfc155ed80ef80fcac094ed7f103af3cbf6170aad090971eaa718e668b8944ca30f298481730e669500745b717de01adb726a2b26c6d961da6c9a7cf5329cce94f1697f22076fa1f1d09d97bddcd5fc48581791862cee343c8e49966b91d7b7abdb7c85969b9eb9d3c41ef351b37a42f6c80e8a8e969762f5d18f6200412ac4509bfd7f3af12836c11407304344d77daa991a6c950e448225028cb37bbd45835420266f3029acdc512cf42a0c798d387bace8c15c365a9345e5f1ddcc661dd46ed086566f0e51e758b0a4b7a4fc1024b33abca8924801c4433f89dad8a2339c5b61a93a06aae088458a2d3562e6321a7a91530493314b05c2f0ee228b898854d4b38e8217cb15aefaf9ce32f40208d156c72bf50ce9a450900671da77edf99b5361545ee50a1defc1090bd2f5364bb135e4b7119ce4a1db33fa7cc555bdd3f568a11c317e62c9f2bf553a02b0ba62bf8bf552de569b53c1c8ccc7e31ec4d0232a130962b1853f324bef5f07c8bdfa6745a6713e33fa252308f195ff456c2728b0bc502f1569a80998e60a4c8ff53f45b6e5c3d27701ea6f5137739b214c2bfe134d3a6ec3f6b5c36987f06404fbd9e8f1e0f4d7c2f808e2aa1d1ac41495a90ede54e1985fc4c5d346f56820392fd1573533e7600dd9b3e4194901378294cbe298a6cfd00edc1f671b9d7cf4819dfb6d2e342a4f4e4b4abca982339c3d345e60b7620ae497f2b31c63a563f28291c60bc0e16c2d4e1725a9dfdca7dac79746bd054108000acc887f48ca3967db4bb5214641f72880218a1fd2bd20ee6595da5121a9da87ac873001d8a4d0dc837545dbe5d2daca8017369f9e611a072ed44a1a1c8ebea5f1c9f5360060bd4d7fd990c2cb4f89b0cc8ef4914d560b28ba95484ae4ff466fc24d7ea14adaf433eb40e2ef067479cf929ec8001c2d37f061446e68e8a23662d2f8e8b8ca0d3179b0f0b536d08ccdefb271b464a0cbe36ee0ce4369ec8d4ac3d1afc1be53c531a3daf51a01f4baaa469b854c25cfa6f4ad763643582152047aa9a3329784ee36e87069e1e80453cef405d36c72bc4c8f0ac6ff4f1ffd695cd9848c30ab4eb7b995dcc9ac5e7caffa833e4ba755a9b78c51b35d18a3d5d630ca6eef54f1541e3a71dd7644e2d2f2bbd6994112d52aec74a7eaec6630b96261900b09fe27ee4d18bef48e00b24a9aba1f9186c2083a6b7e1efe5a3b6c9e3f4876af4d0357184563ca10089ab2ba4d8ffa039de607b6d3cc407dcb7d1babf4f993b779a55401d41491ca8ee39590da19b580238953814800d5e71e203d9957230ddc2efee9b63df8cf06389c63c02758578d0bfc3ff33477d230bbdab48dfe8f8f6c04b908a9d2ecc6e7ef34ed0f5e736cb48f99fa725465e10952944f646b511a80158de705502a8baf4d0722c5e29b5d8a9102d9cda7789ca1e30773d2785111bfc119f95737b053ddf6a1c932a1f5ba18fd7276713155e8e89092f2ea8298a9442ce784739974b3dd691842821f60b2da0addff4244b60e621e36edd3fc7b39fed86e98c2810aee409c0ae76c3d2d8dd2e76c6193f840eab5791e2c19cce3808a581c50ea625b397faf1febfc13d4f969d94cfdd3b2111ad5dee7ceb163bfcac5f224a50042c97a2ca1648b4915ecb418b82a8b5bad33095c82e51bec6930da401547d72d4e61baacbefb9fd360d6e1a02100a4749ca4500ab424e1b8b2300201f3abb7fb8a4df72b743abc2f79c2afee8ed8eedcc1d6674804cfca6ba095117262eba2cc9c7b22eec211dddfa37d644b0a18c676a65f44c61c231b7eb315da03069150de9cb52c5a647eb3f943afef5da8550db37b6b5547cc58bda380724ed5885b674f57571896b3b5eed63d96e00f8effaa988544004a184bf6b1a3d7586d554885e5b572c9d5d750fbf6f4fdff517c73a8f4228d7769188e1922e337e795dd4c9fb3dfee0f755ae0eaf1dc14e9f09557a1a4e541da724b5e3a8cea92b3b61cbd5ea76ec72417e4e5aed5c4518b9c1381b9f10f71222637abb4f20d78f244ae732ac4f2e5b81a6a1912b686aa3e22657a885206e03e4876884577ce89d52071128034a7b5aa1855771bfb190bcea08224171a9ab4cf0424725ca3904ec4cda1031d19777fcab43497e8d309214e78681efe4ab65ac88cabdbf05823338e41dd0dae354fd13aa787c635396b8c27b5b9170472c6cc39eb83349f37901bc517e05b5493da204182fac4df11db5160d9a58a01762fadae3cd59e57c08ffb694b2fb5d9a65601e72cd7c28c860aa71a75798a6ee29aa3bdbbf91e6bd29ffd313b8b67f459f8f93f13658758609c0b44caf94094d65fddaf769e41b9841803ee47a54b7775ad8ecdeae272f40db845c9c4387596a6476648af606eede1bbab262e7bb147aed2eabfac5e790d36e39259c0964c7db5ba6832c203c5d4a3ead62a24d8cdeca9f822e6ae12eb0732b241babe3920864300532f3c638530c1dcf91fdd0829292020a2cbdaab2673da6a9a5c46c7dd074b1d78b8655f67d889d1679b9523867e45dd1bd97b95a487f4d32637410f0318f82b95d9493090a560489b7aab344dc7b2181475ff3d058b82001ebe46b4a25c19194a2652c381210cef340fbcd0a1943ee558302d9263d6413525c6b3683f199a7043d5e6fd4197b914a6c6461a836632f3a203728fbb33ed6c6b7cde375675aa93b54597738c92665d2f363f37c6d718ecf135b7882502003f4a9a363df18628d7980476590b5c1d100cc0cd64c302bcd68596b3259a2cc84f8d0061ad65b328cfee31fa4bc1f04128815b070ec6c45e1b2fb0d68b4072e8639d71af5439a64abf1e5fbb780f6d17f05052a3ba8e4ce93c9ecab92976fa193498a057594e1ad6e62933a642717cbafd60a8038877504b1fb66bc560e55a92fafbb8e3c2d70b2092d8579e037c7a661b18f4ed716d4fcb00f6c75edc2aa3fd03b57f6a71507caa665e7459e7de1943759652b2245e72f06ec5fe7445102cb1788dd03b2b22ea7b3ac2e4d83c5d9d7977c299609c5f691f796390b7387237e608df98ff000ccb914ada6db118897364c5f9c5954905df82404a9b35fa3e1fd748acd824ee79cb8945d4f8b6d2527fbc4afc36d79c190fccc44a18e704c76d59c6184f106d2a29cf5f9c7c81dd4a72fb8b2752d7939d8419b888e6f194124efaea06b59389ecf8e9740d045f3d23b4ceb04983b397b2045f4c3942399fca11d1976e0ef35b495ce40a42b7489947b78f123955511d833ed5b0bb67bec835bbd5d846e8bbfdb86d0d129a50729bb9dd7f4d3b478b63f69dceda2d1882e61edb86c8d019a3f5d95f7adb232f98452337d0ece7b48394428121d0fe4e3152b8feba82738cbfe74ac959c75da67267a9300a20411cbb4a04f385b8d0dbc7cfedde84abfe1b842d6c6dfa8740e996f1cfd6a1307ee4cc030754275c18c4068a4f847c18f5134609f1059d4261185cd053c086d9fcca1db21020c0bcfa0d9369d3f832adabfdf31e686e58d822c3d64f90c4f9366ba37079a4ca827e9471cb8d8167454d1987f2c1c0a4ae4fc9eddb4a1f73d90068be4973af2312cb73f4d1a7c5d9a2cdd2755619616eb23dcacf7bac73e633376bef238c74b9690b09b39350eb7b34b25a8dbc999a5657b52be7b3ceb53f382193ae04b2d3e99247d6abe2808ad673b2bbf3bc5663c91e4c8332d3e609d3a98c8f52dd08fb1dfa3ba3c8d18ab35e4fb77bba49bdf914941a37032a0eca6bed221897dcd739fb1ecc77e993f7d49a82be014079d902210cf0e7f987c9c5bc100eda48aa5965177c71dcbaa4115db27a1e13d97f3bfd497b40bfe5bf2519532bed716f0ffbc9defbb2418e25c78dae4e9a5de3bcf666ba46f25b2f31dd3ea7d0ace67e2f593a7d5b01cdffa7d20a708a10c6f83bc9321a327077d7a7e8577fd4245253108a1a425e9359ecb284ba3507d15b985d7e81e61fa549d0dbe3c982f71f6304133538289364716fae4648eedeb5e478a4aad2e297fdd06bde8824bbcb778cd2bfa2a66464dfbdce34a9259373f7059722ef2fdd2c0ca4472e1acc3f664e7012b811f4cfc8fabdde81e55cd2ddfccd3e4886c5db2cd405d29ff9ddefb1965f99842451ad43a6af77ce15eaa8b0ed8da23d5a03dcc63f1fd279b282ec26a23fd7a3285d87f8e14fd04eef4b2eb548b84d0e9e1debc0075710bd8e6d04e99394a6a01e82d4072633c814dd03618ae87c8538ec360199e6d9b4228da71a9a160d9fd55b47f217b0fef5b9b9bc890532acff31ddf5ef28af8f6f7f12ed1225df81e25666f65a707f0a667f8c7d1129dc2e67421b3982dc03d97a92873a287bffb25121337ac9e3a0a43e66befb9b0acd9713b4f8c9195f57006227ddcc5de2691754c335878ae7aa65797ec18a4451c3d82f6b44332c04f72e555dfcb7e75c39dc64fb44c274a1e3ae3c15e22e85bdae4f65d156c1da472679971ae0f1a18ef06c2e3ccd259afed869d3457b9aba11a20b8e6da4fa391fa729598b8358fae75809715ba5828479e0b6201becc58eca5aa60948c2cdae52efcf4ffce0bcffd735dc557d299bbb80e91cf4686cfe1af4051cb0fc90c8ef7edd40867ee59b42c781a7985c46a36a309316fc2062d63f7f6ad31c70a7dee10f9bc38a967a003d7149ab64a5cc7436268ba251368eb8afab7dd5ceb550608424109224d3ec1bd9b17b39ce9807bd6122e78d4d7a8250502c27f02a9928bfee82b426b623ec94b9dc7af784736bc8dfc2696c880718baf697e94abf84259f6f41623be50532e4342deb272f54144dd48c2fe46acbae534ffa96b206d64187f70b12ecd93a83c472770be62606513b7a7216583ff39db1ff5e3a8d9be1cdf1b352469c128906c6cbe8c36b990011318ea50bd29af8639c7e62b8de7d1d5345e777f7ea5769f84c285c28c99adb8f656b13284eae2cec7765e87cc7a18352520fbfa78ef575f7d2036f7147c76c68545c9ecb2cbb282f70a86c50e90e2d598d998c32831aa3f4e57f29280c741fbe75188ce8bafa7442a80cf4ec6683d6af808693fa7e99762aaf9823a4fb7c9f9c914115fd02c2679c6743b64db596a9d53e35a1fef62caea173017628de38442ffbb9bb9e583aca2c26efe90a9daf9e55093d1017497c55030e4a028fb361d1cc26966d1d1ccebea5992256d136dad0d55188e407adda62f1c6570c6509abf950bb338070b30b166426f58ebd93525cdbd843c326e5e5acefe44458ce8369ddaa261725240e45c64368ce360fcf2c269fab1df51cf67863c32f709aaeae88da13a495d94824e422bd06e6baef2042b60e8f99c362de178608b59abf4034abfd91ba66897509dcb638ea718a3643d76ec08bd691b99f03b140c19244db6070593f4966dccf1493ddc5c16dd8d11d96de91794511ff7b67617c45f6da2b21a3962543f5fd2f1668e56568829c533eb10ce1392724bf9ee7b698a0fea79194e8a30444fa5de36bb36b29a68c1d2497a61e5cde3e38a6f6854641c1a97494864d41f0ab45becdf1320ad3627afebaf83169800f858422f1e6bd2ddcca0e39d589b4bd10fa0b93676f71bd1cf088bc5b0776ff7651d02835b703dd3bd9e54cbf0037f4eafd1c3956e9e53a1945755e96b736e5da7b404e6a1001ca6f6623408533b06cdcd0537a81d1ae2eb647a417be1995e18491944f252dbe8b8f77bccbfcba9b6aa762f89ce579e190213fb5bae0df28098ef7aaa0a10db8bc601805edc4a285f26a234de616bc8a5ad472585859eb84d0eef6ae66363d462b4fe5af0be4defcd1f4fcaeee26c1585bc729dfadfda567224bf5e367cfd64d6b9bf302cd31bea4af46ea8c05581db5cdd07edbbabfc13c512bddb04ba05075f937b697841ff7a92835c53bff70daa8f9c19b6aae33710e78df6cd9422782ed526c0378ef85a74d517ebcdd8d1e21321db3afa868a398aa234d0775bc2e8bc2694330b197ab35666356e5d7f6eeb2f9dcace4e8cf793d3d8d56868a1d2d6ec05a0ba6cacbc3f159360ba97f8fc3ee94bbe9c89aa93058e4abc03f255e2018048ef23378cb6f26fa0517d5c3f7248266acdfae116b2583fe2f96de98b4136fcd75b687a73dc4434643e52524b977f8fc7d39ed1e4954885efb9e3c6e9781ff8365911bb9c9feacecff8fd03b332385586c7753ddce876492a8b016b1218486706ac733ac2bcfb2e2dbc557b0439b4ecef2104e77a2e30fd45614f2a31a122c817eaf5e5f20beaaa23f0bde75492c654b1c57d9b24dd469134900b6d4c507ce83241ce4220ac499d73b23959d1943ec4401ab80b6ee734abe9dc2b7e8661eecfff4827f9820bd2cd007750793a75739a5bb74b221c3b5d51e9cf0b61561640c225e0e0d2abe2ca79ea201e6c466eb69f25d245f9885a575056a4357a72ed90ed077fb68defa1792eddb26664097ce87910a1068e7609a8463c5c1d3287575387df3267b7facaaecbb2e514e417af076e224581774d925be19781654cafd72738f1b3cbfe9dfc658407479663a492038dee4411703ae81bdae7cb7143f6753be779c566c37a2a5767f6a4351678c5712345c1497c9500aa54ff27e3c3262d4be47715809d6c597ccb2196f4da2bf89aba014eca1dfb5d24ec435b39588dfbaf3859edbf3f9375f0b603fb5971a2ad8be08dae43f8831376d3069501696bce0c9068dc9c4729b99d431c9466bb9cbdb669651f412fba7e422b836f8e8a24f34f2248b06fcae32f948c9c84df7637ffd74270a85f81889460c43607f3b70c31286a1c525d5d43a7dca8e1a837bbdcacaff604fdf4f86ff70ac80f7af70132f17db36ac843578364ed273423b5bb4c38d6daf6d992836e1b5f5618d49caf1a470262b455ae184b8e6e9f3200972342dfa8176ea73226a73d2c3226672d57e7d7f5ba98c9793427a5b150421c1f064f5d63e7ba1430e9644a4f526a64e626a304299f07ac67bb45607f91aefcb765c84ff62886c4e031716b03c7d3b07cbbcad4569ddf9434b5f29b860481e61d5984a9cb8b68be0a35ea07ab78b5ea6b9f50f3cc6fc59fcaf024cddae761948ddec9af2c1c9ffa1ed9c23354412a38f5ca78942e3f20b02e94beaef78e8ccfdc6b376f0e227b3e8132a141553823c9b31206daddf2267a81ece9d994e08daba823dbea23b62fa36bf69cd277d2659252d1201a3c8bff2fc71d9bf7522c9a8b05b515154ae74305487efec0ae7b53c3c39c6c0db8c0425f572bbb68306edb7bb22b2383ced52b94f68572f6d4cdf6631a7ca4fc7db91b986cf5a9d3ee77899cf686b7b99d2b7cc1b0526ea4d803184328c7a5299aaa69b3dc9bd191ff32fc21c3869fadf467c0a37f8b4bf30df44dedaf881f9098b7c98e50578a624ea035739f9e18b3151cc9b40efa6764b3eae19728bc516bcef83bd1f801bfa9687854c5e31c872d6809070b3d18922892ee66f7c68c614618a94a8f24c77ee7d2c7a01264c4662ad10aec34a70e513be6d5994985c23b2e9f40abd1faf40837f96ee15ef35fcd244845a2b7e520dac0183b7025398006a3896982905dd9173ef0ed348a4aca57c61c80fae199c0fe5695f3fa6609feeb1591d9cffb87324a912fda683a999cbffcb66c7630e1840fabd845175b6d02261c7f57f96d8d4d3c0b2bcd267786cd4c8feef14b14164c3f5e1e9cc33de608c3febbeb50d234c7a50d0f38d8fe5692398ac50093175abb7475a238e9b562d8f2e08982aa27a1304c85568d83c0a6d9036ce5b0176c0672e275dc1988198ff2f544f8c57f955821334831fc4a6bfba0a110b6ff7ead93624dc08eae934e5b7cd9eb288ca32b4e0e7ee2e23fcfa275a2d1f9d761577589d37ec29f10d747861bc1c5918d88221479deda038a87725cd2acc0f7c5aef3d2b89e827d72f3b029cf221c83ed23c00b0e5340a58fe661bb8571cd301cadd23ab20363fc0d321f0272049f37227a0ba466d08441537de97de6e0bf7d3a89267cab0b6485a440aa92bd6cce5ee2749b8cce047b00e8a9132acd9d8a32bd1711bec72c197f0c8142da1b236db8bb1550ebf0fa5a4257d471c5ba1da6c7e4d76637f24448db6cc9f7eb5f62cce14d283d01f7195daa3b159eaa0fa86e84f7c538891fd95032018fed732897220ba6ce74cf83ee70b0f7eb648bdfa60819b0f3a10f9dede354ae0657e05224179473cc4d51a9c9f6c56ee867d9b9af1020a83def9dd80e276b3574db4946441e6746874a46f0c9a080469dd36882de4101f21a6a83a337352c67ea30a5086c2bf81f8fe8da165ace84f8e6896c4bf8fc83ec5419df05711de5cfea353d8d7b8ff62e982f82ecd7512848419a60e552c3ae4aabaf70472637c12045ee85856b71e8a5e04a5340ae5c75b1217b83096d8be0f6f4251f1cc8106f973b802ee5ae22423246bede249d3790bb2a636a1556597e33718bda5c1f32138a9c7321d921ecc93b2165a4a982777e1c035434e998fd518d9eb41e57f2d509eff770c1210eaaec1f59fa2fbb9aa022283188f00bd7fed38d5a8ad05354087762c458061dd201b4def8cf43870d4cfa1ddba3a34bf5128c08a32db057133a1129195b770f273de281be1259b45a7fc8d803d1d878447f2075507c2e65d32c5599062829dee0c3d69d64a1255e03f75a575f63fa6c25f3c04ba53ea6450cf285bf68f46936c8ae651e663c6a6c54cc743dddda639f07e33609db9ed6a80a53b1a11f6a740a64b7a97764d1ab6253348ed0177c9d8df63cec84d4b9361e3c4b117e50de9c786e700a744cc2e0409d2ca98b15dc387fb9301859c9858c3138080c5ccd81a1789f261dbee22763b7847b3dfd3c9b05749b792ade12c2b8031354c90e0e65047b5dd59ea6750a5c57bfb7d4524e465521a45b56fae3fe37da515464efe545a3575abb1ad7217e599cce6449397af856cb9ebb94dbc4c3e9f8aefa850598fa7d144d00cc008c5e62a15379af8efb7c519bb539236cde3d1e56554a05ac330a085b26f71841e5544ed23820aab5ef2cb4840a257374e01a6e5456867ce9f9cec6c89cb4afc4ceccf063cb2a18d8557ea745b52a51f77a647f633f43ed48fcdfb7f41fe3a6183bd2cf73c752965706804d3befd22ad1ddc62fbb1be04b0aad2e9c62270101c9c6ac811401fa150a60e6144b594666586b9f27df4d8908881813282fc0ec6bcba01117b4827289843d387078dc9a6979ee77104bc17f4e9b8119b9ea7b89d8705dfd731a938bcf049be8111c68617a0b37ef3ad532f23c5f72e1fde32660e6c1f315443ab21dac0623dbc72cfbb916956e02fcfff408701bd6154c3b0cb96debf5863bba25acc63c3cac1f2aeede2b3a50d6337cf3e9a66e1ad95dac29e7f868c6d8f7504bd67df1c6e88c31c81f30cec2d1c6d48b57726145a3cb12c2e7cf42933130cf3cdfe6cfe606cc36b2f0d64cb5cb61ca50d3bdad954994e83cfa1171fd8743b699b8ffca252e992a4651da07c2297d22b8fdb8ee241072e5a3de6c91feddbd5b3456932d6af1dd0eb5a55a393adebcdd16fbfd8c5af41be25baae8069109c1d76e8c7066496b52966a1e9ec103ba2a236313312e39eec61a54c0e9b8d73b1d0f97396b91059ae22d2c99361f6b7ffef5c8db85339695b339fdb2728ca341f1490aed811ad76bc929408e056e37083840854ba2c557b66a549e393974dba87ef2b57d6f1dc61cd40088793a3b66e49bb7e4ceaca413889528073f022589ed3b6d53aa1a8ac9a637a19dd76dfdda47451953cfc22045454120879f67b566f142410cb1ff5bc67ef573a2fb7805d5f924d76e4168f31f4db33b79dda52a2344801281f0e4159584aff343f60ee2d15a0c41339f70d7255235cb894a7ff0874b59760fb2c12656ee082365ecbbabc87ede31bec018f76633ad51ac015ebc6d0bfa06f25b3d116b7f387fae8b45e518e9878a76a08fbf74f7981c1833d4f99316f70424cfc78024d96d8d43e11835b1e42cc3a4f4a7565cf917bc0a76128ba8fb26a1ed907b9adb1da96f39ff063d39ee1479718dead5d900578541b735b6d387775396bb2fa474a2ed75736f45ff550ef381bd71e297baaf856b175b14aa2567aa607005a128db27d0019b09cb3be7601c126384f0ac11674c9def440c5e9f46870fac5bb85020307fa42192431276cdbef77839ebd68dc1dfbf243a6f9305523f6f171ec940a901efec0174ef38e5ca48a584fed0a2de6ce79656a8028bbe10de2573b4984b2fcaf0c9047969f7a1dcc9c83ecdb33d20d9ebede97b120e6b7d1a2de52c16411e828a4487d2e62adfabe6c59e84fe9fe575efa6930e12fa8344b189ee1bdf1bb9a56c1c3dd779c03e70211e5d1cc383693bbaa1d0a2d438027da559a0b2ce7ffef2b83a04fe116b014801130778376255286ae04f2776267f91906488d6ec2d1b247ca4e8ab332983a0e7a7f7cc1e6177f5ec252cd3af24c6a5f7abfa6eb324a2ef4558206d26783eb1bd7790c5fd33a88116cb1793608ed0a650fdbd0d27a3785bca82bd205d510cd41cc703d9bc595185a405c9235b626c0c7e9450ce4355f7fbf89d7957c968cad37aa347b13531feced5597e5c4c0a755e2103a5e78936b01040840a411abf6db2c3ee36f4d05064704f2e4cde247383149cfc7f16f99059745ec5890eb259586ebdaba2af81a0b8b85d340d812adcd4458f75d28d9517120174af3a6cdf3099a632e300a54b712f1f794c8392715aeb872343be916cfac02e23deef0cfe2fe1d6790c41f6aadd06fcd9099bad7e9645b22b807b8ae0917c2a0dade0d546cccb6ab7b532874bef3350474f6cb7ff8a215e3e979d5159b99ae66ed4689654bb2eef94efd25213fbc3fb9b389d150e1944f91a2ce66468c8c4d33e87f381ccee1ba0332988c2e1fa9b13fac920546d1c45d1602856cabf2d473c99de94f9b4cabd483dba980cb1667f59fbf03682b44688e9788d378e1f82f79c84e68cc2b7661f8222247f76d354e414149caa8ae68745868ffe6de45383573c94bc790844a9a09db032c77cab3c7ee06350b2c7e965d0574f307bd22f624be13db0828c41b16295b913078d95f671f5d24cdf98244c973066a19d3cd94f8f7407478344f49d8e67e57ef499115e9d377446018cc72a42a70a456f1399894605faaa7e315b63039a17f407529ba2595adcea82f3b76457bb0d4d3b2ffed0013f2bdf273b91018d0f614abc456f02cc6e4c9dd6e6c738c64de0aa95f6455ef27527eb35de1bb23bfddbf4db95b9728ca4af08c5b9fa6b39243c0ad6e52324f17a99bc781f2fc07521ddfa9252ae9c3f754c3e8a75146124b8dec6f90cd4435f486d2b77980f76463931f94965af7e3bc167742e3cb6d6bf1f70efcc450dcceeb2ce52ad8c453e1bcafb32028c4f64588d133e0742a68f85ca6df3f95575676877c94373442a71ea733361afe38e4cd68b8aa77866f2452d12fd4105f94101e6f8233910e073ea25fdfabb16972470614f2713f8b62975603acd60e3328117638f457202a19715d346db54af47af0bd888d6ab0f13cb78b30264eeac7e33c8395efbda8cdaa4a2123c2e268aa1fe3c9c77e7ac70fdd1fa42ba8ae37443b7f7de115fd54117c29322e02eb823079dceae0a527d9d0dd3c387b737c4e7f384958b89c5799621f62b5194afc10061719925a6c26fdb8d697493ab8091bae1b79caf73ebfcddba5a477d31f076a9920403d212dfb113c7c2b867c43b57b68a84dbaf99e2ac6e4f205f4cb9da7681f364822a682433e1ce7e16f19a5aedaf205d3d9cbdca6b1e926866ffebe1124e0ae218bc7ee254c0db76379af87069cc38f2587b661070ed6818fcc5da6bba6bb3b361ec44551fc0fe640064f989b209f5bf26e9da9fb2d2459b10c68143362bb88d1841ab2da9fe1758d6d3f6c8c50c32fa34fe7deda7ff46788c211bb482792afc90994f873e6e7a6f2f271fe9ac828343afa769e4ab2ae9e4d3dd77dfcbff0ea1cf966e2613ee2d9ce61f3fc9f8a3f24a56b513ccb8db266f71c82ed053a41614b882fb5d7dfbbff274775d0876a1e61b96bfc56976a1c1357c3de5c0c7135d5444d58a4e0bceed735b6c9b5206f7223098eca10d0df388b6cc80f10556bce3b5ed0ba99f579354b2042d4b5ac9b83bd23ee2fcbd4efbaf6017e6de23d879384f8a25b582778ba1584e0c657b2fae883aae40afce2a2ada236bce032b5bde155ea7961ebde6620258846ed64628cc461b0ac2e738f2c1dbbc910bf00f2b5564e47c86453aeebd551886f89ccf3eea91bab24d8403dac715d04a45c9831de3dee5171123c633455371e3e461d7df895b26dc44c78016abb67a366cb7dd6458f79ee348eedb947d7969883e66d20eb13894b33ab26ea6a091af0ae1d6845ae7ae001f4954c6cdf234361ebe1cd48953043aa007c77093567c3ef8edd7b7159cf56709e6595d981d47eef5dd632274a596d552c3968492b2d7e1d5d729b9f7262dc2865d33c8e4adff195a33452f8d52fca6f6929fbee3bd0da6e051318be9e12fb4c182fcce1a0fa9489cb72dc1a3aeaed7eb0636e95d8e7953f76f65592530d5014eb17808e3d8e12b6b42fca1931ec725f5022098a28be064e45f39fcb84133232d5ad0d7e72229a8f1e458dff65c39b0cabd8747e22594d9a7611e3455a964792ee12ab8a8a7cd4fe4755a268c56c0db10965be200deb0de344266f6e997c1f77867f5f698ebec670e212788427dc8f6c3bb819032f7c82993c652d7283152e9825f74fedcb2d3a5894c3b16d86012d8b4e2c5af3ec0f4a14846f3728ee8c079bf9986943b7db60c2009749e2e198e82dca58c163c49b6e7ceea8c6ee4a1b0b0d9aadba6a5523b66565703cc8d3969b07536770a70058fb64ab7bb280ccb28f0097a8031e267cb54c1848e5d014bf97dad43252b4954a689205982fc4c801e511de1c7b3c32c830c958b329d6ef05c3a74080c27e2bffb3b464f9ad19c1d390fd576326c57123ce8608963f174dae186b6196f44ec4df600fdaf085bc89532d75a07f34bce07a50b7f2b76acafe573dff20c165dfdd066699b36954a7ba708474a4472fa31dfff4b78ed6786803aba4633753c84665f1d8530f26e980a39312a3832b65f051ff24c5d95398394ab760f0e5ae78f1fd9b018f51eee8a4276a88f3c4ae8ac1dfddd033cb312ba7db126f45abb5f3667f558cfa6d9237aac5d22bb11c5f87591122b83cc2dcef6fb2557a9d11d42f586e8c36c37d588249b73427a38f0b83af65f96e98a40655b42bd3513cfb59cff0a3bd84f72b3e1916f9cbe2413df7ee47daa37493b6d1afd266b09d524a560c905a36125982855d8dd9ce1de4a2a8d6f72fe42ae1923e20391ba5d39a316cbe1a06c874f93ea0dfe36d86f591e75ff829df312ad332b9a1d1c4854a1fc52747fb7eadd5c7965ec9c4637ddc8873a4cdc94024f3b305e0c3681f45fe55f099d14838ffc02f0953b95e09bf9bb7ac7edd399608c2a8bb2c833b26b9dabe870eeb7aa10bba8d549426bb4d45c896833bd753e12f6b5f4a16edce0f2fb8850e94cd279eb3ba16e3659d7f7f93a7b7a2b66655e4a42c6957c2aba87f7719302a272907a86d405ae6e7ab735f961b27f3f9acfec172988cd44f4af8ffb3ac298b4363e1dc7d7b6ae5d2a40cc35cf610dab8a1a56eb76c7ba11e4b60bbc6635748f0db9d50db047f6316a169a4f48975faa7eeae769d8beb3ad05fecb7c7e93918c87c4f5dd83852c4ebdf15d11f859e1776f41284d71792d91a2d6a20f18b9bca141db09f0c125bdb82a5046f294ec3f5a5f5bf481ea47952ad2cccd7dcdd086c4ede5811e958c648f46bd5c884f01cb663cd3ce7785e3a2492b970dfd1de542b7e4f7e68a5338fe869e5015f2d4c0c89700c07532232a08f80fd265b7b458f70174f59a66afd8aee77a33c92e5ad6dbac2cfb96dbd594e59162caa140a2a9c56de63107b510a44af72c635e8fafc08e3edce4ad5174f8021b262b9ef2e5a98fc66314f631b9a8cabd7634cb7aa9f7ef685034138f38ff38b0ce95bf59bb1a5f1058bdc2bae3fee151ef2ee14691420b12eea17a23f80cc32529ad293d18068e749d50260b47bae337f95678e22da258b6758e3e0fbef6d3719477244b514f0dae299a301292a115e1168553474e574978c6fad8d7fe2ec06f2e87890b072792108be9baa0fbf59704097f20db776a00a66cd17c91b18b6d84870b62c96cfd2e74f70444cf782fdd53a208f930341a3c3b524f4462cf41a53eeb69abbdacb3de02ed981693cf13634345e6d606038334200339d4d50a8ef7aa5f6441e2895e99307d65ce7134c8bea8b2c59e9f9c0e39edb08990b1d36b55cf093b52c40e6166c83fa2d158272af89ba25def80b0c3c6c40c53ffa78f3143a4c130160e709e2f7a13102868c9de0980a2eab80b9149bede200f0670b64ced89460ae10d3f036538cd07d725aca1ff153826dbbb3ef2462843cc0f7b77ff6fe6964e5304c6a4b6c331776484868b39cc7b61e80ad0b7b2fb6ba8ff98b8494cedc8d01ca1a937fc4e5fc5024f0dfdd15fde69a15bed29dc9d4c0269e7c9654c71925b53f45d545bac88eae0f90bf1066853233c16e3b191f72eff4d0dfe5e1fb27d639b22557093c65d37c6ca13935db6213b7aa432ba348cd95843ea60b75d9edb14be3329d70615aad7ddff2e1b5ef96840cb2efbc99abe56f322f2d3036f87ca6725856a49ecd8bc6b71d3cc9e8428ff23a3acef874c55576056280d532c1b297a0cc0e269cf09d8981b5995b2817cf3e2b9006f46d623b3589e396ab56955cd9bda81a0e4c8b9565f4a970bc1e431da1b4708277efa3fe8f53516b8fc46be73e2150db3ea145a9422eaccebb83481863f820a16c3952c3234ad614035752ffb2f98b2e0ce8e1acc7443f5f9a405a5453b7b3ca696f6ac0e80eb4a443c040fa8cb741ff39f9858aad7b98728e1a10da09eec0b1d7359be1cefc8bb5b436679dc96878ce7626a4dd56d62758b7d6d036bab33fa9dc538d904b3fbaf00df347a74ac09189d7d2c6bab70ef1a3cf1292834fab55a65d7b7155993d4c4a3154d8af2791279d60e7e5a23738fd7cce1f66c00b64ef9c6208bb81d698d39a6bc5b70526590f1288517ecfde10d3cfe696f8c3813baa3f2ec1fa0932722b87403354879eebc8cff6c5cf9fd76352fcf454e39ded29f91e9d289c9f7c6ec27d7bdd3f9bc3f1f733cb1206402e1c8e6d0bddd442e8c019ed97bba8c2f66eb2aa82e043393c74894f8a09bf96ec70135e834c83633ade2bb994394ea0ae608caa84d69d9de823783ff4c0df516cea34847c37031f78a28afc96b8b468d5a5db0a6e6ca11072d92f1492916255190d382d0b14f98a1fc74737983fc4ac2c8c3d5a7379cb4ac9890edf5ba795c92a89c581ccb07119becd6b7cd0a3cc9a3619d4c018f7f445f4fae98c540a9308de51a05e7c2e03f0db3e42b5e6f08d81c292c92b0005bc7b0ab65def90bf356afac011593e72e58ed6f4c17cdbdbb9f10d48b2c6d0ac231620231c5df71c99d96e33b752e4ad32cfcad2f236ddbc6c8c0419899008663905d5a04225eec6c1f872dfae72e9f8542b9c709e0251a7376d0ae81416ec02571fc84d9ca66f25fcfffee0ba3cdef15a49d193b3c63248206bce3401097ad84cdc2baeb1f04f4393987c20136fce51d990519fbae58c585bc4ce5420d911b70bc74fe621fcad1c1f0063cedf3e5ad062d66d0a9c9737c5fcf2d26ff32c7df3dc7c42eaf618176e21e3fca35eaf49a27d3a6f124ac1856d0535ca99fdeb46fb597ad7250c3be418ef6c3ce7f4936652a47df213a61dbd30601b6fe6594ff2d8f274ffe9bf7ba168b81e36b73bfe16d879275307ba9599deb62c185b5bc5b488676b9ae69a43a22e58c5006fc0270018b47d9ac61ed4201a62ba5ab1ec6d9e97d9bbcef276bd45d25aa03cc530e5d14ebaf0147c23cfb43c6f0d8e914edc0736123078d8fe933fac94ef91efe49eb25b16d7e7918cae81feaaf4754e730d64c0e96dc57297c39033037f244d8c07bcdee72bf3ed65ba37732d4ae3dbdfc71e49d9974438dea435c33c5fc3cf83fda5c34147395377c5e3098e7c44db0fc7ef75ae7f2f6b4cfe4d6dd073a0016a8f0c4ca1f1fd61217828f75511b6ea26f413db0e3ec630f61b2cbe3c589b030129a10513287c460d6765fda81fd8d2cb2eb445d2a3ddbe648326287e61a5874b23edc6ee790e27e58a17539b3a54c5fe24817d39e10b8e3560dfb6ac2118d222d2467f9238faff283e5662317f39498e039e73f021b071b9f87f47fc328580bc5fccb691b73c4528a3d9bcbb62a5d436e72947ae9b51fa48bf731605753117246b756336703879beb0a814a9b2e4328f6983928c9c92d5ae3e078dede2ad91508b6a4ff28bd17414343a4d7c4e478bf0673967c62c991f687730493782ba776df69f41dbf0ca337776de1528815497d6dd6affaba3d80d45d59b91925dcb61a564fdc7461558cb3dafeff7433f37af36fbde66f985c660f0e3cd041ff287c1f3d7152323a0e5ad70a7dba40b3e60ca15753c8a8b6fa91c0fa1a90459f2939035d92f7f9856173881cecf16eac6d1fb0f20b00aecd7bcc34ab875c3029bef7b8fb7268c21dfb79a28858985a1b5bcbe977505446f451ba5c002c161f87d5ffc8749cc6ed2498f90a334c69cebb776eaf9d76af14a9b78790aaad0c1ca851b454467e0e1c133a6acf032890bf7f7191ab0ed2fcf3481d74acc59be50a08c5fa2ed644c4dced932abf05c1227c35390c0738c2cee886d6c9ef50ece164499df5d5e98a1ec3d02c5901336f61f4af7eb81030efa38399ba19d986beb4fc9c69b8c8b16b6dc3ee71f482f91bd387a45afb764b7390f08c232ac38b6b56dc54826805f9a5cafab47c5dbf30b086b4e1c8d2d528c5dd6e86209df750fa53b3cc631f6f6d06b01bbb6db405772ce298211cadd34caefa269bbb27c3058224191f99210247f0534defc0fbc9afa517684052767e8f9586e81d5e1fe400148c8b96122b5d9fe180c8bede657121b6fafdb6e8b6ff8bbd3dadd185dc1f596dbaa26cb4c26744edae73dbb906dc125b45053d0901db46859b5130796bba481e1b7e18a05b5b9bc00d2b385f7fcdb286577a642ab566f2ebd200267dcecc95f96f5598f93998ad0f7810530bcbafea55e5ebb96fc85c0ef11cc9a2350ad99b3e5818720ea7d2ff511d43904254f87a8ed344016d04cfa3a120c5abf19e641178fcb5597f74a63e6f1debd844a71e9292a7a1ce9c0115805feb89e29e616247f791615a4ef768a7aed04dfee76fd4b1b5c86f1cab51c8a1913765b5e1b2f17a3d6f249ca2ade108eb3f41327fe14896a74ce6c98a51b10d9de90a7dcf2683a6f3a2f08d5b4ae5d011b50fde782a92d6c84563b73a13d2b055a78f0425748c8dfda9f4cb10aad5b6c52865656bc5629ab31e18eb1b1fbf104069ec5048706e8bca04d2456504991cb2fdac90fa7617ffb5971ec3e449c34190bcafeb04801df29b22854b40b7ea4843e789ded94852bf06a90d31c3a52d366c5037dcea83a584a1875fe0c600bbb85d377f839074e565bab63077c84c9aa80f4788838ffcdc2dd934c2fff27c67d4b69b79fc3d9c861551633adea9e320eac558fc2d0877695a48023fc9df055c32c87e5db710c3c13ce8760799cc774ae4164160d78e84f4df8120bef09a26af7599de48d0bf9902f37de8bb3121cbe9181f2bfba1a80c35126551a83288df7e3fc3eab7be14211467af644bd276e2515c43ebb71ec5281cf59229f8a65f047fed9970e214aae7b30ca844a2d559dc34d035ea92a1cfed3db27ffa47676bd53b162c1bef8723d1bd3e252a2bd45fe19edd970d5d4bf4c475a199674fae06b8985665c83f8fba8751c08ecbe05e936ca0e95f298979bac2c9ff0996533fc40ed59ad3190d372949b93f553df9cfe593b8910ab0ee1d21c4875314ba11dcfe65e3cd8762f56d0a9edb5a07966173fd854f5ca5ea3f07c8e5b38d29d1c4f830484ca9ac7fef945f445fbf8da1f0a9ceb19a9ec2bcee54ae8d546780ee237b400a4d98e18f4ceac9d48b81471a00046d198af0607aaf7b768a46ef6b069ff5f17320ee004dbdf0f29eeb48b9add1fa8d3821da1788f5ed6ba93db08723401bdfdc767ae93cd656c0aa007df91818165cc1dbc79493408a77bf35b418a7bfe8095ff6c5f42e0edefe5b70983adf8692dc2eb049eddea30c2bf081261d65ffdc9a8bafe679c55f57ae34c2ec001b1feced14c9c8bfea9bbbb57add205cb563c1fdf41186ba4d31347f6db96d8426288d712fdf85a5c52046a443d834e435377bb93379eac565c1a625490ad090c60cda5a00ab1991b3370d6517fc497b740f03bfeeeb72b25fd6d9683743531a9f584389591f3aaa675d5d9f98d3082d8394eb2ea452dd4fdcf273b47a05231d37c3ca00e5d096fd682301f43592a3d13e3f76561f816e3b67d70ed6d3ecabb413ca4e199d30f9c129cb85071ce0203629145b96cf00eec1fa8432460dccddc59e5376dc9f446a54f53508ca0591c8404c7b16871040af43f671b7ea9b7f79a600a36211262fd8aa7862cb79fb6a10075be49d653fc1941b0509d5612cd4a1efdda847595e983b5807327460bb7a7af29df640f1fc5a59f6f4278c394c852ba95453d96de82f82c8779da0cdcbf728ec9b4dd5f0f193a57d078ad473b6a2e99f2a05eaf2936e51e5b4e251af453c5ede696f21f94991d4903939f62774a377d522728da52ababb7d4e71922b3f59e6259b683c978450433e35465f72ee1ed3036453c9ab46ffe22360bac1a75b958cc8a05c1735ad39292188f2ece29e6bb5fa14f43dedcfc6fd6023947952f05039c7e322eed344e8d5b7289be67da57b5ffbbc65df61cadeb912a37e5462a66a1bc9e6ae893d4fd9ee28756b9bbf5d0021f3d5983db374dd128617b65182a1bf029e105cc9552c2ed3d071701b966b159cadd5d46da75b1175e3803af8392dc29f4283848eed00c0d3cdaebfca8d4a76067dbf45babe9844ae4189f3c2328667c62c91874822aa5cf72d3dd138de2fe040936538d1c7bf408f403895589ed63ad818751dee34c3b376ee54e30a49df59185da2834f2befdac388086ddbd6b011b01bf0db1f59bf0699fe45130fe8d58c1aa021b5e565183d932cb20c25c906854c2425a4fd86bcb41721caed773287e7689f938302b7545cddcf7b91f5048b9338d12838924f9d47cd8cc1f262884b505045e73fa904d456b2837cc06e5c3d45535a110a799a63689b0d26268029732b5d6309d8e5c61dc5ffb45c8d1f16a0f142f86c6a60129ca16d8062615eca6ad6dc541a29adf343c4021f8b3a6025da3efbacfc93486d499df65ccbb6d6cc43a3d25992b9fa29c37094e2fa17d4909ed548c3dd4a384c69e65d78564dca91c20a625701392c03bf21f9e4bc3ede6f527cd57a164b5340687ec524b93e2326f4e913ac05b1cdc75d2968d449368cc061a9b0fbc46daa6b7ad36262d4099c6ec3170025546eb5198f1d7756377b558b60267fc373f68212dc713b82ba38c92013a61de23677e254ec1e79e02efd4617c07da35d5131717e410c43b4c3bd8c1db469828302b1175b2ba51ae9bba3bdaba4d53bbbed88d6676ef7d72426764bd7085391638fcbfc3a79ec7b5ae78eaa92b4a70ecc3e413ae330a28232dcd79bd91d22d3e43d2f6a4e006bf683a7c642842c52404dc60ae5b7c6e8821d2fba53acd5e8ab3f6aae928e4841bca4690a6c8dfc2597b7a5fb27f53214120e860074fb933debc475406dfb02b639b391a617649bfac33c4ef058a453288d505d953430ff7941b5c4776bf98475e3ed6ef95d111dc0db6e6185468e216840f94ddb9bb4927d8c353948ec1eca0b4e6563bb9b56105f411f8d4128b128a4730359c365c437b1e617dcfffcd04bde0cdb76408056517831c4b4002745685913b46a42df0f4633c18a72083e785bfdf4e26686501da3126123280a738a87fb071e015e0d29b1322809d55a9f2b614953e3a7618d79b7b9182652f9b7b33d3d949859c83933a21adc8477ce02789ade6f6a2cc72a6ec92d7aabe109b5cb05a4f16d2570caf23761348bae4baa576df8a947f4ba60f6d64fad63214c7db76ea65aef084da3ed90b99492ff6e8fc7637b6cdb70155a80d6da7c95d40243bc08466b1168422f6e25a17820fb283f91cf4aef0dd3c55fa78286b390543acc73c77037de8224c974977fb981e5c21376dbbce7621ebeb6d92dcdda29d7bbf055db366b5b7cfb5cd86858278ed39eb1f225b02a694ab23708d1f2de22ab2ff6ad341923643a0ee8e2ce75b217f03e22e3173159fd8ad031d405214bc8ebdfe6e80d73641fda3375dcf33dcee12b630b6f4161470ee7b0931ce5d9b3c3dae0f9c596d363737a7db882aba7f47635ed1cf9adc9a84f4ea1e04daa3af0d30e2d1919ed7cc38fcfdac68d4ce34810c92e6cfdc5ff586151cc4cace019a8db461ed1e25956b24f029f481f15694aacfc7085d938615a283b83b50ee0bbb27d95bf81e30086f9f9df1563055e221232a78fec0ff42dd5fc3edb7122e24d9841ae8db863bf4a6af62c4ac1effc0787b6766f30394205f43e42da57d5e1e0087101e7fb421a5f65ee8b80bd357a4b5936618ba2d4844a25061f2f10f7f822b6fff65936dea2d7d983171a92c642a49a9e77aed16f9fb232194a4643761d0ce551b68a5ae27b7c4890fe157ff022a7954d787477e51e2182f12faa2db4d27c371501f3c9effa1e25e34a4ba16d76dc6967c83985dc7d529ce87bc42f14e830a9a5374fd203abf736116ebe46b56f871cc9640856cf11a8f11ce3aad354b566836c89123c7efba170a955635d8a4f238cdf128e6e2235a36167f4894a374995531e08367fd7e19b90f4c27c604fad36df094ae0b2293a1d69770a4e0e97b8c4576447e3f5bc879105d1bdaecc08f856d2a89c55aada5d9867cc880d1e314d1c1be4ed49438b06fdd2ac97b67ded7fed015970edc65c582e5dd61ef1c0f0d02544f5b8ab4e45f7f136195431a6d2a54817fb526c4dd8c6e4d50f2a4a2d5d26ff7ad4a5e3284df5812f832f20ea030c7356f744fcdbe6ae264cf0d2bc67dd1242191ca6bd6711e3b4d259f40119f2b94275dac71cd428af704f5a580b6d52d2ad2373491f89120b5ed281ec6ea0f2ff84bca90b7c2d1622e42354f7e8cf0d14a6dea8a75733374e5371852824cc4549ba5506c4d3a77e0fe52b001d013756f3a056178fffb3576a08e5c3e82d8a84648526ba5823945bdb941eefaef5e583fa3a110616175966284648c38548bc81d4430ea989dd19d34c670fff310ddee962d7a0c3a37d3b0f86644e4e895e8f51733f9ab99b301505ce624fe83a746e09f1319299a9dcbd81868916dba805c100394df4966f62b3451d852dcdca603c48d74411a9082b670eeb01f5c67e62e6e5f85be96c2bdeb9fb718dad080bd93c9c3153aff9dbe8a06a52f89d6795fc4cdac13c6c76c4d837255048ebbccb2a845aea74eadec403e63213c5910a91950de0d723a7a6b25613c9534a62f9a0bec3d5d1213b80752868dd976a641a47cb52ed479799f6bf845c9a664038a5c69166396627fc59737803d12995caf4d2971c84394bab8591e2a8a35e6b707a6206fad4d650a1c064a638e7c3ab05940069a296e5be42c41873ff4440250cdc63d8e5eb21952c59bd444306d46d76d602b0dc458fb285542f1da89e02bd26e6bac25c080bcd40aa572ab59394dd4f8849db87241ce23ee30e51f7659bba46c6905bc77b956c50eb87d5c610a797db06e15d4374778b02076cfd3940106a1b345eb22b246e207757453b0e2a3e430dedcd2b82f52de8e3713f8b451c3296fe55276b20f06c578389e65b56f105c9f321ac2de2339c7e83633b2fa6d1774db47c0d00378752d33823282fc00b6a7af8b72b8f102214bbc971b97f821449dce95ede5415ea1fcf7db72dc4f5da19a1c1d049d6fce63fc0a4b937a20c41ee327adf8a73eaa3e3c647f08b0daa719683655bfc110670afae038f8d390a1481254f10d21742d801540576c0e7cced0d0523d5b2a0cbd0916f21caff923f33faedb1c0af9da4a6e7c24247e7f969044cb7f9f1a3bd3b5d23936457e9fea696dc576c71a5f1a72afba8984f2d9b616020cf738d9467b1f19477889f5328bf88c2ce9aa5d01a43fa78ffd30e97705e0d11530530ce0247ea334c62268e38676305c1d8220ac4d84519ea045173341e3627c16b222812520cb2aedb30670032eaeb259224b95bfbe5f612d2945288133bde41a808fcd818bbefaa74e5e6f367f7298ece04e16ca2a0ebeb2c8eb77fd2d92b46188dd2cbe32cc2e75fe16685d5cd661f87e2701115fe4d2f1e7ff01c3bee3b110112a3298bf3705a343c5c348d56c6cfa4dc5777a766bd2c5d81116dab9a12a0dd13fe7890ec9e35f7abda24fc2b5a6be7663d59e18e337442e27ee33a7f668d1b1b2acdfc87710609a7e93e42e44ba73afb7bf5a2cd759f8386f2589a0e3d4eab092c290911b771ed9160a03b102a97010bc660723911d78e61412a0a2dea96e6a84dc71d7d29f3a661db7b2999021ffb594d90c03df9fd63a6bdacb7c417acc7d66020a220a35696724993298ce090a28dd1ce37d3162b74ac57c77de015814f0901eb7987818ddfcd5064723159dc1ab01e0a5880c5ce763efb7eea4513d6d2724403ee8e253120765cacfa33cd477bb7a885ad72f6a1730b9855a0378500ccd481dac619a06ebe15ddc77c1e19b2b179f9d7df941da85716bff5ea7d6e6eb1c107d4559cbabcc457c5e305902949419cbc9c698df64678c901467396535f164b95177eaa4ea4a006f95ad4121ab150ea4030849c2afb67de83b2f5561af29b0d26ae4c3627229141b2036f76d875c9fc37b1c0e2450256f164eea4781f75e2af83930f80bbe33f83f1a9babc5781e956f1d49cef686b5ffa0944856a3a371051049500cdd88f1bd3917764039b8e0d101e9180ef30f968d9dfb933945714f0a45302cfed7897177ba6c79003bd75176af6cdc5f15f19795b6b016d682abeb9a3f757a5f16fa7748420d784f84a6ffd4d93c82178c32accaa95c5c967edbff5727138c8a433ca6cba3e6b282d7b8fceb3deba8467803958b26ce55f48eaf411adacebdee42ba2f7d2426e0c59ab253f32395be73bb14dbc2417581106cf9af4a24d167dfa7df3bb27cc2c5ccbdce6d8dbeb5df18e2336ebf3c3e14e6a1c9c417eb5e6d5c129b1d66981e24f11a0209b4beb18d80df6029ce59af3c9f8c81d28f53f104fbdca3b3e7026246ab53caa22b0236dfbb17fe63400c52155e9795368a8c51089d3f93b7d71b9691c434cae4644713a30c41246628d9f01e57f8ae7d9a0ba616c322c95173c6742ca854a49819f5e22e237373e3e05ac4aa379241af995919f083fbc1b4ea1c74697af0776c1bbe43dc9dfb102af86cb3f3949f845117a34754923b9485b09b592445c0b9cbb2610345d809f7ef54f7221571f8ec67f3a3c3e962ceb2a682308f0c98d054f0de14f8804e3dfcfd88cac051c433ed876301fec828cc8c7e50eea6dc7b4078d76d8f036f27a4e92045a6e8c603a7b157a21c668f0ae273a847060383895261e0d3ea0c899f0e9656bc522c385a6a5566fc235b0699c2dc15554cecb118bddc310c7d172f4b7737884e8e62afffe1b812356ff460630162efb51bec5465ed136df8359e92d1c0af6ec4d1df82507dd63186bbf240a164a629a62f3733afd41986a83e249b9753778432cd8790569c310514d2e0fa92a0bb4aefbe41b7f2c9178e4d66d7a23e43f5df4c5a6868cec0f5075f92cccb72ae7744fd093a78a682bf15f033b1be8e4061b8983273b47a50874744731af3b8f75f7ee6db3c1ae6ceee9cf9beac22caeb18090e3aa1aad1a216a0707f3bd6c1a29f8b296e8ce0cdc2ce31e9f4df7dd1e8a6822bf3427979323236f64d893321417d2d50357e85395b0a5a54f64f4e120be4d28abeafa1f8907c09fe1fd3aa9f3135f1a9fe6013ac64ffb7db487c2af26675d147321bb8f465e6bd53ed6223354ba57074707d55bd7206374e23ee1c41bc9c73c0fbf92f8b5e7837f832f2d7bfa72222859c1e9aa9ee1282115c873e5646ae5d85edecc551ee3324389f7457c03408c9d7aa4d1e2912918e4a5948e3ea7863ad5b63e38ea92bd335717d5191ccc27b943dda964272bfa3c381818e0f89cb1144e5b81de30c05480914be135b3aea5bb36e4c083e67894beb99df2b3ccb30a6bb0c45185477b1c82ecf8a8de3e91ab4ecf49fc9e82d34bd37f1bb83c229efb79e6410184d48267cbeb852be0a0368d8ccbb880ab87f46ad73c510b35762c7eb5c622a9ab1fad7d7960290a51ee322b2f12c76f103efaaedd24de61b844529fc2282f8eea21feb7e8d7281e55c6c3b7cf9387caedb914acc162378d585d4604a6c20fc26a53df72abad90a6fa237195831ea36d4c2d2fcb16145f7e56f8e213087a75167b5243a7fb77f177310da170477234ab74b55e04311f1df1a6b3cc87aad12d85e6e97b7ec469362983f19afaf2652c9070796eb167bea44758aa7d07ce831093f3cb7ff90601bb07d1fc07070ad33e54a07694c31ad7fcf302e940f66630a02fc6a70cd74ee10020c362ba43d4abf5b77f86f747b8df6f807244b3eb4521536f54896473171918ac2cb01e63f1d3246f3d6dcc77905c32b7ec11ce90ebae2178c3845d082ee1988658e6b55490736fd41d401ce7f93b79d706e0c0dbb5d783775b43feb24ccd01ffc0ceb1009bf42b4b03985b1c55e7f852551d2e7016d615b3c0f13af3a68114daef501c21974c4ff3838159d424f5466dad22b60394638c6ba9b6f150be303ef04740327b5106902d51996f2523ae4f789f37b2928dae381f30a28a44e83919a366c6adc7c1cb794ae0b9c6467d40ff1f996fac2671a110cdd84a62c3cbc27ae304ec22d13026f73fd6236b0d4c23cb044137adfb12a9b9c31e2cc0eb8dabce48362a892f7c93bdd15bb35a07308eb831494ccb3464214833c630ee945fd1bbf6749cfe145c12699d9ad516a6b432b616351f6e774a619340e4cfd7e8a333587d0fa48abe437c0948ab6c8fce9b7860cf5a15fc9f3f1b2b6334cf8909b408f64ce3dac5bd5200f1df34d15bd8b05dfbb715929997cae9b3364787591edc3e9db2715df4bcc03dce03471d3bf7c78387ff16fd061cba7cda421e66012c276556dea5741630191b159d0a2e9b0df58bc2c55070538bf72f75fd96307bfa8c15627c1a1d5d3e4365e3f49d39ff4635d634403078369f4a63d9adb6f713808ec269a6e871e262a1e97fb7d69441f201d877917362c0df2d9ac60fda5e74c5757603e330d68d3a60a37dbc4f5c95bffb14af989bb27123355926cb5ce4d71df57928bd814b87e7865ac6e59c4879573f9c1099c284a3e6458f38735a398cf6b92c2000a655a385d7a78a76a83da0da5572c16f1ffa072479934a4a204368211f2c52ab5471ee2cb53f3d34c404dbf9b0013a2f47f1ce77e50d27e3021c3024555b8c1a3edf92010bd467ec34779b27910193f8b70f1ba4d65868e6b2313d27498c597850fa060f426d667509dbb2e6c7eb6f6087f663fc1b6f33e4f710c592856e2bb6c6cba01615e8e7b3df4f9c58fdb02a115edd5737f52d1605b8e294be0a3f0b69b398acae46077aa8fca480ef4a5254b85a7a3a0d2259d717025d7da7bd9d8ea6954c8c131f1255f011542159302933e61c01bdc99be9902e6721cdfe16580bfad07bd7535667701cc04a0730e0c17d871a8356cf0891de14e8ad1ae8a405aff4701d841457ae5e23150196dff1489b0cba6af8eef7970cfc2bf389bedb37e1d39749b6fa51fbddb2ce78d07f1c9d663fbd4aa98b6dbd44a5a6338097a4974b67921fa15fa2c4c63a715896a97cc6bf6890e12942959aba694f458e0f5ed33b0bad6128d39f2f10a5be76aa0b9eb73ba14f74e82310373000dc764fa7d4d0673a283b1b9e65dac916d3fa592a27ad36be3fe61d85680a587029490d2a07e6ef3be56ee0dc712acf9d357def3715952cb32ff59b1ce2a56947c479d13dab6eda6adfb1ffc599777db73e11851d48f04db80903b8086e6f9ac5a26b4d5701ee487c50fd9ea74ad8f4f18173ee015bf57986c3b4cb98fd5edc59b21319d06599ca020a5bd84861995d221a494941825d62aec34ff7d219e71764d9c0b5bc4dc9ea1474a92516e5b501a1eef2326faba77b26443694d0e9c3a9668eea034302b7ee8e2d33174688bb368de1663c50bbd3475587b01935fb98ce0de6bba027b1d97966572c7a1dfa00b9de4e3f74297ba5695e56d197b94b4b7dc6ff60651c7ec5e59dd27d7fd510e13609e4a761f0a6cf2a76083e948ec1b9b960371cfa610c5e88e6aa826d3d444b4a9deb0d8e30d41c94040295d1b68198c180f5c40556c2363f2d1775dc066be76351e0aaa3d72dd5420815798ee847144e7539a6bbc82d2800b690b8fab88400b3430ebfd73d38ddfd4ad6fc9ab953056775c14983092468e20e02514e3851d308da6f8dd894187d3a8470ef8e154a2e315b398f3d9f5b7a03aec01adb84ad3a9325365bbc798db2ced854a4789fff359db22a903ef61244478976b4aa01de431bbd108e6087c1c39ba01deae87c8198d80f73ce837ba7849f09c515465b5e8c7703b0849f7f603b720198969ff558de072fb531442860e60cbe14e5ed0f88ef03c9e346620fd72da51d4bc6193c336594b24522ab66d9ef7bca07e66d89e7f8600338cd847fddce6e6c648fdbc627ee2218d3ffa7c4899b0e85eac718e7fa27cbd7b63801b87ad3b3be703c6021058636e26d4764cebff880e24ba1648f06e24970fc08b793bdeb0abfae87ff9e012ac1770a7517afe37ce728f3413f4b669e03af032923158b065b5794fbcea0957b03a624a5cb2a5ad1111e32dd57de7cfd5138d0a7532daaeebc69fec4b658765fc80a9e65f3f42377f39facaa171fa9a05b497429df8fc78d61883e49f902f17b12fb51f4ad1c78af01242c1a689a4002583696c164bf0f2aa33fc51e31bdab3c9f580b1f36ee97c3e58aa0db09de5f5c53dc635796b3c86bfff11ed2c09142ae67a91c7c1582d5f0f171e2e4587a7e286afdc378f4519124cd78fa62b264acc0ac733c58afaab3655cc9253b09be959038e63b255e0869a48f247e0b6b472f167c1f97383304d4e3562a3f351e12e61ad8603502cd5130b54f34e3bc8ade8c3f6311f24bfaa23a0e64e0a0dccbb83c2de8444b0e3cdf85282b31b8c371dbb9bc93fc050ebca79c3ced73732ad43d437cbca02977dd0c069b9d24102cba516cd367ffa9aaa2d0be7aa8a4dd485190b6cc2bec4cb74f79678e96899cbeecefff867b8ba1099c91abb66bfbb3eb4deccd11816ff9b001e678591aaba905420e3808ae87796d761e3932e1cf85c5e5a07763fecbc21bcd4217a2a69092dddc5b3cfb5aa2b591406e26328e2ae424279ed689797bff89495860050f19a9b8253d1991ac83b30b6c3a32fc80b43089d019d110b55d1e0dd27dfc1abe9319e162c12c511a6ccceefdf9f4aa6c03df4d15b8a5036b7383dbcd7bf95df9655f543a9dfd6d264612d41b37c8cce397486309f6248ca5d01616bd6a1e4e3a0c76d26018a1eba5a8cb1877ee18340c0adc745e90aa35807d4f85785076144acc9cff0a9e5529ae018bfe872a4dac4456fbf21c6260199af594448fee1315b3020391b685c601840a2c8645d73d95efbfbd05b841b5d08c8c6dc75e0ec8ef0e2d671d42387aea295535432b4f83dc6d7d0207f96d972356f7143a51bc7bd982f1b3534ec937f9ba44add8c0f861a7a44f65d8550ba8cc4e807e430fd08a431195a65bd700384ae389c2a355cb1974b9c5edfaaf3661c1b8aa88a46f1160dbabdf8bc66c4b7ed237289631401a0fbff1f968c17682b53d8d2857efd70970d9b1eb78467d2a4766fa33a979564a5a5f59051c508b644fe98f20912e2cb15d93d64aaf2ad231b3a2ce156d096e16bfd976fae08faf5b0eef3ed30505a7aa04073d6ae2f23cd0f241b6b606bdf488c8ad7667e8422df438e37ab533e3bc9dfa29236a82d96a5505f7797f8495f8e754f228d5c623116ca5c6bca9e586335cdaa246c162e25dee48458d471a9f7899dcbae48cc6836dbf8f097d5d6c65054f9420757c015a3ff599afdfa84acef6c3cfcfbed35676843bfa09b89ef4a6a6ad97dc4317e135d84988d3d1897a9270b9beea89fc87a3f4a7c86cb0c95760ceba3c07df43e07b380e35aa559aee19a8e34438afea3b72bb06537bf5fb5a0dbab7adfee373d0d405f5eaec6a6174880dc0836089e27de1684036dac2eb898981d31cd520c0c3fe44af62cc46513f045586af06889d83eb1100d48771ee65e46f300dd8447a7db164614b640e2717f771a4348019197f5db89953bb6038140f3a25e3fc55872932fafdfbf7265413d453ace144bcf1081ebc242d9d2d78a41b2582884f0e28368843d14cd3708f9942aefb58270bcab47326544b944d2a087f5ec50c74be7e0bf79a83b3a4e6180f1d4a7bf75ddd8fdf8bb4f9e5e8c9b66b123f03f0077fdef9db649ebf93c0a02493da9880edbef29c76fe4b4bbe8a235d15bc6cd23e79f8b628ea76239bf81a94401dc2af91d5071d6d407c17d3efa4e561d171c26827c75eda2472d697713f4e8159725d25c37dc24bbf37cb2a01c9621f65490abf3e2ae7bd906d54d57f4f58a2567a892293e569dea4415d05c12ecef9e0c23fc46be0d38319ed2ca5f4547740bb437fb8db6997e04f4c9742f908f8af760459df232dc9395ed490adb464d2f22243015ec863e3895af75126bfd009358a799672d1d35a761d09d6a40bd7699665db1c4e5fc94fd758c684cf8121af4bc77e516dc6fc3adc27ec4fec55a87d13ab71f777684741f46faf2fe9cdb042c52ce05980766bb5d533a6c951876ada5827641bc2954f26187d75cb34fc5023cf1df53cbec371eb84eef848a0e42fc7a448a60ec9c5bbebd7882b28ba8f344e8317f751b666a6d889e23d1d8aebd8e9079972632b9b9d2f7599fde0dfeb5f6861459a459278c538454de666228d8af9ca0aa578624863332dbda2aa3c8b18a134061877cf02656654d1be53c8afda8b0b7f61432492dc6b95b2d1b730ea51c65fe30efa726a7d0866597499da4452739b7eba9afa386ddf526a36a6fd6130bba122d035bf0b5bb29d952607d788afe3c800cba4a7834f25a499fdf17790b2443987f65ba1bb4076deef89ba1fa43496f4e623e286c38c6afe94788ae9748589d068a12c052fe348edb59245e647c0c0f1c4b1397ec9a62f54422eafde2bb8045ad1fc394b5ee8182c654c49b37bec305f70fcda96c01e39d774d392de1a723d9d3b7d0da1b7d490254d2ff6c3ec3f2915750342f37f83ea953cf8c5b0f8194d280f4917f550a560d44c00801162ba5ed221e674dde3765f13fb81dd7f78760489f8f6afd4d472f6cd1a7248378eac7ede9de3df790f76160437cad6eb6fed0b219e19a0c3bb062409b811b44fb0500a5907295307ed18affed9a95e814a7fb706f81d7a93a6346f506b8d6a852f2053935cdf39457027f687ca508898c20cd88ffd3e8ceec79f1ddb9d2ec5f95495277800eaf2c601ea1a8e3bbc29dc338c6b731b24ea2ed5eddbf1a9547ab6c0c58a7f6eaf47794bd45961982e5adb2aa7947b1eaffe1c127c83f309b45b14531d5c5d7cf7e7539d1659141c6c2615576bfe754f365ba939a45c04cdce7b6b0bf89f5145965507218910fd78a66bae73f92b5adb04653011ed2dce344a8e7d11fa67fdb2f77a0bf7ff9ff7d9cdb498d68069dbf8f27e5e0685bc351cf9f991cc682b86f71c2cb2f1e6ff2bcf848beda98d8e93a09f15877162630318e5cc39f19a5e84750d15e352f20833ecde7d4569b0644d6b8e7a83d6955ef3d716adf06e303936bbce7b6b7e96c2e4eaf451c6af350eb22aa540db9dc9b70e9e08cbda75a29ad1ae488a15d0ff0fa5f842d799328ec8577f8741a343c7ed0daf767683b2db6658c99d610656dd8eee90bf195425cadb2c47e964400277abb67306023a46aee8a28d2d3826ee0d97ea96a387d5660c39cc851b09b33d27b6d6e23a6a546a6acdd8927ab74d00c856b9cd9197ad7c4039098aebf1b1d09f5d1605c73db56958357bc9a17759ac7342a207f6e2371b78cf3cc19b47b0899e907a97aefdce0a6fbfc6a24a36b349c0056155899f5d9a1f045edf12df0714277aeb4e5704f36d1985406646fc472d33f5c6c646a728407c5c5b7c185358881901427f9824ac354b1df73ec3f8b27f380da932a9abef212c8f9e9d7b16677f6f1be3895b7f49ea9e75c75840e23df56953194102da4e0acf55e1d796f4c8402d7da4303076b2d86b4e6186ab43ad436eea5be70cc388cb37bb1ca77521cbc91a49ec6d97dce2c68cdb02693015f6fb5b9899a129a54dc22ae46467516bbf536a9709defafdd84b0e552e4dffcb5aa7672386975b5c9779d99a6b1092338b07c84424632ea26f30db85ef722d62322e53cb75f89ba9659eb340cfe121f3a4d347449a061a8e86643c3cf084d7f37fe811e36f479755f0b63ceeb52e42a978e722bebabd44e97d4c8f84bd5e9bbd4347e176a43c3057d7974fa9c9c6cb2f4e7ea039e226c365fe9ea88e3e06451a5d4b68b23d764bf2b42afe83c4f1cd40935afc7e0dba2041237048018070d1ee4906bf0712efc537e71de9ab30e5b9a68c735e079fb2b4a370343dc60c86e442d6722bca1f355b4228fee094159bfa46b0028963a8777b23fcb546f6630c8ae34c72f8d43a5cb9ce408aa1ba017c99fccee4394610c4d4f234a308a07914ed60dee418b983b0145fe68365a96d839de50df149b7c66fd7d565eaa7ef6b78d4d5a64acf99fcaf36162a2cc97d0b020663d3c74caf9d694722dd6d036a2eee4f357ea8a391b17178801eb2f46e5942983582d5f9968a913222979ba6be525c47053967e40e2d04e5015ef76db76aa19640d0d990dca155e6061e4fce286bc0ad148f2370053dc33361d4b73be5b6c378c57db04fa3536876bfbd23a327943eb189ce687ea6582e5fe5ff027fcc4515c646a8ebf2d3bb51f1fa2288c3d15be3cac93dcdba92b3c3b5ebb585c5d455051bac1faa6621ea8bff9f55841bd70b71dba40d80b7f81f5e418469d90ee6fc238b4054d4dae3ab32ad702a5bd614cd17a787856475f5b3e77596017f4efbfe28d72d35eb2eb43d86ce2660c8cd1d4c678a94d0ee64370c055769f526a46e995c63ef6b174ed6510c57512012c0c207f9f394fc61136f805ff2b90672dcfc8676a4f5bbf724f3e80dbca52c1c4b8ec6c4b8a670849fb08f48aad83e4b53bd5949b23231cf79dca7687f4710a713490fde255ff80bb58db3798d196f485a3aa537656e88b26d8099799caf6ecc1c4bf2370ffe14861ff38657fceebe4350e95429e378c521d7a5baf158d010b15d27324d8582db74175326598e15e967e074ffcc8f6233d645ec812c688a1ce4fa3741cc5b85f9e65d24251a255996a78a23efc53bb3f90cd4bf415e719342964fe943dd4ea3ef73b46ea7220aa4a2121aebd1fc89b73887c542ceb70ce0e1d2388f64bacc33e11aa468fa25263748df0c290efbeab3a62ff13b7b1bb1eb5b6f6c783384f72990f399770fea85d46ee43a9eb8dab5e99ab65502fd9e9ff0a199484b0110f4db9dded9cfc50ab3676a2daf554ac79a07056281c7d12781b0121f1b0445c98e6dab52ea82d240e67944a4853bc4a91ed377bbf9884dcf0a9953fafd0cb9b7ac417046590d5191cf28f4937e59cf6f258727ec403dca3cb5798b817fa5413847a3c018ae0406e2ac4f7fc53b39404a155c81919d3c36ed102a3962eab382fac7a476a31923ebbdf98fc1d36b78c5c35214c75b5bc6369b8984276d302af698d3c066e71caf22770136cd9713739b413a26ba6daa3a6ab902651e978bbd2561188f81e8db0f8319669c71279adc7f90aa08d66b608a9ab6849cf156381ff89f62c07c8898a9267bed91c3579f94e36fa6071e0c4279bc188cf23bb203ea68e5569fbdfb1099b5da3e020a6249d31c993d5ecbebd37ce654d523661ebf8da79af1ad5cd19e4fbef866a3d0a0f2ba91a6c1007dd126d1a4e2b90889dd9cd8f3b3456e48bceef4e7c82f3b674fcaa0ee005c8751cf8ed22082d9fca9e7c4ee186b742cc1fec7bcdaee5d79c664da1660c2752fef7d9439b0e1a45a0ec19dc45373da2387f8c6ea7a30b77783f08c0b2c25c17e468be4dcf48c92c7ab9ba21309c0832b79fb1d74bc2bcaeb3d69e5e4bf43b4683a2c245e53caf57ab4c31442a6e7e9f5e06899f3d7380785c24ca2777181e1f485c234e553620bcb7c8562dd80c2debeee95281062441425dcce8341895be6c3e8147e9868189697436e5717d511359771a1cefd50b3e2cb44f40582b8b113fdd7547090e955badf224d85ffa4b6c4599548308a3cd6857b476f400ae0cf0143276e73c46cfa01a40a422c6a8a5fc17f2f87a9117eef6cbccdfdd6c5e6d5b74b7d5533e7ec8508320ac31f2ab49f3da11a1a91da24d1451e7a8d3b2ad1bb353929d4463df87889ab1115dcfb4972c8e48d9923b1df5c7f9b7342eb385df99f3bd5f293ae26ee313e33aeeb4c40d6b2d4d8e68fcf7ca2e02d1a7452d618365e47565270c6be49063c40fc400715450ebb8ecdc2127086ad39814ee711bee6fc6ca0c5ad770ee4561ab31f468c7863bd5736c6f01b7944e4322b7852b9ac4a9ca67ece2ef9f825a229dac6c25f4361e25483996f0a60587fb5035c803140909c66a9a379fdecdaaccd0b7713d475d67b444affe1c68abd7a812405c504468fd0424b75e89492adfb2e208b065e6f4d23acbed22ee4e1f2d7f7e235a580a517cdaa011cd75bf72ffd13cb1929d2b9788ec7137f235f40d699d6ed295a8ba85359f768f8f9c96a7f74c91ce4a8d9f3c375576cff7d6efd6cb3f5bb8215b3b64b4cc124ec87d5fb8e7a07262e97ab6cc5ed30aac0ddacf587147cefc2e3cc1f7b51f18ddb8835bc5c40b98ed2968368db8be63c24c911211c37789a70cd7cf5e11739c98cc181c13c64f4ff82f50bca3c67ef9c048417a38187d248dc6428988b8dc0f9157899930be0724c2ebb7280387f6571b08754160a2ae85f1de9686b1bdb29b463de0d2a46f9b77cb1919a0d28593d0d8be18174ba72b5aad5feaaf17f9917d2d610fa52951cbf8330a8887a1480045b7d4daab72c375e4badc74ac85a30112a2225b05ac8c39026920a133765b47a5bc77f935737b3add79d4f984b1423b9a316b019bdb680b6e7b252bbe1112aebb9f1ce6bb531e6ba1a4c76cef63489d610f2266a2a784d5caef6c4c9f91f4f86c7543a35606ba814dd1c1f7d552787d433125c5415a62ed565f1cd32e9e13a9d1323c1c078ca2fc49551055e70d0447904ee4b0660cb55888ef3d71918e453bcf5fe0f588274030bc01334f0af42bb693ead6962c97c9f592f16bacb39c215eea064a114dad536b185b08bd26f4dcc69788a14d6b995dc98944d87011a35f35a4a629fcfe3512ccdac4f056ba27a661024e496ba1343bdc5aa69e8e37c8b29231fcc177ca1a372287415e04071091f72be7815ff16eafd64b73d7753f3906dd676907c75619e6ef68fe945eb15b0d06ec7c10711b5764f483403d90c580be44d9c3ff2619c0f12c158d86c914c7383c3b8c28906e20ad711fbc2ef383678df3d6f3fe93ec95d172be88eae694b82f59924a4661a43e42a2ea3472bcb38724a95a9c68aa04671dcc3da542aa2ff210f1d96a68206d0c2066c616b2e9c8eca2e8edd1b51e9231d7c3ede778b43db38f8a1706faa6cc56df7dd7cec80e87c1d21e955b906a63bdea1e0a548e0a1698aa2f23ab43a45b175cc9d637018e37bf702ff660d1a459d35a3a6dbc355ffaf669fe96ddb3c314d55cb6d592b045648402e10bf3ed97d4fb88d2bf801d1838a3c7d88a4ae6163b46739ac895e58af593a3ad5693f32c81eda18d8bf3fa7e6cf4bbb01ba8c2a68d492fd43c42479cb8b333500cdc592db6d546cddf554a38a680e893b8f4e54b85ea94b7f26c59f42954f7c76bf1ea75c2f8dc7847f612b7e727317c7e29027a26db0da7f8fadb0d304d0541a2e61513dc9511535cede814295c1d57496fa8b5c0447665460a84a6de3c818ee8209bb1873e73443e6d035410d68d9cd719e7d4b74f9a32073c29c6e8fc5a2c6911b1594f8bccf47690856d97763d2025d001a4ecae9fb9d6161dc34cfabab287f58c8b34b4f95027539c7bfec4e6de9f2ca83370be88760a9334bb0537e5361fcdde5fb1567624ed493779728436e5df6b93941a1efc2deeaac60ac607a6ff6e945356152f832dc8f19fbc5d2a1a6a93a9c1e077f7635ec32ddf6dcce92c924d12cf1b98bb10a5dbfafd4bd343c4950627d916293e771cdb538f312901f4c8277282ba940a89be02d10b26c20f07dc230e7619f03c052dfb933d642db6b6c8fb60008e528077270ec7d93fe0e42e6ec9de31839df84187da80670451cb25168c756a47d1fa84534e804c02e10352f5efd85062be6f5f41d849017c316de44ebfba9e73420c059f3051f8d419531a986b0056bb8646e4f3b4e76c397af41ba9fa38f76ec4a8472a2b6496ab0dec90dec15e2a45c0645fca04de7e9baf8f6bd429e6b40e93f292539d54e42770c1c37c4b43ef9355302a2e3aa293f42f25e4d76ada9636c2b6bedc3dfa5751351ceb1fbaa890053cc965ade1231c6a8a91ef1ae18b3467c072b8f2be79f05180144ed885df37da632b7a9f761575c9cdbd1bc60dd51b090fe34029f7f8e7e410536900f5b515d4279c1996f35b9ea4d344b221ad45e8702987571f264f775372223f0e7697cccf828fec7c7394f5e44b81760ba885fb7054e8f30b89f60d8dd9d1a72dac5b94ea3c5ae11bd2ab59e901df1684a4b34fcdc6231cecf78ca69e47bf92fe2d65921e5ab1bce9f95ee103ee160f70f33ed8d0e53ce1a3b5b497584397a1927091833bf415683b424d3b523a171277104e5693851e0be20d2d4c501d83d85c5453f7d1417659a8636d8dbc4debbbedc2c63fc17cf4b7753bef4dc66aa908df2f37bdc6601ba0591578e92fa2251777e4d9dcc9a0f2d62cff6722a9b1ad5df6f7f1ce2a88e84725508d7728637abea650f73b2ea6342611dd59db83b107238211f07a0183d05268182bedf30f8177e1e8181d15878a69a6fb6fe6f2a52bf5f149b0562f745bb798e8e254e3a3eef732b14a70ac569625ce0bf5cb7ce4985f556906bd27770c5157837a886c1943b68b5c90fa685b106e146ebe9e564ab030219d6eebdf787dacca781f99499e41346ecd02a7be5a4ddb0dc6711f3daa5d822b5dba530678e3c2ca2552e55635df1727d3d1c5bf18291b171a71b0bccb246d2112abc1df3197ea256613a3e1e7df72a7c4fe467f0ff1f17ea35256218dd370af378e42b38219d49528b76020602039e88d3503cffdf96f16cb8a3907a6ff75fdd0f098c433f93c7d2a3e784229b493d3826dc09af7660030327251138ddb50ec0065324a669e93f7d5621108326355e2b58b00e8b3ea8de9cd0f6bd15d44247385334a414102b4e35e3dee2e2ca6d700e5d7d75903e9bd1622ccb378b78eac32af0e184b122c17acfe2b8792baf196c679bcb069239964cc65b184d3c33b375cb4bd06dbc4addd60d5a274496a4ef38ba6c0ee67df5574e3ee619cdaa4894218269d2adc73b277939df448ac3a42f2ace08991eb4eb1c00028853b93b824455284e9176dc2080ba7f27bf7ea0644e2cc0a5f5cc7b8adcb2aab68d6803cefccff29ae1a91dd079966f0c4ba25c1db32081a5366e650ae335681369f15db0f12419c92e7a05e9eb3ac1d85f3d6b34d6f6417b285770d67f9ecd5af9600e030ca76ed458cad29c3147eb6bc4450ac484a353604263d6a3c3f01dbd42975b650ea312221673b7ced3eae1ec9169795839f657fbeaec9fc883b11f82609e82dcffc3267a90770759bbb5c2f04e73d76e8f2f0c80a1401ba61af763d14d3b182d39ec54449b7e71dcd53fb18aa2b4e7cfdbc838623ef5d2ce2337ba51645494d03e2b968e43c9413798d36b1835347f5ef966b9997919eaf8b7d0645f405bef229f90668f6b2aba78115528a123fdc8a89b8ecf6b5dcddaeb402eaafead756f501fb23876c69b4707083b5a83b8305133b7eee745788485bc68e206924578fe93d9d210d6e4264d4e2345ab5843cb787aa69473760e13fea3e7deeb28df68b51069e58c94bd028f8ff9d69236aee3c40c15362f737933d1e0309b5c01a1f14bea7865029c15fb64045f8f9b954d534e5b43a0d0fb32913913332bbc9eb4c86dff7ffa2a6ce9a6f4401ea64c59cb4e65a62b5d0d57a1e5e610babd439a5e00a443e6a207852b85ba4bb6e767735ef18d9aa5a542ee03c7afe16915a16c50bb1b20f2d87a758abab91ee5de2a9a28d7c3e6cc7a69d6233a99df20dd19e7d6ecb32cf519b50e82ccb0f402e871f98a6f0e4d90ef272ff15ef12f052685f581ea87786777ce634efa7924df45e7d2d6ce75edcf6b75b32d1770cfa4417e27744658cc73a828ea910f5e0c64c6b5d11f385377a58d6ecfe63e284e385054169388f84a45e1ec39711412fae2f1088b93a24d4ca5719125413c7c203c50402077b456eb8fc06a213cb57b2e5b0fcfa8191a53b60d829b24956968ba35d35717b7e960c628f6f5ab4a4b6f4812928a191f4f1794af1de6f9d5912da6a963ae8453857faccd7d9b39917ccbf04abca8c00d593f6772f14b0af3620241eee9fd2c1ff597a8c9b394b0a3747936f54e0f12e6ef1af234b04d86d7da6feff0030c95a745e5b39e02c86cf5952325a083c09a2d89857bbe3f7c081de473da36d469f87129cccab1626bf8fd1f0373849ddae4269a33401ff5e255084f8d42f71732fdfcf6d4374b2b2079fb11cd1769e52c2641013823a629b687f13fe0ea39c6d7243ff8a4aa1c3ed2473b80ae7e707ace755660e55bc6db53f4e766e774cad7c3a2145a4f9b9f1b5c1f2113c415e045f3cfeb96a8ae9000636e438fcc3c9fbe033dfb7ffa15ed357ec4f3101870bfe3267d2cfbdbb34c91e2b5a9baf4503aae31e2e46376487fadc6713034a92143d3ba5029e603f83204ef64fb6c18c136440b0c966af6a03ca7e66265c44747fb2b1109b156ba501e6b77af928c36014c8771f21107ef013edfdcf5f0b634e84e84cfcc04f1ebf089ac1babda01b8a4cb96f03e222cbf510cca0f55ee04d0ab6339271777b4db6689bae9a0c57ef9f737d55526eab7bc0cd175cdb246301b9a652c6c1e48e619a17908daf5e09518d2556caa676a8660ea75212cbb6993c9cd2751caa19483e74fbbdbd2dd7c373af30721a3dbaddf7859f2dfb8ebfcfbec3f4bfb619faea07cbcba8c50ea0b8981f6f27547e066fbe85401aa6516d124a0e77c51eaa98f7b116b52716357d8c78234690de2b2c8ab86d403ace4bf803478b10aa55d779eba9f93fa239000b8ef5951dc812f79ff63982b78f593cc0757bacd65de45d825b6d5197fa7a9138ee355cba4c6cc81b66a3a1aa8acda21e6e35eef13cef0ac7529783563a4fcb0370472c09396640b8bf2d83f40cc6c780fc2298239a69994c351bc879d44dde6a5452cd0aab8c356b7a15f12d03f9ec1fdf93f83f6ad5ee0e926fb57bebc938a0aa2afa26d99623fc8601085f6f4e9ce88e0ad491e4ca856e0872d1f8c6e61b858db2afab857eb24fd3e2979052fdd774d474002264da4f6b18835f21823d77039efa12b15336ab10a39d8705b2be6fcffcf49a3b20f51b5f045b4cc9e0cfa5d228aac81e1b51622facef4f3bada565e0812eec33c8228a054cde9905d744a306e0f1b4bba12aac269d30cab3dbc7d4b07c4a37856f7e566e4fe3de5750a2dc93e74e2386efde7b011e65c2c4f8ee9c765da5306b4481a72cb5db9c0396894e96c9903f3e6c4a33f7c14a6b6fbb5993139509cebf7f63da9f21032c0ef7ab26e09123a0ed566ff411970524647bd4ada2492040e1af3a702c75c14e41231d7cba0709fa8d7903c2c145a67cb4a4a6639b73ac605177f2a153162ced8b453dd986b4340b46091a94646ebd1536e563fbee23a3581f82c1c2f1a8366660ee6072c98ae47c8c399df93937fc585da2d878fc4fe8fdcc757f9c99359a82d1cc8699828fbfe892b9497a4705b531b8e3612d7994f981b6f7a953be04e6e8f67c75f3df98c41a8ec2054b76575fda396a74a2c69c31070b5d90fc90e75050885a7ced48245cb9ce0b00b71d19f8797ea86551ae809a41ba4bec48c3d870ec1d798dc2e7097e0707e6ebc88c8699084a8398c4a009a8bd6abb11319d78b1273bc6f784aba7bf0bbc3739fbabceed4024a7e151c7469ffc92bceeed96999b8e84ab9d1f3a83ed1ca410be43d16c0ba4e0a915727dec1f15e1a30da48a2a73a1ef9af2504408ba78491f5e7444c1c25ab115a2288a5aac9bbd5cafc6a11641a9befb487900fe709797a151e77ce4a96e579e8895373d9730d1aecf408269ab1a1e5a580a1d8b6b96a45e4a2cfe2fbee75b7d83bc9c7a97116d7e0e34bde160c0c5a3f86a83bf4c5a8b5ac7eedb70fa4ff05f4c8c21579e611b8cec097afca0e7a8b99772a4e2748b440f337c199059ad1b5cd7ce1ef8ef83acec7eb9cdbb183f4647b44bbd0ea48221d7912b67db060b3b109d3af021c15ca80eb071ae7f31eb68a2db90a6bd2eb1ffc000f01b3241ff378ae4051162180c47e939aec4f3aecbd73d9c99834f79e39b97c438e1e294d36ce9b3e29ebbcbe18dff7d4b88426cc303b4947f82e06101650681c2ba1138937667bdb3f0988417438b00e5ada5f3e3d262c1bf245ae2ef3a1ecf70522a4c57b32f42de41436f77661d033db3abbe6a497dd8a282e85bf9b90c49024f4d87a2aad3c71a6e5217aa8b45d9117891279708e8d8cfbf4ef21e2f76cd36364d162135fed780e0c51ba914563f2fd91eade53627cc8cd518e82bb0ff047017e7a165c108d05fc3c9a7e72102ab3ad72281887cf83026737b92012ea288d3ddfb546faee55c4834dfc2aa5e1593ede0f55cd1321cf71d0ada34717c7b00bd60994fb7e62e02254699e7e8ba791a09ce74859e0ac70bf8ea2f26c6099de34b9a22f01d13c9037aac7933a80944d19893d27363e2d0482d1743fba75fd4f6688885aec15eb4143b6d04308e868797d73dd9ae5fb7851dbbfb2efbe1eadd15746df64235b39c7b8fcf33ba60eaa29ce40e426f159b36fca16af83a5f9864e1c6397c3a7b813bd5de0f16936d3950b1051a1a3d1636dbb48d3f3e7db79f517c64b97fbc39ef91f279ceb5cca3968be9cdf27163e1d80b2b48ee582a6ec58b743f25a5df55115b99ec04d6e7f91b3f83dbe288f15f8e60b33d249d458fb1cac4a82207138bc2749c99bd7705192495a2f40f493dbc629bec226e0c2f41f85222db75750de1741393d735211a86fe12bcb7838cb75bb4bcf845eb0b26930c24310f63f9dba4ac8222561302ba43963778aa75ed942cec1ad458a0e3e97ed2888e8e658c9314f0f8e9de1efb60d5aa83861a9695eff84f0cb5f7ce05f6c7cdcba5becd70c70fef146fb841fea113e9a50fe62b1d2221510ffc8d9f9b8c0c876d8caaddc6856d42670085ac1cd459c99a50287ab5de54817c409b417dcdf28f3e40c1785adae9479e1328de46b148d0e0ad532b119264909e03969fec085657093cbe03f8d2420c06c4dad051ffc33bb33916cf857f5f1f514f815dabaaffdc720d1e7d50c292f2385d6c26dddc8f71b8d6e85f3cb319426525e414429a9aaf95237ce22598fff1715e2cff450e465c8e4cefed662d7ec34e0a36e815c7e792ce6afce303cf7f4c6ca1005c685310b2d0b8ac2291fa68796e4e58f6e0ac6bdad103286250e019e40c9f30dc6ead5a5da130a46ceea2b1965a7e77e52ef87183c7d859331a676756d6361cfbf6ade46281c27baf5e6c3bab8536a3dc9c65bc8ed526ffcc02be25e17f6c22e3b510935d12f2c1d423dbfc255c5e9666fc85668080eb9609f9aa8569ff6162fa73840703236efa2bb77297f6c921c9f28f4ceb1c6b8e07a43da2ce67f0e3d7ac2c1781b047e03eb2860329cf0fc49d9eedc4cf1694b1ec24f21d6e5d7076853de05c566a1f7f98c59cbe71507e1cae696441345abb070739f800f9bffff7c69e09364a8c7949963596a52128d96c0bbd107b112d2fa6c49cc2698c5aba13bbd7659c796f1b905f862302f1e8b325a86a002ad1cc2b2ab4df81391f19432a0505560ea76ab33f8bad538a867690d1d5741792b70e20f43c7432d06b3b3731093c40ff1380d0b7816cdec97c2a5c37f0967965cec6948e42e2cec78047fd9d85363cc27f6de7a3b974c623f792528234765a278a4b6c76e9e504f4f53fb085eaef88448fcdf9e54fd4cb7403814f909a39d98c3ff0ba1eaae7d63f9c7d950af94a5b5a4c1a5095484936e3ea78dab791c48a168edc54557ba213cc4ecfe38a0a328bb395e25c4ef58168b2509bcb7722b464211d0abed20e1ef4cc61b2300927c20c5fcc9fbcf2283c7e88926ad4b31741123f88d5d9a8a93670abb21a80be4d21ac0984bb402c4aa76e47ccb6f31fcda0eaae2ee2285b600c9c8d30f550445aaf3054a04a92ae923e332099e6116b147ee97402a8e0ca77a90a12136b5b5893f1b36966a15803f86fbdfa8b359350f3711cf2e01199e5d55d3986e7b888e14d61a3dfa24c91cc84c065e02299eaa24ab8120334b6c5a44bda2069e016fc8ab8ce611ad17fd5708d5ec8e48ec51c8c6f5fb8c9d96c79258928a67448562d9aa8fef359d8fcb12f0d741cddfea9e72aed2b667279909cb960b600d7c95f644f03e6834d003357468d9eed2d94e2345649b147913e3435aa1b8fa8dafbf5d6e64c65c8aef5c04e4a63ff651a66fa8c7aa867bbf88f0b3d741d7b3a993153cfe6385feb08c2602abeb1dc45b42bf90d0dd627748a66e8ceddcbfae5adbafa6a0e4e4f3d345aed97147d48ad63ec2c3d34c9f5072ea163d4031438117387cddb07e9489d265b4599c4f0167b8ec27ad06ceed11b98357d00717cca8175811b29e33b784b672be3b07c4b916b0803aefb8fee08a924e5e49ef90d6b4639d37e419e2696d9250eaf920f9cec05e53fef1037aadfdc5124d662df5aca12317d9d813e10b6b112d76094726d55947e58eeb7d640777bcc7479b26a21714c8e7b801e36f9cb4714f7a272528efe75cd2957c96d0b111ce4c19bde7200c806e7aef96175c3283614e0c8b3f73a73a051e23fd60ae082791c6eaf934aa2052698516a82676577986ff8df7c857dff67cf6d385d32dde0939a20a98fb067105eb156f0b01049bc5aa82d28507ee1b4abcb59a481a2a10844597d37e6d555e3204d44066af2295fabc5c8d2eba9965146d388a3141d9e859a4c78450cc5101cb5faceaaddd5fade4ab239050405f37d92b1515414ea288d896a7ed225098d42c15e89d213c8c09bfd2c5d4016b1db07cfdf403f89020c7b64cbc4d4ac0eced101fe6c27927a3b5013d4a0cb59978f5ec44ddee37d19b9048b062d06f822187ac61d75ef937c8e0a724367a5f7f0256bd274c0554566efb38c093a2b3a9c09d74e79652fce6ac7108ba764c5c5132ecf177b57af11db5e72f68540f7f95125f7fceb0047b2fdfbd4ce5dad5e121147641141cadf56d85db1bf87a13c5cfe818ba2539dd2712e648b3cb15db2b6b4f3b79b4f9df13eef01c72991ff801f460c0207fe40fde223aa88bf1d9d9aba19a6b6220e5a6acf6190b44b546cfd308b23625047d1a557775d101dfeea75d40262ff09b072473831a6ab1de07c5dae084e267d061933343dcbcf5b4975dd7096266bed9a2ecd4e388b1a29dff8479c6fc8aed7631f1fb4831ec681b279463ccda6ed84eda0e485bb1a96b5a70d2d390511aff3186dac886ddfb9dbaf811c4da1c8e71d7c0c96a99d303711b04f32326f03c85b24d878b725ee0b0c9da6445adbf60438e64d94d3b3fe3a107449991ca71a22613a2a35fe72a5dc1ba48b1f928601e7119744fd54e53df2fd20bde42b85fa908a54ba0fad2b6abc6b772897dbb44af765988d35021b409b919f1f2bbf3a634405e295fbeabe133a3328f7f960a9a336b34c1281ad3ee3afc576f925469478d8f4c565098916f1d9b5bdaddd889b47aa056393a577cef8c993f3566e4ac7932699e170eeebd18bb9a9bb90ecc261b89a08535d7083df17aa5eba849eef3b78092b30b78937c5c5c9cde5bc053461409015291803fd37754af8b9c4937d83fadf5e991b6114c66a36a3e0d79d4a1ef04876e833c0cd668cab6a91312fc4b2b64cd2cd0dbf8e68e73544fe906be07bb16b900f4efa8b80ee3c97a5228de40bb0ba416c640350a1024dcb99dacc0343865b2fc9738010bb1eb3b46c7c3b32650342bafc5dbab16c7cc7b4534390b306dc604af41453d40a3a7a6fa154526cbc3af785423d3c35fe36ebf12d82795861a53d6973aea0614f8fd620d0f6774f374a4b3357295c5dde7b7a14635f67e3ba76eee3398b3c5d30c74508998cfe357d4873b0a3a109e374525d8adba0469cec276e2c76a9a3dc98ff9fdd3117ce75a2221b1df97b6a1cc68cdc9698ac5279d05a04f195e0fd32f6b659b681a6a4f1df5a5ae8630a1f6aada8a0598ab9aa6e8e86fc079d8e53c828467696d4254ec5af758397352fd41bec834c5086fafd6e9f7ab7bf86a2bc0305a79c39452acebc6e57a306e66777b4df88b4610a3109c05a86d84bc62414ab33a84c646c89e926e52d142c1bfec775bd604bca6dc67a00312a35ebf1ed15e62cc14177e85473c600f4beee79c1bab907061b74d20575c3aee6a930091ac354531afdded70c51b74fecd61a27c9566ed50e994aea59b4fbaae63a33fc096546693b88cb54993bd769c11fda499b6500ea0cd9b7d124dca6b7176be1ea790bb621f2cb2c50bb518a2e2eaf3a95407f56b745410df3cb172f16f8b8a1473e1db16921931c601a5753830652651d99eaf0dad5041be3e9092336c2054abb87f1a91a40c83fb44844003ed0ed8980982fb91a0c8193a14499250f2db7c471a997938be6811b20f29b21e874c2b58d6a976d637dc2feabb09759829ffc0ed3e332a58e152c91eaac8a7951c51ccf8e7125e78b4bb7d6277814d6d99295c395d3e728bddeb59095cba900870a93e6bfd92774bc7330dd852aadc97c640fd3d83dbc2df26435ae18ee44841ec82cba03a4a7745aae5ef53e59bdf1686b4527982039a9ed64dc01fe66ed75e74cf41fddc2b8fffdcac143f03ed066c9404b700dfbc249eb4961c70ce315c2a377ee9a97fe76d09e2332c91f3c17ac1d979b2de75b12f49ae9a3ecb9f4c63dc18e8f44259179f8ed6eef2f6f0129de2a41d43fb0e781de5ad011bdd8481c05c67df904e3340f6650f6e3949e97ee16868cd7748bdfa984b3089112c44599175b74e6d1e41c9e947f2671f9c3f04a094abb1ef550d9ac4e50c153d2796afd83d4bcc18d8174355066132d8e45874b340e48f52e0c32f67b2b92cb1e228b38e4a97cd2f7b55355d1be98ceb23aff4b5ca77affb896835d63f38a5ad1a9583644e49502c86f4d4590fc7143dbb7f3a89432f67b535128fdd897921c5a18931d09ed8b663b0384f08ffc240ecfe8d16add9cc63fc90846d80a8863ca388a94f831b13ec54ff43a0d9bec1cc931f3f368d018a1911b1ddb1052496aca203d5b5fd02f3fccba6c31129c46de9ac87305a5f529cff6ae2ce2636d4fe3af7b9d50b68111840dacaab27ced67ea9edf01ad26c67b77a9fd1bb5ef83417dd2e7b509d068e53db32cd1d50539cf18645456efb6ce9d9bc1e6001daafd044316d405e9ac3cbc80200a09cd5338273616355035edc598e2be90f4ef59cf4cf255ea80a5ba08df9fb73a8b7b17fde8eb279a5ef51929db7910074ecdb06c59f3e98a28f97e9ee4e12c90202a464fc5c963fb780125e21d3ee9d03a17749ff8b27853998904bbe93970646fd15d3a9394b43edefe2f5a747e5f9c55e2f590cfcebb03bcbff13cf69e935dda353f89483c979a6b1d0891a2b5f7135937046f86cfc0ba26c78e30fc6e111ade1798400b0a16070e3ef3c5a65779cb8cecab9bf797c90d6056ad58b293c90a7157f61ef9c61042b10b1a20836094c96bcfdf58d11a1df8dffebb738911c00a9250427b28ce3eaa1d0ac7702d01288b989d1ce963d77cff18125660f6b0dbdee0e1e0ffcf7685e55eea2f404f1eeca84ee5733b833486dbac6b9094dda6f3cd1a6c77160eec709dd46d5debadbf7fa4e3c9e1e3dba538b9523df5f40554b5d808901dd207235ad3c16f90898a7c916422a2ae73f108fc8b1f3958bbeedee8b2e6557200ff629c9318e791c9b4349a5e506e718427fa9311d5af87652ce3c43d25b4338e0e03c71653d0cf81f8afb482021de5a805c9bc870249987a770af2aa36e743034523d4705b6ea9e4cc72748cad459cc519529996eb87b830a3fa57cab554c356ff48d430162d8390c0d67b9ad507c0c247266e20139d7ff939b7dba7c105c042c8e5e3c7ef3cf565ac5032c483147069a54e7344c18dd0adb7312717bec785ceac2c25fc33841591de1e46476db821b3bf57310fa6241743e1dd264280fc7f49a7c13f91bc225817689fabf0c29bd92b43fb9e3b6303c3560bf18b1fb952d03318c46744ab8ff5282a0cc755413f99d9adb19bde821827c5a8988348fc794a0c4dd0da05791bdf78d8e10a18b266a938701fca115e950a7c57b450d697d9935cac0e3f254ceadde75bce2ace1eacc9af786bc4db045bd8290b9d63741311cb133a7a9a30743da43493a0ef4dbdf03590170aa2704d554b9d40977684be47bc5f44d163b6f8ae8fab61a5aeda8f5db46a388174095cb1a0c16e397a42d396d5b89188c976f37f9c9ece50ae14240a28d074e06e2c8c389c935d556404f4333d2086bc47d2e1b710f23a3474ffe3132c8f992bccc7aa898daac66f19334e34ebfb59fa776938dfd3ac7b50e0ae6e5950c550c5ebe71464207caa4781d93e377483b01cfb49f9b67bbf3b9c48cc147720302eae0e8fc894d036298690b2e227f6e68804df535f3da971ab8b407b0188cb70dbf0578cd386185eb77c1919351331b9a3baa27d72d55c61efdcdae479b7ffec5db7ee0d7f3eb903ddc8c1dec2c09c61379aa9c465dfee60ba71a3c1c9410a5304d953ee1ef9bb9bb829e01f418ad99c9a9b098efb2e4331f6e080810abe4e54e660b771195b99e35973bd7e8558dd78e74164887cde46f3087d743ef98bf08fdbcd11eb6e8ed5a56dcbf4351ecbc86d6e5e8ccc26ee0e8bce9e094df2758d0402368cc65ae290d45adcec42ce4e3804bd4c2f3ecd02791d7435bcea6c9b037d3f524876767fb5d49135c8cf633c5c0d77c867aa7c4f5d7e49f0b3e9b037c86f07522db38c6b9b51315aed7792d49285959114f0b5a9924a064e6da3538e438b0c924b56bf199233cb876913c166529bc7bb0616dd7111bb4bcc3615e4743dc7cf5447479d3d07bb5252f657cb5d5ac662e5dac774375802a2ad6631e37b4c9eb8548c14deb71a1f65229dfc8fe0af6e329ffbe651b78a05c9676b7ee4992dcfb2ace771f6a268cbcfd301a390001db5c86f50fe57c5bdb131e4efec1835e12ed8c7db7c9c88c12ab01d12f178dd584ca0890483ea609eeeb8bc2699b6b7ac773e4d747b9cc5828603572e35ea09507d61b5eb6e7f1b6fd9f573822ba363aa6b7a7a04d5b3c81d4984dac62cdce86af6806e65148d94acbadc726f551a0fe52c079fc86693994cbc54d2af7258a5a348efbe3532d3eb4af594b2521851c1b594060199b1abb46dacdb075273b5e5e5a50d21f54ee31c1df0f72c9dbcaec0fb1a56c3379d92fa85d6e50c0b64d228c56e31413edeb0b3dda18bcb1cd0d150066f398fe9c8de685b6976ffb3ba71fe826d52a41241df66ce3c0321cdbddf55cc8f15abcfc252720d80686d4c0fad882fa5cd110eb737a42ef58831f08e2f00ce04f1ff9778e7d793250ef1d330e367a8a8c2374815918237d65a29e457c9ba78012ea7e513058597786f8b141f92c3a406d5e204323d73db01a8ea4b8723442585770b68660cf6298e49b5a15cb35b91de3c42f1e62badbfbfa9a0ff8d84e333d4d5b97b958546d27b30f744531f188e1d6098168059842071678bf9d82701e3b3d652eeb89e6b6b84864deaeed1f7a4b27d73a98e357aa8a916491c15417b44ee07f01bb5917f226e712ffc0c7bbd46f3bc1208cf8dd1da6150df7f4cd4010ee93e56e11af8235ecc173099837925e530713aeddd233c05067d0ea14c55cd2fc44a6092ace23c49be15ea6f826b5b0f3de8b128198cd29eb021a438c672ffcaa966435d7154855926deeb56b62a9df203a08e44be108d0570db2a7292faa7367de07b5dad3704b234626695084328a808dbadd2d3956288afe0b9b1479779dc4abfafb814eb672a2993814ecdf9c3f5e91fd9abf1d1c86de475be79930eaeb1059f292e60b7bc7da202402e1f3798a42b8ac22b95017f39bebcb5ff04177abd107d6807ab0e09373feb9f38f1126a980a553f3c9dc9b27de62f8212cf6669919fa26934b9895e235f75fcf32c0e7c65dbee38d04a50ea4b1efbe039f0e3239b8e14393c3dfda2bc9ec7256a92efe644fe0f89381e1191b4fd82ceffb0387ab9a68fa9d65528ddd907b5c5d6ce1b587b5e2f047daab170d96fbe28b0c66cceebbe79ab73f97c4c77095e75ca6d5928a9eb717b7dd35ea8d178febf1f05bc181c240a2b6d4ea2b3deb691cbeb7729f25edf6156e0ddf00060cc9ca75f648d162bd91a724cabd9ef023b68b0a3409f28930960659adbe17d5b8b8f31a7a1704bee998146f7ba9623875e083e0c54b4d30797f038f65583b4d4c6d88f8dcca798dde5468787983806dc93ec9106780a2210d936e01bc01d740d37a454c9ce8bc132efc3d81088c7a31c8f7fa585b5a7451ac98609659ceeeabe131f726cfc824cd9020652ffea5a5fdfc7ee775af8f5f1e615ae0eb26baffeb9006b0f6861b82074f5067b3ee54069ef6e97e43d9e8a4b73741c2d09882f6a14e2e82dcb2c3bc835bb3b0eb0bdb81b9c14daeb3857b6c3da92650fc0f333623967d163c21a22f09d4e513a42d2d26a39b7fb4d249e58808ae29f8379323f2459410ca927bcc14c1626b9b83d714c7eaebf1b77c5ce046a58172f8f07320c2edd127f62530196791eaa11b3fa17342d86bb2b4cbf60533538eb9bc2d3bc47a9754997b79b0d934bd6124260fa0d2327a72fd61581d77bd84e16555eaa831a7d34d501ba3c6bda88be916426c9f174859dc43fc823d7735d2231298d98836ed69c5e6e5fa99cf21de06dbf71e5cd1fb244dc2e225fb6fba41a8dba81124bdd96582e032724100f70ca7457f9f71526b839efd55eb6236f2497b9737e4976d09eb2a2a64663f7edd08dbbb6d50f61c286a5c25b516ad0eb17455bc74a372c2de1ac4b5dc645eb459507beb7f8a2e9a71e9213047545fd3fdb5e17f2a25acd911ed26e3cf5c24e74d14fcaafd0dd6c13ca03318c994eaca27299154fd6870c9e301ec96d233f9bb71f033a7f87132460cc42f707cbb09df5c9f09781b724987db139ea53afe8304cf682901147b13a586a306bb37e314fb7f36afd9512efae7776815abbf384a9eb99ee6f3c437efaa4d08c338291fcc9d87066d94bf8806c739f2b7828a604bb4a293fb34fa6cc9bacf562197155fb672a4477b484571d28219537c387ec379b9fb97de0c5b9cf1d4e682cc8e1984ed88cf5f69cae7743a7e966ad86f222cf52e35c2fa59dceb8c8b3cb34d39174c7803ee2f28c70761e5cc3d50ae71b04ff4980dc9210d224138371fb70bb2590a1fe79170176b81dea2a27c1a9c2eebe2f82ffe1f57e9b3122be79922c38e1de5f278152dbabfb592d68433a38ff2142a0e9747fd14c9a1a7b077567a58b97d4ba1d7c02135b96cbd9d0e178cb256f2ec78d074897da4ce715ace8168605103b9d7f5da5769cc3c550cf02f7c5d7a11a0ba6c05e9d2edf7296f1019ca8a1f2b394ef6cd450a909c555a1e0368c43aaf1398eaaff6883d41c4b7d635ab34d934f06c9d8775276b2d7b2bf74fe0eb17216cf7c1171e9b91e72b6f782a704c335180700fdd91b8947cd26bdbeef14322a60396daa98cf52f4ca2dad6c6e1b3491be396482c00bb1e41bccc420f6063f6b3f57773ef385595595e438e2bbaa4ca2a3d5938b60fc0dd0fca0e651280ba199dc87b91cab26794623168bbb6bcdfad58353d6611157c653fc2401205db9cd9493e35ebe0550e6c28b80c2369a4a37bcf514442bdb266ebbff4b03f7951d4696f4151d28790f96baf0e22e01ce887565f34e4a2a02a4e31ce87e25725e027cebd2d9765a97c40371aa5d6461f7f34cf92067c83d9179fa383cda7cac77e16b2737d3789048a9776def24591fd7b8bea5db48aa5d765f089b2799abfc32669bca6fbe4315ecbe36785c82ae4e25037c56adbe96a1107ced440bc10b4f6a05f1247402f53e741ef796d64ef9856917fd9ad724a9373cb981d8a276924d7577b7a7fc26a0d3713edf7107ffea82c63e7e614f8e975990d5a0b5e1f965c2f174b69495a2cd267bdfe4a822b0ce10d33db0f407967185ba8841d6facfd32dc740ffd391ee6a141210d83fae4f95235c90dd61286be9b5ef4e07dca3d6e02799c765fe5358ba9f793b4d56e373958c0b938ad0c333a1d390c1394ffb705f240d8681092524694cbaf6f96c7d2bbbe44a59f39b1dd27602f03e144f4b69f470d5a7a0b687edeb16854b4db2482b7a3eb4d62e957195b2b125d5b5f184bf553078f87239431e467ff004ac0a8de34198c5463dbf461f8d5e7f326f22a676336e624cf8635e7fdc555abaf4961c44dddaaea8633a27e6bfe2da546011122524e9f17f5d3db65fbad4f07d7e4c6027c6895f969dceade2f3359bc8f8545d7acf79f7dae2cab1dc6be3369234278a885e282caa2b367a1dcb8df43bd982d9626c8f26b57ffdfd7b845bc88f4209fbd3a38ac0040de7dbcaecf2ccf9a8271145ecd4f7c97df92474922b56682556e3dd741f9adeef7de302813a1783df5f9d4f9ecbeff71c45b15f77d2966be9070e776eefdaa4f7f71911cdcbb324319192100e18875d21e3bb8bca13716c4a4bda45b1033f2ec9655c55eba5dfc2591c77f58706c83eded8d11397fe8ea6111e076477ca96583a7115cb9cd12bd6683aade9746619a73f56b003c210787de1ff868b99ddb48f356921548a3beefb2c2dbea6e07a71fef942164aa2791e8f015f8585726bfe1dfcbe51f56dfa436ef97d628bfc7babd58b31f4d83abe1dfd716db1e60c2231960a8845b6ad23caadf6789d19be7fe59f86a756f022f28805045adfc7c2cec9026b8c0fdfe21efa9e71c1d24044ee40fc99e08ac314402e167902c9c1c48f82f79a5bb517656525f692da999a5d7b95315e882729d8f35736efb6128d58626bb129e0e5a4f8afa7917d60cc461508bff9d493fd14cc542b46cd748ed0f7c32f0bd615a027e011b2829c91363a44e553fa55aa6f38ac05721bb590a892ac1268a7d0bd197cfbf12e96a02f3d87d2f01c6f1917f7b61cb01715314a661530688c4a4c7db26fe7da4e04964ac28d49657774317c2864509d2c4f1cd019c9afe409344cb4a10bc8e0a3befccdf8a7c0478a78be743777500f742cd9c87a44f1357bca6365a445dd89d56b7bd21933bb78e9f20a33345214814a804a0a4257b4a09f9a8575f42b71d2514317b3c87836b03241fbd3a0d84f1d772ee550c2dce42210ac21edd61d5601130110cb765846344fa61925fe47e3da3ec9d260b3a385c353db206a2ca1ada7fa1a564c27cc778e1a0b6430abb5e33d520f20d014f131b4dae3d010574a8a7fd487c4e58584071f6b4600252b3bd4ac7ab5fc9d046c80b984314d2ca9bfd4a3d2bc81d9c1c79995b40501f7a4764f727cdee1d2bbdc6a8582c85f79b01794dfd8b24596e0c2df9038911ea9328495af767d1dc26198a179f20bb067c23a38497e0740db880b63e6fc96ed87d2d77bbad28c7434c11c468df870a7121e76714a284575dcf436d11b9f14146d842d8b332de7d620d2ff4e89cbdef8d6bf9ac5550ee0e0f8637fd13f780bacc08ef3d7b1f298c9e31b8c72465004f100baa28bc917cec635102920fc4554115ec100c48aa418ef8b3666ec61c1e736e9ba43ffbc3dd986ce3ef929074917ceebfae2a55b62ee7eaa47297b4c0f3dcb57ea6883b9e6744aeb2363fa5f8989c95e8c0e0151f4fa150dcaf7762fbe11a42575b1a402a45cf37cf77a90f84c3a0bbe8f98f27e236afe05400e772fb1856f32011efe3ce7c0c1de90ab2cbfe713442fe76a1db905e1fb7e0c00959529255443581c788529dabb5922874093aa001452c5bcbd27669e81128e4d5d5112222d62937549b4dfa9b4feb451a15750db561886702ae22b2afa17f65ef29ccf6b22e9022069f0605c61511a96843430c9aede38a50e973e64a64fcdcb2ec53c2626a145db652354cb829f9407b8c5af752b53904fa180df9fb1b3e7c970919c28b212c4ebb60b6cde7119e4f4ea7ba16763b90863f6d20616f04192ebb69f6a1d4510e8afb23898aba9f52ae8613b8a90c6e220dca2148d486902a38f795b4344b45d17e35d99cf7d832278fedde5a80a606e318af3c6e5e6bca24e2ab750752b0d64b9fff34f6eda7fe8b8bc5b1628f0e265597a4cafbd7b496a88dd9ae9ed7e6cd88e2e6c283bf3977ba129a8864a4e38973618b9cd86e7decd79aeb1792caca71c4d94dfc0e267b6ae65579667d0cbdef7e495e8b7af5fb4497d0775aded02f20e09dfbf7846c65740355764e88a599dd47e9a29532a9b0fc3ae119943271ae51ba849b05337140e5fe49551c06abe182c18a6902a9da7d9d1a843735174a939ca8af244a218496068eff7eb8401e3bc5bb3f9c13842538edb1ed95b2742de82c8008cc71ea6dbd893d34e8c8e9efb315ef3213844d89f2890cc595cb5f530e90cf17105b5736eaa336c1b8944832026db814f2e77b24b3d77a191ea668bff9aa2646f77f8af6a09d8be13360c5001dd58558b7b734d3a34876dae664421301f788617179a46f77a6737043a6c1f6d20c613df0128a4249e7817c535def1360c12c08fb1a7521c7e5b3010a81d4fd57bc14c84bfd739a03e9f903ddc1f56c74f6f0b181d24268b63bd013d093273a53073446eb478cdcbbe9caaf163b1477147050defe27463f32bd48570bac44a90a091d553d89d292051338cac011bd4cfee10378c814f9744d4e4fefed7edd980a63c387bd54b4f4faf1d63dcfdf8af11edaad173df827c6971bf5a91f4990d10b60c85ff9fb6780d2ec3a89336f05b01cb6fb0dfdf89b7b0bb513269c7d313531e559553df8d16fa5334ac83ce7257420e37121bf68b5b448b802ab8919b2f400181edee16ad3aecb2fc4e1dc2ec89b8722b640eac4c7fd3823b99e4229e387fe36de5f98f67fe9a578a32ffb89fbb627310244f0c441d9e610382a62c2c9c1058eb4ad1405487bcff73f66a5aad4aaeb00161890005b89a759fbf0072579988847928f3cbce4c5b9628b4eeb6f7c6d71654208ad1023cf5eb50532f574218336b3b62f8b271402633c2016a0fc3e71c9b7b717cdd3bae903c1b0fc5d95d530a78d853e67865df519bed9f0bc476ed50d1be7ba8f8a52dfc84dfb884bce7ec6ebc93cbb3ef6d5c0794ca83b4efecb17e14f0113f3e0a3b577dede0851c409247d5865f61562fd8243f4134d23173eaac3a48a436d05ad7542d2ab2b0bf6f1b5c047654ca38dba2c2c4f915281cf8b71b80ff1b39f14151061645c78571b835cd6de645f20623707a20207ffe42ecb13d01f33cc6f5a6cfb66c21cc12384668d9f5b5588a548e32e0b1ead9234e637ce18b659012ddda22ff96c05eb6198ec51443461e678037cafeca4563f684c7d39ac77f0e73e5c5c73b844937b2aa9936adc54adf57cbeb36b203a15aa6866706a670ce378cf74a815182046fe8557cee3b901e89f61a3697e4a2ab91e8d4d7332ec53309c4c4d88bb3f2ffc5a86f64fe47760793b31daf99340a2b14750e5089314d180c7f7c343adb6e3fb173669b168d9f40f0ebce5cc28c4c04a2d63529de55bfff965a3ba3abc041414c4d016f31a67ccf10aad24af9d812a69ef479e70665b08ed8040561c7e63254de6d69c2065caf59a64ed6f3201570835c4fdd764862f35f503077c80eb9a8fecfb8b310710eba3224f3c9fba0e72256ab1c436a6d4939a75ba435d922161fc337695fe66ac9643a0c8a910d274002f01719397809be00fc3729e89268cc05e9c3a5b964e8a63e64367584d6d0af7004b5c556f78354af9bba538524bf3eb898caf7266e115dde624b3ca9de157458649e55785e31dade7642471aff4d339e77e0ebca8a4fdf1e781280a2edc0b5de3158b77d9dfc465e0ea24cf7436db46e08b0c8f52c37d47601bc8a6df18f65bde3e7e1ed2a886053ce90527129b981ba74009d8f05664350203d292bcd134b0666be97f4713917500694b3906fea844bd2f2f5625dda166ac6b774aa4471096375b0c0328711d0f9442b66bcc66ec2e025554076634d5d363111ebd944ab6fd48eabaf29f560ea0de1a5c01236ba7040c8adb66a08bf40680dbf910544f22d3652badcd7882bf771d0e6238cd43b259bcd05490c410d6ecd0274bca7762014e93bcdf8c8a5d898a2a927b40bb64abfbb670d4f34e9992594b219d3b2e6b13636d9e747c0d6c453aa4b59ffd16c7a064cefda2f91605fe58fe3ac4a294eeac54f74d85663694fbf2668fb21fba49feff0fd4929ddbd676266e91d74b318aedbe3dda2bc232e3a81edf5bf40ebcb36cff8274e4f11dd632c07aeefa5d9bbe1b6244fce48cfd6c44c093cfed0f96d20efabf08f5bfe85b658588a460495647a410d742e6e433e6c19a0de85001dbfc358edd80aab8582bfaec212fef83f5660b65fabe4034c0fbb014c6b2ce026dcd2d6cd7030f2071836c185374cc6df2bde71dfdc51d4330beca55a8b5e42a4ae2a1b5e7c2b023b4c76ec7bf34b25025ddcf9418eaad01121f56c69b2fe19afa656a55df31da0cfb979840e12288ff18578af020d88ad7e19f15ba017dddae696e7d668d8cf72022e8940922259d9bb862c66a8d2de533a9089ca265c60e7096f5fb77408f9959d4927047a45117b76462b84154735129fb0581cf10f4cb0b9b7fc6e3a5456aeb0d60ea1092475539878743450c4b8e458f84a490ae588649778943599073c6018a709b95cd622ae223c440bb9d8e83767c788ccc8e0d2e36598a344a8090e299b0cc579c7ccfe4f9bff9e70ed315c3c33f5df417af71d0d2ddc43ed3c5c80a9dd3e098f2f7e729761be1a4920687bdc1d22ea5f4e3f0103aff5f9741e990546f8625338b5ac8de64247c4bff73d3f6d0bdc015a72947ea3abd0a9c4a6eaafda39e238eb83f4940e3016dc413ed4d0fa7ec45723c576aeb502c3b95c70c5dc39c2d2962535d11f4712eb067edb046c3ded3b429e7f01acfaefcff1469143fba984327a90934e129a26ad4513e9e83088ccc503593f9ce687f605878e9e2afedac41ad1e4504ecd54c3f6c4652afc482eae2e89223034e30cebeb4fce3f17bf9987f37a4a584f3005bf9e90c84ac0d7b9fd4f96270b730b803ccf6c52719b4ae2b92a740079ba42e85b201de8123d8f846022848d670b26776f641a43ad286660b4e42116124932961f05aa6c11085055ae5dc7732ef88b8fe36ce0d2e03c79d4a36d5232a0c0a056c48d46e803ee986b0ca120019769ee3315cd0a8b52c513dea4b5a7d9f92c57798f5358c6767cf7f23f76027badf0b6deb6c6a384527ca8d8cfcc27fd57ce9dd4dc85aa3c993cff7f42a21c1eddfadca8f7876e0fcf35a05acf701b4c4443f4c193e5145582e8fd47871b25c72d15b729a090e89c01590d61ef9de84e46f44c5a0085528ce90cdf564d94886d4bc0e1e518dd4d310f14a4b95f7e4966b08a6ba9273e6766963662a0882b1024bbf8a4cd3af685ce325fc1db6e47194b14c8c306d605feed9eafb964e194d3d87fa241b6446b5a4f7ccc2d4704ba4fc6c32e5606380a78ed506ea5fd46650639f9051782a3a442de087bf59846a0f18fb4314244b2d40b513d3c60e3023f7e9dd06427cab0196fec10deca2b0a8a7782736ed2675f4b0e6387b9f9b9e4827cf32b331aa9ef9d53a69ac48242fbcaa40e705f48779242285ba1211731fa0c5ed90cd448666ace9c584c55259119faf8f7ecbee0f099bc32aa5433969b14ff4c538fdfb724b9fd5f7eea3f81e92eb5c70bd69331c34116a0ef6afc43048d826c573b21fd82f54d4e13e62ece07f5f5a9fb842b49374c25cd247fdebc30f8ed6a725247f2d330ba8d3563c3aedaf2600907bbbfc557c571d6475fda588776eb67e67062fdfbc0e5b680e0f447d98eaf38dd20f5089d113015a1d4906696f649931ff9b19d877d6ecf384aa8068998c63a267cd88d3030bd478d61de505eb43cd2013be87544d39714554e05b156a9b16cd8e6a57c85a08b83030807b5da572a27d7bd1aa2d7c344ab659b61f568fdc2f5b367a84fa2377431a916549b51d518b27425801da83a481664f3103bf7012d0e142375933c54fdfae726481ff86351bfc5c0447acc8e1ca21e8dc87877dec4113c7f2063994cda296f6dc6e035d2923a379bb986320ca4d5e6b93f95d5269d518d1a3faeb9b86d049463d1075a8f77620e1796620ce510b43b27c35323008f792109d7b8c33ae1eca308ff9996e9a0da2c2910fee61742a92909505ce1e7d489557d1c5c89ba225e13781dabcfd3e7b9f1751ec71eeecdd0686f494295aa24c0c2eccccf5928e0c906b12f825afb12d7f4b0694ef628b8998b953099a1a91a5189413a9e6ba9b4bc2ec1f21138764965d10370b2c303541ab315204ab8213070cc27dc4677fce5516f557b85c4b0492bec73c98727842f64e276b630c680f53e393ceb756467836d49667cab67b463e4e02ce78bf6e2dd33fafae68fb97cc860fca554aa14b60f8e6eaba964d7c58bea7d2540a7d4b554273f35d3670573d4842ed6f1edad8fd7cd576132ff4f51a7536d6df6ed304889cf9ef332e83e01bd2a30487417bcbe3a6e5de19c09cecb1f722ec2853153654c67ee49da816761cfd90d5b5f8ba77ce291fc3789a2b37e0c4212b215172e5d9fb5022349b53cda6e96f2918cadc2a0f028487f7c3a1c57aac88f43d8cb176fb12bafec6fc657aba3221107c6166b890f01e37600110199e95cc7995e0db2d94d5be9b7becfb29cafba5199869a459a5412f44ec7628a1ed6a7c1a51fa2aca4506f2190cf10d7f212d75d42699f04b97334498aee10f0f783a7f5bcd26842cfedfa6c6f650ec3286584425e66f50a596699fc09b07f637391071f2cc9d99d488b29d1705970d82e643e0cb9e6406c01334d9586cf72cb860df240ff20aa3ceb3fc86b5a78e59a417f914aa8b3ea8b50fe6e93d0ac6c3891ba69d2662d49a1c987fbce266b2ec5355e6837e681e720bbabf3cf2663b318440a463a9e2f33499bc9ab1d6595b43194c8fc1d325ed816a94d02fb45f289dd7364f6523630cb332b77af6ff268f720d9011d8a94722d1f27f0e3d188fb0e4608a00ae23aaa04c852d44879907c559ddc907fcc86a92b1957637d7632a6f5ff7109799a879f1389330efd541e8bcadc14dbeedec17180e6459c3efe5166d37527951eaca13f87022fd9c3de3ed5dba4940db917accaa1cad61f1a41917b14ea7ffadcc65d28eca29b12dcd968aa8e71b58f88b562183ee30b5db9e674291ad102479645a24ad0a5e1d9f2a98a0707e7950d79cb64a35a4b725b7bd3897f7b5fec505a9489360206b3bc8c17eaa1f4940256e1b5091ee8aa341ec8b3446a68eaf7bc6599d62760fe88db39c2ad8afc7aa1995085d7fe7fab9017bf919ea96dfee9c84b314729621e2d7ce21fc0fa05213d5f737ea123b15f853a64dfe5a19b9936e16b08f4709b85558a1959ae9d4d2e8f17fad97bc5d54717fd1cd41c1b42d7ec43079e2f95aeccee1f7ff397dd19fe569f4a9c056e257bee06e113597f42fcd0669d37811a098dec07ad661b01d9e7a7d7e8a426bf62b600ea7f5bec42baa3af043e6eb94c920c06eb6dff499e13519dbe82c9f076fb7bba5c67686f7240042a3113a06f2b199fa4120fd017653e6e313eaff85c0f279b8799bac75d7fc4d14a6cf8f67dbb8ee34315c308c5487f5be4e9123b2984e9dc1c4901383e4cccfb5d9f506e5b557092f8126706e4f603653f7b936e98d967e129fe7359191e00d80e91730fc0f7d8f109424f7fc7cf9a0a7e809527bca198bd2468266852824b276cb084fb03de764a3e7e3e52b09571cd9071e3ad391b8842daa61a70f7497c8a4dc8315d146885bc11a26ccf7e02611b01eeb0df7249686dd32fb06fda4f6e47ec76b9bc119b49bcf6a98e0131f7850efbfdd8729d325e1bf024f1c8ef44b288a977b3c57304d8736801c2d0deaf16b1900432950501c9cde20b4c57e802ce2e11bada178863e775a8b21c4c96d3b97148318b7b4cac65a7eeca711db3708c698e5ab724d851af8f5242da26f6c0deff64f0840529b08eafebceac47082da2c082a319577aed5679ba15ff142f096356a700a60f66c70c8a326f9105bb7638d69c6b1709e69ebe63c28fbfcfa6e481e70fea9a9e79bf2b47b4de237bb654144b8761918c862635cea05fc54227463437217d2341af1655c9b4fb43eb5db11a0f5702901a90025267c13f11fc8852a6b11ecb52a3676c59769713ffe7207d3760ac8a633fec8868bfe951492c19e49e2b7ba90d155bb6f267991ab724f5b4d5127972f66d432dd7050c17a9e5b33d09a271c8aa408e649ace7ca34c95ffc80701e52aa114a34b8aebbd73f08e7643f3241cb69cbd01967ef5138809f0f09551e21b31cb191e840cee2d0867fb63665a56098d2195949314fdc2a1a9f3aeed8636457b7dbb0fc278ac576a5c23c6fe30863bbdcaace6f0b6687ec723e3f126338ddfcbf0f9b8e0d37839e36426eb2e1226a2befeaad9db447a9a8c16562499c569d9f624e1f371da73dbf0f296229ed7ad83723cc75a3b911f6c00dd39832763283aa68b0cd63536a40f5eb18e8e98a7b589d76f6de2acd6e6f3edd4d981528ebacbd26af6ef9940a10ec97de5f5772f52e4040563ccb6d3d82306376c0601238646cef3d3d00ade6e2eea0059817d6b609825a77f5d6167a0ab8b828ed255567482ad781e9e5faf7b068fdb96444afe60fc2a6fb455df2ec304ff7037521cb0058e45e724064aed0df9dedbca858270ad8302ce90f8af3ad9c23be08ea60aadea9f6d945341efceacee88afe51f80b78e31e355ef4ef0e7abd9738223d8144d55b449c831bf8efd5a514044d76a96ddceaf888792fcb70bac29d02616c232c200eea30e52bce32da663b411b72198f6e89bd5cc67733ada6b85068e08c9f3d67c82127f40b4f5c1fa15e8fc95b58914e52947e0df69abaa57b0d6723f5a6e87ec2b2e4638c738e29cdc3057042c2c7ee7d2bdbffd6c450950fd032b514d3f59998a034e1426d277b1ada5c06dc271ff9d5c2150772c303495d0061184fca4fd786b24ef0929245d79f1da25627c2c8c4d77efe47b6a58322b1e591060cc5601fe8fc3ff67d3ed8199352e0b6a7ad1dc6e221f75dd55bb863079638501a69e322c270ac2897f3a2151f2cd0bc5875b9f0436ccbda1e305a58d3477ae202ac0b9adb5df9e777eb5e08aa7fc53aef9b1ee02c1a347019b4708e26cab22691eacf15d13fa3fcfb21bac97144070edce28b2af93bd6ff594d4985b6de7cc59ef07a5afc6523b27d88305eea64a463c53aa522f1f359b7878414de3ac7653104c903cc5f30c00297ae5c2304505058f15487c37a03dc46ec76883cc7cfe7a91c7467cc428f545edd6b7d13564c759f021a0cf141de57ceee40ff15fcd8700428aabf4f09fc8927dcadef84eff9367c7a801be18d812dead749838be7aba6bd3705cb7f91b7b1d8d0296d4bde38c8fc87e66a941079a5c0cfd4c6be88b3ce54349fd6d2540508271c1f66024a0578a9df177859d047b4af1a4971819242d45ebd0cd4d18e804d40899ce57ef07efc8bbf182da89a425de01d9c97fe29d8191ebfe7400d147915e2d28ec455424b80929ea509a70f740eae59cb8991f3be8e26ee7be8e89076532f707200acad4cb12d41dcf04b46576edde7f847cae139264ccdd0b5c5821286a2e76528c5acf04d2261955f8abbda1dbf5f102e2d36dc718c0e81ce3f560f0cae195191d479070d9748a904eef506c91ac3b6f3bce9fba2f7f52c8071b22005d09b36bdc39827f01103a5bfe12ca37bd86167717789e45d477103b374134cbf85feb2dbe6c1c9b89bc39505001582355b2712b65ce56aa7676151ffba1c4958fbf8b7407633e4b17fd0ccdfbb9f207a67c1dc6d781def70b068ce70ae5190584c12cf1350c4272025014433ab4f0096fe8b9f0f75ea332d8cf76e5072381291c46ddc788a988777b172cff7337727987cee77a9f165bb289b7d959e2466b6f2961b69d0639ad0cebfa901c88bb069fb451f8ee9575f8f8a5dc68623cb0c112358262fa49e3672deff39a5f6c73c84fa5475fb10a2f356baa9332aa29f8ec63bd5cc2210d62daeddf83e918974c82a97458d22c02647194e0fac802a1e92d0924ea0288913dc514e8340ed5470224af9791d34820fdb01d98e9c1bde85e13ddeeba2a43a5901add710e0ed235455a94faa5ceffd85dd1ccd3c6097cadac9ed5bde438e84cd0b74de772906d6ab9775ee4a148d0ecbdc97babf6aed9a37b85f958ed490ba8676262ffcddd7de96a6ed55914eaae4ceaa5a212432e5df61117334a28f116149666e4fe23016c30d9c2b19f465d05a49f78244d9b3aa5f501a1588e1fe8448187fecd74f0eda13745790e69da58b209b3258b17eb563a6884e2b6cb5c5991eec7599b6e79a22eb78c6f6fc9675ffdf70c501a33b7c35566832fb0e2040504c1edac77fe6336d7df65fb8e1c41620b697016485b101b136252f13bdd45613d6a1f7ec3678035a77f6fee4ab919667593d9a7a89ef0f2f2226724e60924f0217fe3d751b0af38310ec7f507163101cbbe1e8c37f19843cc33fbfb30b9ea297c18e3f0367c87bee5116ebb2597cbb13b30a1c98dfe86505523bb2f9838c6450e37dbd22383fab869bb3454eb8cdbb23a1a9317b4fdf0c2e0ccc94d92ca8ada8efa0a79e3efe68f87f4b938759b273667d3fd8e4b7d37e94ce37f5b420ac59e2090b78be703d9dd853d82078c6ee90480d3cd2a9d5156a5bf7a1fa815db90085be45f7d5bbe0a7100a1de8fa13cbcaa7f19f64a6986d3de9a2c6554703f4849fde2eb614a6b09182632c1a0b1216f91f8cd65e95381f1bf2dce605965b00509ab461fe8fd29c52a69c5eef67a275626097f6c7ce0d1ce9564abb30f332571faf93ea8f9167dcdb6f7f3f9827a72c3edcb749ec3b3eb68ccb69d22bd256062531ce8bde983c30f6e74293abb2637cdb0ce9fff0b0b88ff9d944c29a1eb6ec726c98f2a84da967d9cd387ddccbb798d751ac2c2178efaf643eb9d55848b1306a471bd143f739fa09d201094779e9b049cdf8334e77ee8ebd75cc3d944a281de1228a51a1be7119533f777eaa7c946a0e958e51865070e091f264722135cf4c1f9e4a99737becfba81fdd9aff3c00fa84ffbc6b6d72d8ffb0644c177f6fd8dd034d6887097ae3a535219479cd8d8b84b69796f96ce72e36c01bfa96326a08e266e1a4fa3204297824db760f028f750e3ee099e235fd8db74a1ef312f267467e572dd9b0c3c5d1f6705299dee65dc66c3e8e2a7c79ba8a595dfa41fcee2fcbd7a696b000cf8c20347a3dd0f0f2d83e2f7524b9c8c8677985d1d3f2185a0f02878d947cfc8c4e50c84cd5cfdffa8559841e5ec5af1d0f950d89f8794666cbebdf7f9e08b2153c353966c0bf09f4762d45bed4ad77fee8266bdef2bc5ffb52968b9b84a8ce9f8bc4604d0dbd3f4337cba9f2b991d0c0aa071cc291f2cbedf42edc714f54f25de9be90796fb750beb838312c5e952d74730a57bd38f05069707bfaa5979ee3fd923a8f2bdb21a734829c40aea87087e5aae066f33fa8d429ef65a45bf13e2371ea0c3801158cbbf92005ee1227438a8ba2f596fb36ef200ecf3297b2464fca2d35ff10db7c2e0f2b5ac956364f8e34f864b25736242a2caa4df8ab70759376467e89812bee8def69d6d01629de9c41154bcff5bc7dddeb4ab0fd678a4c735b535ea963b54691b7e9f2ead4d30fd4270a4a906b73dd8cc67ada940371e42f1b06363bb820fd97393ff6082fbcfb378a06819910cee34cc9c2f723fb4e0ca0b6292a765618a6de82f269855d2a334beb7b93f5b9bb70cf0e3e727ace8ab9452c7d2b5dd1e99359e059ef372e1366639d63b678593e0df8ab2c1730f7fcef40212009d559e87cefa05e859cc0e5d0935e0797b2899bdaf47122fa64d4612d83b6b3b4a8456ee12b20435dbed85a37a86b64c2d9b1021d2c0ff6bdd7ab5e38f51c9062eac7565180a0041153c0e5a1f609314e7067ffcfcc23e7617deb8fed5a43b38f5787ed4cb8a68eb9b7a6a14e1ef00319b6a0d0418fba6812313ec187241fe80777b9b915490259c870be230b5930a2ecf0cc46d2d69b98dd935b5f2beacda091c6c82e714e94ca77119204b1e07ca60a328d68f0cf954487dd11b47ce42e0ae680135e4b5c1348f36ed58ebfa32cbfcbcf30ce62168454dda5378dbc0d5851a8ed89161303716f3874bc99f985f6ce523cecacefeb0e63b78ce4c77b85d7e77da1340fc8c1024c60bbdd03b173adc7097ed2340db4eb66d6f1ff6ea01cda63e9a139b8f55b20d6d4541d98e40e50bf88ece97830234b697d3206955f47fb0fb46bdcb6d5a9cb8ccddd0f22a3976ec2c2f89602152f5073a9a7ede9494516c7fad971e7b0a278ea1256a9d7286df63c54b855b84c57b55ac772cbdb5b4f5fea4edeb5afe37a1616ee8caf5affd1f3df5ee50171697c4ea43148fdd222864f0b29bf19517d39f3497942dd7133055a9b9ee5f596e8d04979be1d4f8e12489c18d419f297c16b76d777d81feb3cddda2792b73fc3ea5cb19cf8f32e74f360504b6189f66eb7cffce96568ea5c85b81f2fd646122a044780274cb8ac7ada24827f2ae1a23f940d5a2810d3bc8ad7590ccd5ce34e4baba53f919bc32d4804a5ca6b6e545179161cf29893f7bf1a6f487d430ca5480492b37f828282b03c59e760f0a605ee35050a41305eb29faefc853e6762618c8e225582f5539ad1421f20e1dcec61e1ad677d78e4364fd95175ea89f2981f4cbd67c3f659136ef4bd5386ddd44ef68333891e0849ff882e4fb9298a97ab69aaea6f0ee02d5fe78da3de4d40b9b45f6adbee92122f923b28b5d878eaf6cc3360ad18e854839ee044a5a24f7c6e49881fdca1528502225c1c4fa522a93714861ebb237af008983aab5fda3b6d16b8e79bf430fee8ee4472bcf70025cb46f2a0072fca36c603b6cbc934649a975149a8d848e6fe8ca533abb97c8c7d8692a3aa0229c1cbfea8bb9ff619107def7c7d030609edc33685bd74454fce7f9baca3163745ccb7953b3fcc74fe5c44c0f54173a580aafb9bb08caffaee606f9d9bf56f56aa6715df895cfbf038af52305bd22703d0a1146ba665388cbca88169c17c2bb2a510743b25ac0cdadc1d48cf953b919ba3e7d308a3e1aa4a2f7b9c35e95201319e3ba2234608543e19f56e2a45341c27f8e8ce2cf80fa17a66cf56a4e4d6286ba6563b4590d71d42a86641d8a2795209fb54c59786f673efaae6047ab0fbfbaa6cac1a84c884ec5d109ba0424b5703517d2c62d91b03cabb3f68cc89a960b5f9556d74cf88bd95ae45035a9fc7181e0fcb469fe59cecb3de764f37c73dd0f358267eca987e8187e9b32e40676a9314ef05f4bf880f6e7f9de45dd714080f7e02fb87a0c30daa2263d797e598098332096b8a177468a6f7ca8f3145b97dfe57291731164f003775a8a32fb32c700210edb4a50da2f5b72892a9efe2f409217a58f8b3079f9f50e1bbc3570536bf52b5f073a6e2153d15309f86d5950d4eda309ad2d7d9e13edcd58fd218d52fde319606292aadbc5b60727cafee68dfb35363ced21763f53ee1187c7d7b722a68d10367496180c8dd1aaa4d15db9dcbbab6389dce11310605da67ac6657a1095b9c606a7d3578b9402fbf07a5bc20dd66c5bd02814935c9df481c163c80a46cfc22eed28dfd32e56c073c3a2e1d89b568e3b87c49d49bec1c70f33d56779710615519efcd4517ebacc7b5ae055bc576dd1c38c4202c400c83c3b95b83123dda41f3138b5a8392268f9c74886d99cb3cfa913e78ba136eca6dc8c6fb19aefe75abcbfc0aaa89c50b782149af46ad54ee3bbdf37d796e089493346d69e3e6d8378d833baa294ff60713265e0ffece5136db5ff847e92c2aa093b56aa1630d899953486bed644f920a558eb4ac2d81954db03110c206dae64892428db9c8879e37a6ac118f7b69d1b0686c5352e51bc97ad4ede7da9c5635f64de3c7dfafb8d35ad3cefb5d9f116cce6400e895a70aca0595510a2379ad4799daef5248348dfe77f18ce6f796297edf32e6e0b6d242a51c0a5867262df42d31ccb70fc6fdafd3250cafe7bf947c89a196d8bb0d97b31dd5cc83058052bb0321cd1a8d77fc8c3d6fc478663ce64ef082d195d38724f4d6f29b4ae5f7437180c9b5f9728b73d044d1a8f488e750f62127e43ef3748bc32f890af9ea6f47762d73954614a9a6759d5485c90cb193deb4906a8395869406065c11e030dd80ec0c66c5d08fb45e41a6f39f959da3706a99d9be0a499be258895df2c140532c1185b7c484dc33eafdca0581096220dbb9c1b5213e18ec9d9a2b2dcea61b185e95e654e5dea85c5d5b62c305588d465bb22bce6c51759dd5b809e863b57ff58801d87f43cd14841a1c905b1547120aa35fe596f7d3569124755e4a7f937c71e00c4340e573fedde2421f81494096a08c1e8eae3867a2fb5d872c4b3c7a4787d6deff8550f99c51b37ea7dda85d35f4b30cb9942468efd96c2335b3f981f773e817a9e75517a56974c864d6f92e6a61a801cd090eae190f10c0fde134edc84f57285e001e1d1c0cdc450aa388613af0da3d861b5fefdae47ef6e01d747f568070ff3ea9a10cc58adcc9d61c97bb1e0e5c865e6c676fb7d01f66dd0987080365ce71a619cde5e1dda4e194b43417ec0779bfe118f45cfc95e32f206fa7ca9a052d45810a68159b2dfb290e9ada26f156f76bfcfe316513859c0fc4837c7574e5e248e7a9d44eedec4a7feb93ad473eff5e1b15567d274ff5e3a436462e42e624ad788b355669617ec6deb207f94be45125635a2f66717020ab68c5e6cbaa3895afca3b998d03550e1006fa5477388c60b69702148c8ba0f479576fc76121dd7f11faf55c12026a66c63752e08cda8f46fe833a6ade5c113148a845dcdc1991d982a3939720d5604a97097cafcfc5f14b06a8592ca90c9c7483f48011d2dea0586478d75cabc1a179597e21f7fa7169756ebcc7903a3e927860c458ae1333f00a18bb206aeb63e334ad9285f496111573623debaa83366d14bb759cf80d2491fc71dead8abe18b873aa7a99ccb6291a130b78f33980a5b001f47f3f364ccf0e341555739e07272ff9fdb4e2f2ba92d870fd56705d62264746a721f52a22afefd28daca22452e1662466222550e3ed128595729de927b852dfd05825e5e908786dbc67951b4c3b83eecd3a2ee7d523ea3e597d0f74c6b6fa4000474d864cb02375a76c256a26be8c7d904a77b8a79181e1a561c7bc89bd3b73a1a44650efc21bfecb8df096b5112c9c380d8c453645b8d9171ec1f8224fc4153453ee159f97c346d5cd9063834e5df4dab23cb7e5ddf476c9135f9142145624b3218ca43a74c774993a16ab6189fc754b9dcc1926aed2f91c9dd411c064f93098601fefc39be8a2a5ccceb99ff38f5edc61a617e1aa57f7619b752c2d3f4b0a45e893435995753dc23b91dd64b8caac9046e4b7cd7aa10db89807ea5e09591be356d719413943398a677c37bac31a2cb2487c1ce8c36c152d74608469fe22f47c497aee5b59dc3d9c3d75cb3f875389ecc151920a154aece267353b3ba561a8dc113802b9f1845bb57a010209131f3a1ea20f19385ee1a02916075a54698206922bf820f6bf40c163090cb0fc7c5d5f0c7c383edbbc7819748cd8ebf73eed5819bb997453bef5975dd9e40feab56edff903a3e70a38fb6739b192fd94b0ec873279eb863f14314c39b47b8dd70dad7bbc8a3fb975fb858c5a1f93d5e2bb5c94fe755ee9f0bdf362f0e11d65efa131f42bc26a0d3f78539067eb469ddb6d79031099c1da06e1607e1768da4e09d666ac66e6878fba21715c3b59088c26ca3df115368bff7470e45234fb818bbe220a522b2ac58337639b72a2c35c54271f845207ae069489c8a2d43341f3b19eaa376857301ffa997697ffb47ffb3e2f27bea5b9b0591b29f6856c6288fa2dade7552443d7a8b076b13e5601ac08d63825680d07b0f039b85d2b25c5b49db26a13701a49ff6c29a91d681162cf07f94726a20a1fa77c8cf030137b35462c08bf7de8665fe2f390c5cb9a4c63a7708b0b121ecd5aaa3b73c6828ff43e10702bbaca892720996f780b9c6d803fac5d3d3cec315c9edaa5b93e59cfc8891b973dcc1863c19c6e9e26c9e160ac525d9e9b923b9275ae6a6d80a10062be054c3114e376b48bbad81878dd834f7a6cb04b45321155fd237b9547ab2555299a4b500698f6c46064040cca256c417365ee6467b2c650a237e785d7071e27cde5c88ac32f4dc49e039f3d59dadc408a84c5cc5dbcdda111ab3d2fa4b1f664b3ff6b29fe36f5bcba7ce91b99cf8aeb4d198bebb44f896ded1b94791e004fbb45d9b6b44692dcdc128a7b62d6bd759bcde68ea94db8d670243133711b63b9b30f0a2c6dd41eb851f269412cf3b66602ccd04a5e5e7bb00e66b6ae73b092b833d42103a58b7a3224b9d8071e6dfbcfbe4212c52ac36ca3f036e00ad21000fb2ab4d7934a9e28fc699984252dc76bc272876c9ea13a3cfa53689282f4dcff79beba2fde98fdfd9a240a4d2536a115f0953908916aa0b3352df95439fe4a0a48946741c0685d7b968842443ef6566dbed77865461127392448ccac5c59e3762edafea4c665ddcd89ba1177f23225e962c16f491492196770cf392c96ae27c3028585b4ef859744f6e923f95da53f7599e353086eebafd73e103b1f8d050d2368e973ffbae67cfdb545f7c44debd252e9696823b7a2b0697482cc39f69cbc05f085fb6584703f0fcb863f0b5e7bda5c04f8ac2fd69606db950416a730ffa31d8eb579f0b036530105175dce46a5685587ea2fcac3aaae42ada41eca97ec4658e48b7f173c5f8f75f2fb6b7f481aafcf73746df393c47a21f776ad7528c2137bb6d11356f2cd9e569fa46ca158456ecd352820bc67087d158f15a37e9652c68394bbd585ce425db044b845a361a1c8b4e1d29ed0458d06e0822e647afb99beabb6b65fd718f432757d6bf6a3df83dc24acdc1505e590f5ab7df599f6838f44b59f7e33dc6ddda7fb476c850c55522ac24885143ac6d61b37509b84da4efacc3f31bc5b96449336218c0d1ce7ce00ec6cc1e926feaeca8a3979f34d1a715f08246795abb6611c0da59f52ca50aef312f866f76c9a8499cb35f78bd37c56d963dfa7f951322e35c666fc9ffb2f57f440f7b38b61351f0abb344da54bc26fa57a2230914af26a282f3db8f17c89891be8b37d6ad962b25add7e4e0c7c9057555c512b5b875aeee076f65564f00b92e2f76550facd406038e5ac026e935a541b5c52a9039557298d8e01b922ec5817fb3ba3fc3a24ddf0a1b5db0f548ce4ae524f61dbfecefd4a6de97484dd580811bb4754a18379c7be596c192b21cf02370bfe031f312ca7b4aec7009df48c1c430ca484c6db4ca70aa8a8e85c041d52bee1492f95a4cb1c493c477dcc517da360604f7566e8fcaa48351ddfed619a6631391915a2decfde11376b53b7f5880a449f197125dd7d858c2646037b7f2b6b23ad888f69b17daa2a5de48deafcb7199cb7a4edcd3125d0e8ff14bf312b6e84cad007dbb4c2ed1eb79c93b9f49aea4366e15440e3cdb29655029171ed2829c6be352f29e6c53610c42252212d613b0eedeaf39cf721273fca579209c80c216184a2ea859e5875f32240b3dd33d4419ae770c3da20156895c897aaa5b059bc8b56fbf79e020c73e9316481b5b6219dffbdb5aa1a41fd7691fbd5aca1ee8900b398bc928696b2bb453ae333ee28470143982d41c79f81a037d46c362633b32f431ac271d872d2626677058d50a37b7aa77fac124b46ecaeff8ebf40215a8e52eb01a69cf50d79385bd2f9ed67e8d5750ba79a9400157e88cd131c71d0d12db482884417187214013edac915f72295f084db65141316ba3ff621aba760dd25e78f0dad15b32e85cabae2e560f3e9f9530e1b3b62b3d6ca4a7a43f81710e7f6f4e2e10060869cbef2e9a1f90b5219e92a60a7e48e037a18ab17c1e2d90f7978ae32a9e8bf7dc926e15b119bde47671bdf4c1b23156f3d113bf4d3a0044e8fac72fce0f17a35bcb7da23b95ab86b040029a59a239f68c025c4196ff3842d7a67d5a57099bac0134e1ddcd99dad49dca802bffe83c520981e6227c0a22244ae5c515d4e3b7e0777955d6bdad79823b05ad40d36a87e970e56783db25dcc11d8e8ce4f1de30ecbb1706acf1d2855f804c3dc9fa029247ea28a804e3f9a2cd3ff45ec7f4b7387f2e712afe3505d7a3c769757d816d549cb14db61035f0a5587300cbf3d6a6facafec2f18e0ccdd0cee6e0ea4d129b76cbc7e0d745cd324c9f561b5976c1379423eb5b68da8cc9d0720ee645a2bbdcffcae64ac96fa5502c0f4dda71c97bf90a80002c56aca62176d4db24c74b7a8895e063b7102bdc0f918123e10b18ba8bfbc6dc923a87c29212e03b0521766936a09968a54591bd0b95b94e338a80c104513bd88b6f7d347bf6ea9054c11c74e4cb8bee7d4835cb462fcf0a801c1a9f2fecd1f5478a78a7f3bedc6e40004f03edbb39e9cd8be467bf31b936d41a0f9df6b1fec61d09b0f645bb97c9a9d4280f87aa21cc2c4ffd80ce2a1defd93d20d63b6988dcf8850722c160316ea389e27931161f77b032145f56ed4f66a0f0a69b7bd2de36f8827c4db25be88b7f39d7479e5cfb7561a78be23f6ecd5ffcc0b44e2577e11ccadcf266c191f09166811b123ff7a9a9c5fdbcd4fbfc04e69fe80f3ce8762d16203bde7fedd5a8dbbc25946d5fb27736fb2c4593d416d7e1bddf6fc1ee1374fd4f25a424d07fe0534b610f4d5cd42341b4aa04672d6f3520802bb127b04ccdb5d2f96c4de3fba950ed6302040d929461badb5f58b9bc89289ea66690eaefbbb371308adecd19c6d8a8169c9dda611443481481bb0ab59b7c417287d2a6fb0695a2a6fc9c519ffebb58c9b287c0790b7ad9830dc164815b019e7aa1039fc676ca2064ec43c86a89e5afb1f1509e11f5f9a858d023b12ee284b77086a64ff0d0378ac8d9a4b8f68720925da1575ef45e29945d61ec8925aaeef0d8cf1ea66ba0ee9e14a037c01876de553005a57c680a1d871814dbfe9342845a76818d8255c39a5e9c0316e23e5cd2f66122cd3379bc527075df8bd8622343ac5f243398be7c0832f6e4d9ff1664eaec76620958f49af889d448fef2b729cd54a0bafff9d2658bfc9a2e73d0e25bfd762df17012c9019514faf66a4b75611432eb4c9894f894e93df25fcb36816717a38a402a7dab0b8555caf2791bcdc44f62905e3fe8885493aabb43f82daa9db833e0fa8242d08685f7a934004288e69f7065136669e9eb3ce1b47cd0ea79fca35a4b1fbc11ec19e11d9ed8f62b2726e169fd35e85f176a59284aea269ef836731222b65f9d7ea075f89d346b8535a85a66d339dfceff1ebfd051f06f59094265d1582e5ec0e16eff60c1eb147c32a8130fcbc5df129ffecbab19fa8cf2895e3aefd015e1d6ddddfa81d46acc1d1a7ff3d170ef5b643d5634c54ae9725d2e14b7c1b0227c282e627ef8fe5b702b3f4542adfcb1542a6998cf231dccd0d127d43418ef29908e87b6555b4484ca7c2e579ca7a464ad249ea2792ea7a75db871dc14e1d6ebb6977642d9e1ab3f4d4666e89a6bf669da3805f5069ea9218d2c075df2624adce8c66bb658b5bf43a8dacda296a9f3c1dc9f57eec76b06a85c785e7bbab0b5f652c8140f7a73b9e9707d67eabf5a04fd974d200d0a4cbf8affcf23c4c9128926e63d279a174ec03c0d39a8d7bfb906a5a27611b9b9d5e357a4d23867e4ca9507671e287cae6e545814354a2141d8e4d174a13befde7ac3b4e9782d24e5d0966fb7a045e6bdefd2944833263962cf38f5784b63cfe5ca521408fa4595010f577b98c1eafff41cb412d600464dd6e3dcd3f6f8d6207725ed4e001cc9b39c902c22c0c3a53aea121294813d2f0f9a6a06a9083f166d4ca747a8bdc975221312371e4f234443050da7fae8dfecab63df3a1a3ca26f2debdca6f5406cd8f5853c17bd2d42914d98f081535e2409da2dae01da5dd7b33ce6dbf7d9d7c3913fe010fdc93b3c3b02f998cfa20282358dbc75718cde3bbbd4cbedf13e6ee70d77e4626311b3f28f5355d4cfc503835225f50a7ec46d2cd6d37ae2db668428378d7a57a6d8a6fd233cb4935d6b74ac3de96d43519aa91e22c6e4d52c3bcfc942551cb862de28771aca0c3a7baf216ffdc1dc77db197f4c47c0c0be37f878aa98c0b45efd1a5e5bb4798fa82150da117f1eabb119e048dcb0247b2f4061129c96d3a02323f365e42e9b79c2addef6e87cd9f60b16e82dd6dabb28bc67cd895077020e68362888369763082b1d5ea69a252e20237366b83454e2c2ffd72b1ab5fa9ee95d88f631260560f7836e6e04b973daad9405ae19741ea9ed77d4740b4bfa5bff7a3cf56b9368d8feea98b81f3723ef84fbf23147d5f27ad091b711ebdd02ea2a699296877b3f752fde4e5d69a5fd75ac39f331f9556bbde54b6741d45b978adf5214ba5d3a4aa339c715e76d790eb3149521bc91b7bc240b875d6c7b8c95d4d0f6540834d1df0cf7a63e9393600a1d5b41fc226e4925e316901c4125f3a7e59ff5b6e999b3993b67e6e357e5d43039234615dd0724fa65d7c15d4997fc330f7ea19d1bb91a7d6276515a54fb251ff5320f304ed747ed43a3c5596e1d792ee7c380416315b25a47e3334dc3b33b7ee0c74fbd49dcab37ee0687d19a92b222c7a226183961d3da664bf24d61f0cc838eb96dd28de66edce8d42aa4763f657f0c67b993aa250745dce5fde46daf32e71431ac301fd542d9c1714554d8887373f791ffe4a864605e22fe2991cc0d8bfd1e4eed0e9c84db4b635ba476dffd7f7896e8179479506bf16e239a27ee2e06d39b62d9ca07364199c8787bec0cb0a1ddd6b4010b7f865441c67a3c68777a4206889ecf34489253ddcc9639e40ace98dcc7784183087ceb90e16f7e8c6ef23ef18ad3f9f5b05e2c2c322a8a0cdad8a1230f4ce6033de93479f7710cf356cb2016ce36ec632d40b743d91936753579c4a0edcdb40cbb19fb94c3ff4c78539da9b747ea21c574a3cd5b40b40e6924aa95ce2d25670826833fd0246c02f5f04c5b7f435461c906447a0ce90d9e3802b22d4a2aa35a6585042d8fbbd1fc530d6f54f617912d9e89ec4dcd4fc8df9c0e5d0861c8850a9c1f255e95691561384e3e1bfe6065bcb4b4825ed350edca66ff21b3bbc635bebe36cf5280b82e30d58df7a11ac0951a5391523cc7c33a2b29323d77af9b37c58c70597d4277c3eec10d837d70b2c6d4a1b6095c5abc1157a622ea3bb5f0a5bedfbf0f8bb5fc5ca2bd2e35c4627d9182e7f204f6b2cfdfca452c341147eeeefba989c3e2d9e5016d2101ef4645582971527575eaae6bbc3deca4e80638dd27ce6e8e107b15fd5a5b08d5e5664fa39558f1ba77fdce34ee66dd789ba04acb4b7f1e48a03654aabbd1a89cb722b99001dd0a84b69c1a00fe2ff489f13dfce82b1edd3dc9b489165afa8ca9fdc1ed3ada468c9a7bc2c3caff3d25faf4f70acdb2d0fab4c13accfb888832b5f07f2c33b0932a04d161e2328b9fdd136a16297b0e25cfde28fbd2d5b575d12a44ccf2952bf6361b7dc5d7ff3b0baa1651998fd66a0e905723eaa149ce3720f13b395db5c6027bd2dfa49b6d18e604cb6e88db2e681175f1de7fdf1c04f4993e7450b4b9eab42fca102d827e26dc2b75267ea9255a72455c994640833d5c4d02d59d28c4ff2c396a8aed97b221b7207062929eff63bf3cbabf928a315b820177d89208619b0f13c892c26b800540b5ea0cd550703eb3f6dadafcfafe5f558f717d813055874182c93507a1a6141cfc844de1abe210fc664ea0aab11fbd500a22900119f32ac0a6b3a1bbeceb462c2cefe722fa7caa766d8e2eb48ce85d7f03a34529b130e33e214d16e07b20261e58def3610e2a48bebf20980a753f4dab2102a636c1bb11cbe6672dbfe89459b3633517923268d410eacd792d151edffce6eb826aabfb01ca1ad0ecd17f7b0e722c9bcb6662912eb33ed7e27c0babfa164695cb3757d973ef0ee763ece5016e6e9f02bbe7edc2fb3e9ffc1172c44f2a3ebbe4e1c14f02b46bf09139b9a397fc46dc1ffadd2794afbe91ba33a76477a147cefeed448dcc2d2889c4fc88c6f750a4118fc7ad2fefbb31a574765bdb87742546490506490aa797d3cd4de098c0aa4a99f2895066b9ff2dabcf0e28d30ae6b404757b1965f5ec1fed2dc3fb3f3a73cdff584f495a8417a4ab8144859d29c304fd23bbf83e9a24a42b3b10e03fadd139ff7e25f7bf0c0de4cfbfc071226725773df63c0ecf3d4c2b259911f8f94bcc1df8491e951f3732474c9b57c1d21b73ceb102dee57e6ca41e1aa63611ea99ccbc24d5dc180b1c2ca734e5252dbdbec22b0726132e4e3e9311240fccf25962c691fdae41d29c57fd4c86c66cacfc1c07410bac31a4a162ebaf210f2c3bf0152f0adba5d2eeb8206287af8038e6c1173b3c33daff03d4f442c9d3b761d405e9eef196e99b2d9ca57e55879ea23a4fffc54abb8d9d0400425f8f5c65831fd87e5c5f36ff81bf478e261074e019b387140d91bda316749c6b0d581203330e9f74fbd15e4828f31deed58cf9af4fce27b14a68429949ea2ebb99a0b0fec2dd0b33dee4b2b20dc58957fc7a2b9f4a3aeaf1e3544afdd82c947ae8c88174241fd1b9af1e45cbc13801315ea192f61090b62e2bacfd1a7cb936a2c588b40326090f4cdf1d8eb0a6fe671d229705295bb0f3599b5dd1557de6b78de7c3fdb9893269d063f2d6a2b618f79c2636eae5793fede5812ebd19643462db4c6b95bb60a32ceae2fdd41888d9a1796403f606f9c2aa79f8be35772326e69237771e8a332ff670db5d5998360e2706db2a5eea69ef84708d55cdaec255a5b147ae3e758185aa1e62a4d2c7b8da7eb2f12689ff5bfaccb28691b718d7a59c8bc00e8bb734740f23007e47e18d8bb545c9732fcf3be421e73e4a20cc6025d4cc21e578dfb47141cdec0270968a881d48af21bf1b99b0da033298e68639cae979bb553cb6505fcb8d794a9a0d85315e6445636422c6677c1590875beac46f70100bcb22bc299e05fd8f807347ffd9242f0f14c0957d347720941ca033a860f288f8c3eeb846dffbef336ee3e44689a0ecb3523d966ab166cb98862f445eb85cb09c1a6b72969beeacc3050a75353f71980111dcc8eb03e0461e33bbc52ceb14313431a024b428c3ec841483a367984533c001a5617089ad689a81cf2a5803e50d5ff971ee91f393e08fcb0715eed81dc4bdd7f50093baf2fc6755e72946e6af256359c1882091bf7d233ca3b690f9c98b89dec5138b2cc657723c06c0ebfbad30e08aaa2d3fbaa3c2ccbf1d2e98a8f615a2ea6675d31ce4f4c64d1f8cad3cbc23a31257a569d56330fc42a69a9a4a36433c2010feb71702b580bb8f747bdaa4bebf8d78acf1e4fa205afe5dc87e2b7ea117b8e9da28306e85b6bd105e12b6122359103bdc3d2dd90f2f5be5c0d095919e45879c7f3b45bfd105b501cfc52ffabd2f74ec35fc4b2fe2a1bf68ad95bbabf68edca8b9d8d0ac17cb7ea24935e95e86f42516932249a61f43cf9f8592203b6e093cdfa45cfc491115a21be19e5b959b0b04e4771b4fabf633e3c3a6332135d8fe588ecb5c3ccef3252f16efd1c88deef9a7e0bd888a7420d52d5139b27e4ba8f3a85893c89a636930dab2bc997f5b7491ff75a672b73304d25f0b71c3e6dddaa620771448156153d72bf47a78adda961f75bcbe6b8a3a4976a00e84cc44ebc44d044ce5ca4b86fb152119caf30141700101e7d593edafd207378be0fa47882ae559b57e691e618c928a8ddf2addc1a48c34d49c54cb19fde526c10077587d6d681e4ab8f14cdb0feb6224b80dd5087ce39e26234f6f0b4e054873f8b78304e4cc67a8a92c707fc29ee58af1e04343440eafe56355a22abce7c54f43cd2e0acbf232f522fc985d656a186876fb18bd9b424bf90e039b730528afbbe6b48baba6eddb98db8c6888ff108c6304d69a2d4a2333c46fda5bb4e2c937e22dc075fa3d26f81832f05d48c59bcb6a3160a314b046a6f019111c362ac3dfa78bedbda412e86f2edb8c20fca7ce408aa62fa771776eae2844244aa6d459d436983e88db4de51fdf4f28851bc2daaa9f47057fb18e8cdd11c206d65c80daec169aaec1b0b4f51281268d7eb487bb6de9f377b149ad386ac38d450022ddafb8e6c28301f474a3b1a56c73502d73b965dbd45f6ebdf251c764f2b3c47855f73089cc50b50d522da9e8a42c62fa467d6d4ad92d747cdee470fc18271d1d4e5229465b0987c69e2cb94dfb6650e35d8f56c0d4b844d1ba2537248a431ee631de5d7aad4cb864274aaa6cf87b311395cd06e6fa9e9bfaee522f26dc125b07c0c505e20a7a9d7b57ed0e3198c192b9c8aefab16f419c27c10eebe81bc52f71c1aa9850e284eb042d87916dae47267041dc4606b257b191ff704c1439fbfe5782b08b4010827c4a38430ce1ff76e43824b38e463f78851e8920df9d1620604ebb6f60a32c06d2e71bc951c4505bd237c031caf97931334fa79ac6b537223354f192fc4a24774134bb0b27f9def11ffc73873d55c7ec838bd50246dc925f82febe8ee3d882a906f78fe2ed8beb644dd3c0e35cc556f85e15a90b3b0182be40fb17666861f7c8314030af91b5bf4fddbc61832d57c9c2fa47a212ca96c7ef85b5780a93882fc5decbbed7ad27358d95035434885b0876755f05eb7b185eb5b45dd624128df8b3a5195888e73b4d311fdba9eec56b89b8a35fe52321feedffdc4bd5260602cc0319e2981fb69a564096f664b058223293b63d1d5f1d19c4ec55e3d26b447a45b7f27ca6312808ccf0a10388d0feef1b39775f5330231bd71e34b467cb178c37c58344fa6e2e9d8efb7c3d7698e3823f0c85974ec68acd4d580f5b5516a03d4d0f568aa51b165f575eb902c7dc8553271ea3a4d13366af94877ad9fab70d19be2ac6fa21f2c143a8f990cf93fc268ab1d027d5f46a012269fdc3b4b7803688d4b8e098511064312a20abafc01c3d177678797e01bb41c9c200f177590d9b9c5c5bee97829f58a21635ac48d9ade622d76593b4cd7c04977c47ef4d16bfee6abf15ffec76c53e56063a97c74934681d39b1045a45e919bc250642f14d709a54658fffae0bc6d83e439095c5db3c2738a27ddd3938fcc019ffc75d2cb14f7e57c36145882b68c7c60695a4e5a3be3a45dcfc1df4b95cb836fceea73249495df44bde6309a03c1d09726244ddbb08ee29649405c2816a67bc3d54ff9c0387c7f707509c4456745e42b2ec3f46d3e7dc167e24f6f773e848ed7d01c9f3c2e2d6091ebd04642307adb3f3a495be24b85b2adf5c5c9dcca3b5ad5e84f84481d86ce290cf5ac4e47d19d48550b69b0344e2707955ed5277d962af5343c1404978dc7797899f22f825afa97a49cb9f9afc588db0c22c74da46e053baba91a75eaeebea9e3ff4e5319fa9657fe7dd153ed0c71aa7ca48162ff1a926b370dc4b941cf494da7b942e4b74fc3b5d20e749933547163e38d29031fc2ce5f9eae0c3dec304530566e41c7bda8d982067095a178f1fe1f474769d6db7dc6b31aaa3971d462f250ad0eb2878867dd135448a178c3a7904c35d8c4b1613a629157c707e48af1792ff845e0d540e43d0d7e2f9e660679bce7a7105fb0a4bf4c3607c1e06c539993c17ec446f26bcc66696c08c80f4d08386b37779aec4d853c4bf87b016c465860f3bb1e5785af8d47bb6a611e71a720b2f380fa71cef364c97697ee0e83e32fd36af10280b43aa35584d5c56067732092ec373c40fde05950439eed7b51001730efddc4e042ce172d94ca3cc3b7d69909a62847101e8e0cff515bf3817cc24d640464ef4c6ce4a9d8db258b3829d91f3e56307e8b2211f267b269904f10ef3ad6bdff67832c382fa5ae96c45bdb7e368c9f5b91edd063a7a1c624e8948e7f281b2454924c3a5735dc770f1b8f69aedbf28b264ab08a84ba01df1f2f8789ee0bb249ac809726cbf4d72421a4f1be06fca28c0dcf9c1c7b0826e3f775f911b12bcb33688f2995fce322ea0853561152cdd6e57484a64c70c18d9994ce46ff0e3305af1b2b72553c24504af2252b54c6fdd1f5884c61fbca621753f67041ddda30b096cd4288665688f0bd154fc6e93857d3f52d909b1d0a3346625af956e25ffa87c95e0c14560e77b08012878ab34583c8ad2242d272e46910cb728f91b72707c8c4716ecfd96ccc0221bf9c69af9014e6c243ad64447dee44b407d58417018d92e0e7d91ab98e1dc1ea1b0a3656e67098bfdbedf9ce1d927b251ab08b40aff39fb6a40510f16abff37624470658e4f557f1dbcf171b821ad6a954022635490d9f6f57bc361ac8b71b5434f94c9d4839604979ea122b2d187a77748157dcd711d6ad9627f375927db8fc4e5fb6960f73a2bd8c279742fca120b790f796fb2ca1c31d5522a296a33b3c9c3c82a75c356b4e9a8805ae046c4d1bf2a4e14a06d6ce9393526be9d8c766cd8152a7b2cb6d44f5aaab434ad03cdcf8c418376b285bfc6a04ca9b73ce98be58a356ee0db9edee4508606ca6a8a3fb23ccb2ad6c796cef965a26d941a22e8c05d51fa1594d92dfeb483205be8cfc8fba779a1b498a1134fdb731aa5f980eb3695d3dd39ceb2007af40c20fac81c7bb2b64d193c2da88f3771191153c2a8d0f8ac5b04e48aad32bbd487fb27ab1c316917ca6082ba35e9e9879dee57db1e280119710ecc906387fb1e5db786ed2b5d715db8354e033770ea40f51889a2a8d3d3e34b10b089cd87556a1a295180221b7c079734cff0422581230769a580ce16f025a7d5860b9de92531d3700c29411fa077dcb79256e9862a09fe2b471bfb0822487d27562a49aee59a4fb0735b8084fd2495c8af95f8a82ca31210dd9085008731ce64e99f635374919f1d0f5205764b8d6bc884011de6cbf044696a38f9e9780ec9685f66d46a5f647c8281c2c06040fddfda1c0d18ac20ab5d4f6a7d341972fdf5688ae2f244f85b88983b053823739ebd7acbfed4984dbdec4d82e93878f6a982122f5497b445506b44a27ba9dd17b4d5520b9c83f5aa3bd3123c628322c410e15f423090efea4f55b0996532d03de898282d9d397427219de9dc0935b8febdb14aea57747ff27c57af7b49b35bdc181e7a9523a3be7b225e992a610b38e357c9484d797bb9cc7ee28d7bc8f2fa40b77df7459c633e14c53039a2dce36789e361b5aaa2d8de15c2c1465e868ca1db02cb04f9628673c790c9e39f0d9bc044e2e26948c10d9ccd4419491bc86daa5179284bf140a467173efc36707dca47681f1110a27c695ab690226b36d948ecf6a570ee22e1be2d18b2d9fd25075f98530e3823cf28748720e3310df955aad9890f7b91f296a32ac6347bd591d2e93d6773499e99f7f4a2be21b49e2e1c7bc0edab8d2a4a3ca954f9944fd08c22971e6798912f0c83643aea1fc8473e0685ab133b00435fb187fa7d06e821adcc9036a3ee823865dc8d7b1fcbd0f450378f270af1f6516f8f7315aa78699fb07de2406a879880002bb38dc1b33a11534f29460bb5c2b933271a4abb3302e76e647a0744c5b8d7a4b6b5eedbb57f44a85925ef67a386758bcc222e4de10e589063b83eb0b8167c74e28044b330f12fc94d222cf38793086c8ff2788917d3f417920fc0fb76d689ae8c4538d4e02a83f39c2d4b53cad9023e0f1528f773a54cac6e16c694979a165aba8b5880c75c0c9ae52ee83c156157b2186fcb8e3013f404de8adf913380ab5889375e42e663792b11f11fc3202b9e7ba28c2975ff165fc02490d698d4badde8cf2db75079a6ed175eb6680f6dd5ee375ab46424443d2af4c96a6f261132aaa7e10bcfcb1cc4175c20391aab371c0832b3179033dd5762894ec95bf08c8dfc63c4f34e07ecdbf92496e5de791d3e2f57144bc9b2bb29d963357b045a6887f4ed2b3df44e59d868a2fef35344c70e925b3f430d01c538b94f43da64230cb88ba0b72d42f4ea43103e2efe5c591766d308ec792291d1981b08f08ad87c70b532d90ea5c9b2473f75ca3922b61f330b091234ff2346a345fbc30aa10d0648ea22264c8c715f976ede1ad2b4bb8e6a84e81c2f2f4247243f26a29d045b929a6b1217f2b194beba67fe92f16b185f3d4a6e08e12ff8a15152df11b24591b222d862e205b0469ed7b60874af2fe5ec6368a7a08b0c21bdc2bc2f5560307bfb1fe42517d92df93bfed945b0b34276683e6b78818d8e30ade590f4a31acda8fc0c825a73158bdd1fb571bbeddf18da79b0680330dac6025469d16f8d58485e38a0670124ec86ca8a04f55d67781c27c9c8740b87c09a67590f364f3c5e451366f7f4b2566ed39992e11dba5918391da96f08178a33bdae823e4dd783a6cb2c4322e56fe593abc5d551e1b25868d3665f90a1041d4f6f4429806bf6b95fdf6c93b2e86a53f693bb5f8757331175bbea7edb28862d41794011cd5582a4fefd9a93de99e637feeb136fad1f4c9affd4c8e011dcd7cdc7a159a7029d7231157b9a79b470b60332c48d234edf1634f4207116184fe57e3c92a56340bd0a197c0bf5c28460e1356ab7288e4a53ed1663c4806bb8ef98994340653eb05baec76308356e6f48c6970505d3c3abe2d85a2bccf1b2fbf0e94289ecff6d89f5672d7ee18a3fae85dfe465c161e72fe122a2cd1677ce77cab8369086ce6f8515f3438e7c35a371f3301da90ef73195cad47362284b2885e3d704291e6c21ad21ac8d41e93b5bf60d106c32c6ae84125cb9a9c77411982da4ad8446581505b886ab0d8a2eb7d5af9d0be1dd0eda6f48e67a4f8c2b2bcca71d5e25a471d7a7c6b6b2db0c88051db5f26028d055ecff504ed0fcebf2a7a236118c5729ef281ba8f8b3fb4dfb0bf38ddba288f3e7e8289dbacd2f625d86a5ba97644fb19c4687fa70913cfdb3d5f1c9b7ff81f4ba0622b46f6bde29ae1d491e2dbcf7b1918342921f9f4f0a5e5226de017ea3aafcdc30b4a2ebbe38f4eab9aa33c668927090f78fe1e418a487baba7b9d1f3f1f29747be2e83cfe558aa22842deee49648d86a4f166e85eef822741af32b934dd42b6753b1ddc1458122529a90ddce350323d755458a450b840226864d05a270e0a5d024f3b978cc698c33d7265170fb777703ae2ba05bbba802e6a4dce1ef224af52d9d07d8ed587c345b61710ac9948542aabdad0976571cec3f7a9c0513c8f699ef266fb1ace7793acff7a9cefc3e618eaf6a552d148c9e6d3410c48bead164952ed8b3d22cd2fb8eb9bc368c69e821be73dd22d602e8fcf7b6ec343e1695084282f2118ff58a6c4d435e704b2d28a1271135b3aad7baf1ab9eff8a1121dbe3efad016e53810f9938a8bee717426d87ad24539f3bf1687c8e4bc498d8c9a541d9c05a2e442de90170a1abbb7239c506ffa1e89ea7fbe3e422664d869d30f38a96c7e50ad2b734637997c9313578d04782b00d40f3b0fb2ed9ac760234d7bb72bec202e1318e7a3167ff1de29058a71b305f63284a35822329b3383290a8855ec344a3900dd7659f5256b82600dc95cad8c97040ab1cc9a1e7c242d63eab6f4c2f895f7a8871c15f7b6b5cb9f216affc9fb484d405dd4ac1026e6187145cfdbc9e9f9be1dc644efea082b309b8310d5fdce29b18b5ae6630ba39ab81f60b837c3d4a0e9827ec6510b6ab4f6604da6a0cca16fa881724791e8c92b95cb2afe15553899bd88035dab60992a049033d9180b665b786039a6e98d60329bd34fbdb68bd603efc4c044634109e9da1729078969e6e333cdce7999a6a4b7e4043165bfaf235653afdeee684ac516b07af3ceb8f2b680a59803610596ee4a75030bace0d4449b7a2b3662777d27c86e33bae13046b20a92140d819df422389675906b530e9999b2a341280a38f86365e397265775cd39785ee6f9f8d0a1b50f2a2f23445129045106a934d56375bd8a445665801677e8729a6cd3beb52153d1cc91686b003233828256b23e184a060fe7c42298b9ffb677525579ec4012fc55c79e5912c51c21f3a610dc45a5226617c0ca64c42d584c609a02a7d7b3d16ce30b0cba23cac146f4ea9be7c0288ab7a78795ba08d73c4ed01ddf70a6d9dd35b0f50aaa489dc9040518a6b83cd77de34f72db6aff602d40a9e292e4513d0acd57bde657f7b9cb688e1263f2ba11e3ac71ae5cd29c25f078612930501ad367450c7d1fc6717416b7537db09ffa75b01ac8f76ce548c6e447240864faafca7f1fd9b621382189554d1b1357f811aa3d5a15cb78db3420008cd38a4af7a52649a0e60e61fec146c1a42795b0fce136ec31cbc185cad3f586feaf970ae7da5523d69999686f5e618f0fb9bbec7f8e073b301ee0690628761858bbc960c8447fe229155418e7b397a9689c2dc9f69b270d249ea6e81d51e53fa00613beabf489616a7a2a765865eebce35fda5ec0608b8b789224c186eb132ea399562555b4cd64231d024a98f03de0e069c1b1c10307eac921aa4f41f5ab7eb71f235c6030ed180961a839e9506c262c571d4f577c9d011c4f2b9bd565f82c89f65c93a1a05c7171f4f9de703c4fe9dc16c17664ba3c4ee11193af1d5d0ce7917f0fe7cb57d4ed06e21aca9dc1472e918d9bade7436d12e15e921873a61e958b745cf387198680efc2ca56d878fe06e18e85a429ea50a2eef0c90949b999853d8b2ac153be49942fbdf5eccb490c8f3cd724d4b2809142ca492802f01e5cfb3a13ed6c9837c1a2d0f4e55b1c468663999b5c7304b64c1b8c9f66cc0cc898a4402a33816a7afc08bef131e1d9abc68d5fb89182d9e339b15a6fddeb85f6334fb3c7d7f54558b63da72742c1206f70faee0f869b179b7c826b3251d48af3da3fd6804fa0a5f5d7188129eb2d818c461af637c1b5de14e0d18725de8b08a12f39ce7f2c4d1db421b37bd44560bf10d6cbcbbf2b92065830d92ecc83cfe257abe09ed6bebb9fb8321ca2e22260dd10e1992db994c0850086f6b70f83bdbe0b96423d9498c57623ecbe0fa1e89670ebeef28554d85abd19d015e7012a68cc89fb3afd15b07fd0fb92e2a346782bdea7eb9980b36ea01b20c6bc6be8cfcdafc77c42949d3b88310b6aac40c9a9238c3509ccf6f567407a40143ab259ae258fc42388be576816873c58b148ac961900ffd4e97a7fd0c22d0ff6b04cfa3c478f856cdbdbf743f6f813ecee3ec6c323fdaeacaf1be57da6cf01f72e11dc55cab8f44c7d9ace53e6758a98da786b833f9f5e3465e33c1502317ad56ec541049a014b71eacb7780487ddbb9f833d2cef82e6ebf60a91c657e91f8cdbbe7dd61b69fe9a9b6257b9582a8eda7feb78694844af83c87f0eff2a8f74bb972c6d861c51ab8263e97b677936dcd7646cae3235c9edf29a0fbb29246035e811870f20de68e6766334b7b5c00da25da28264b672c50d304e91ee3b9aa0477391226a942807f8a0504a79efe56f4e18b63ec3199b2c0cf03437e05c2b7f907b4ce781e1fa0b99f049aec987b6e0056b3220bd5eed4341d924fce73fcb32be81d4ea557b8273c9705f4b1263da8885f5ca130fbffad1fecb850a84fef0a2745050682ee287ffa6a6389ac785cb01a6c0f2c7e590c7a15cfc1b8ed2a2334b91c276452ffea955ca5d66eb3bebaca275619ee1d99f1bacabf5b4a28d7930afd31a390215556245e214edf2a1ad592620b560e40e15fb336dcf6043022cbe61293c7cdc3939bb842433997d5235d7e9308f0d1b0b29a8a221d34081202e1e247470c2440916ee01f93866203bc212b2adbd5f4de74fbb749a5164d51a9bcdda28c2638044eafc3d538de4f409b9be782e9c0c192367f849aa6514b9bb1d23ad1d2f528daa49d30ca7d06b8b8b5c68f74a9f9a1f43e057940dec7118efdc3ef1b5de610f8045154f909593048e5076d0348ac2cacf3745cce5be54def6bb531faac9be3abe22ca16e1cd48f599b45999a8514e375cfb5f3f07475152245a33e115f7587ebd59c9768066b645049894df846afc76440e1eb6fbe7f4b1f57b0ee2710ed4d9d919d48d22566e2ff01b67d7fc9d105bda672b1d1b86049b40d712adc15a8f5228583cefbc5e48505063506088c0d970de56927b3dfef24213a838f9cb93457b923884ef1b356a6069ce572168d8fced673626bd3f82f5aa576e4ada2be8da7eed88d7724fdbf889b4ea8c3fe8017177409309efce002340b5e735b605496bf1a22dbc152fc1b9394cbf3b708409773e2ef2e7b0fdd5a4dc1889d515730f6658b2fa08bb1a4fae92e7d0a23fa255614e1ee1e8d7fc0bd8221826256a34f5572ed435628576e9f2999080b0abfb3e8ced795e06c937d0be3736f2e7ed907a30728610e11230d94c399a61700d224de84e0d78a526965184269243bbaef008ce94c6eef8a0128325a5672998c965bc6f8af879a286e143af37e100c46135ba7ca11b8ad403066837ff125f2a6ad528f5d55c60091d369de6fd290e4aa678ec4b9126ff38dc5130eb8af73a15b8b307741bc26b2ecc08571a0cdeab9b3042f066dd628f97a25ec687fdebd7058129ef4b4ee8a3f2df633b81718a564f7391369f3e9c50b1b5940358279af474fcf6fa814f7d29d75893565c7cda937471ae11d5bfb9b5ede0878f9a57662559851ef68b795e233e1a03d2cb23aba220cab07d552eee3503f151bcf0225f63f47410c0f25f39bb92dc372da8ae05590b8a809223507fb2f265b39105e8ffe27b547e0d02362f7113f921bca2c70854aff4d2776b97dccfc675e4f21352c6614f77fa56aead6aa8c3c6c6167f0168b7b6a7e06bc872a61eb3b5ac17c33741c48dad228160aef20ae732e62e2c54834089c371f1c574f1302555346a4ac651a367ab9ee79bf399ce56efabfc7f42c79b1d12c2cf4795b41c01a7c8526d99cb6293bc3692b43d84d5a14ed1efebf540bfacdddea5d9ea342590f95ea504f8890903df17705939628b46669c1489552845805233337e04cfd1f67afa7991ca29406b40d64955bcb65098685d5380d08c7c89d03f95056d4e90210d6d6f45bfe6d96a405daa1b52147b72a69bd0bb87e66d7dba78b1371b7aefd59efbca06567722430c6d45c11fcbc9004b5365f5a7a6496d51112a6ba7c644247e8bf40879ad6afecef2bbd281642c73095a014f5641eed013554a00b111dad0927129b0ae87fd9c7b3c76f69612ad0938c65143ad8aa0d4a2a454065c3f575ca6c7e35e86dd24ae229b15a3674fc17b24afb709462ef8a2b63fd44527a6756684c9d18bff61525846063671ada33dbb608f4d4fce045af9beb306ed32534873f7b3a107430477d456afb90b1e1b58f34ef7b5597efc42932b728f506052f3fdd022d6965bca153d29ef849c445eb5ec123a9c7c0d4a622e9bee0c5af9779c30005cbe15283b1a117504d13482686040b06f1b6c42bd56e8e7b762a2b58afb49ef3bde4a4a5f358dd298c9532ba610322e48eb6012b2416963a45a8c6f99b6334409ebb1a4ccb187c2bcf93d36b04fcb978dd5c3ab4cff15b184afcd1ad3b1c83a3f83d5cdb072f25080435b3733c7403d68a33f8ba1f8f899949d109739bee4c94bbb97213f5ea5c13608aabb6fb190ff6ebfaae99bc1196baa52c5ce55248e8f6e71cb2467995e1d8cd0372bcec1ee67fb05473508fed24a9584d90ca43b57c0e389b40da60a2b72170d62bb6e1c9677dfad2c88a2302a685a0feb300c4fd05afb9301aa2a4c073ccb9c337071aab37529d726fedce4ee8dc67e9fb542e6813765e7656c496f350394608f7046baf4ea7683f594bf4c8536318b184122435c17a7646b749dcbf420af59d6516550c73daebe755e2ebc5c985b11f813e1939c86a35606cc5721962a673acfd002d0a63217c46ab1abb77d072a3043b6f2a7c4ad4624b67a7a53249f9d25d04732e7dfb8dceebdb4d7bfdfdba43d15a293a42d13726c68d085d27c58266466c778888a8d345f96914e37f5fc6c70460039ad2dc912dc32020d50b35d86ebdd5454376bda49ccbd40cd8f9a1f365b2be1fb77749813f34bd9961ab595f9e57fa7edc1062eeb95c9f74973b998497eaee6b3fd69b826d85023a4f32c96c22e9a2ab6d6b411d05cd4503513726b26d5cf9fffd6ed56db698e056860f8f62f2b694596cc5bd72795439cfdb3ec127487f26816bc10b053b2ce16ff7819dd6dba1705b70bf74f8a7bc8d4bb1dd7874aa8b4627de51754ae47c3ee92aa1410ce45acd6f8519fedb0b34530f972d14f8f36da4dc67d802a1732f1dd849eca15b643e1a92fa5a632d10b2c0de1a7a65e9ed8567d6b6a41986afcfd7ec86d961ff561124c56bbc9cb9fd6dd234db83fa1a7bfeaaa12b677fceae04932ae9e3d0734a0157f74ad65bff764f2688609905ffc1eee121fc89dd270b2243ffa95aa10d205d3a37ac07f3b6dbc4eacc03a360f90ba61fb20c2f21369cf97506066d9be2c206b47c951f13751d15483cf0f830accf27f56235f6b02723ec857c2fbe045514cd6ad90c9c6c4e9789c6b1df57c6f3adbd9e30e1c6e6b084f7f9e542212921da9626096fe51126775163debff860e58731a3531e5f96e6239a99c76af179e3d9966251d6deecb3dc6cf357673e77f560d75a853908277e8580e23be2dde6ab01424d3ab290d9e5d6d95b4c8abf17c24ad04136b2e92f5dcc3a392f2fa6bf5fd7c78427d854a16d5534abe4256dc96e20759bef7bf3e228885a440dd8ee9577f2ceff5c821fdd4cfcd503cb664f4d48ce7e50415d18828f57fa87ccacea3020eff9a706dde56ea5f6c9358605e532c37fcdf4dc056c3831451ac444fb64c2132a5825f28bc9637256fd939ecbddbefb46866eb711e136f8e8626705c4f253327fa7d8153715510f4f7d3a5b6a620189a5c2c00efcd3354431ee1f02831a92e03521264a615936310484e9036a96ddaa6e08c2c842cf61f0684c4ed0e235cfca8968dff91db641ceec4d51a13d1b90679d206c1ab4dc8727a2fc5174c92d3ad008335670a02877e13fd6a930e621725e9b2dc242ae911db1902a4bbbe65138a04f1f9dc0c4ad31773d2d15a0cf9616624a4cdda392037970697706c3fdf638ba177b1afc93b8fc332c3f8d55917d0930c0da42482df362ba5a3bf0cabaa2c5cccf5375db4f24bfbb36fdf6efc575fea564ae8176b3bde40dc933175653a192ce7898effca2abee97f123d9858d9e2aa7565d28467de85aba6c4e793a972fbb4f77a0f2451a82e56a4caa7a4422d1dbfe91922eb4c79be8df7540a9989f79fb524b703647b7c090ac1f40c57d22f427caad0637241f28b9132ff56c6aa83d93fe48bf26ee27f000a81f7e6bf74834620b595ffd1ce7e7425b9487be94a433bd6a7480ac71b763fd2578f24df737d4cca3cf383a8768e0eac11d662441eee64482992b5c4d0d398c1a2df43f874f89359cd87a8e6d3716704dd2e973ab823f8656223db5098704b215a2b186cc6264c1d579ff1728fbba61b4adb9515365caaf77405c89eb87d328bd9a9e1993caa82c5ab452a6a5d13dace81f3c230f3eaa1922ba4ae1faa5e82c7b8c1466e7f83f3f11b507c4bd4bcdda294742cca02cc6f1c756c5c3328aa4085f616c211ed0d80380fe4ba306f9262a9a4d28faa3a37c5db981a0dca3a6731599effa70cd575cf8f87a0a860078c42d38e1bea655b3d2774eec3beff9562333298413a04d0389c79b73ccd949bc850e322968a19abe697a133e57b574beb8e1685f0646127b6568d7fc9107b27dec93ab87da47224a5196390cd5113a3ae388aa623da34fe36563fa5ca04007619afeffc8b6cba8bb312b72c865fa62d0c7ea5f9ff3c95335587a86b7012d7e22c757e7130728e5528c1f88b815ac0f743ad3e7f52e8d229a56da63f76a5e95197acf042e93d03943620d46b9b66a63fb635d5917abfe4bf4a469a5f91852e3b7cf1681eef58731292b77f8f74390126789ea81de3b5a2d21bc3e1724ae5c2472ff17e823e5e769ffae85521b5add84283b1ebb975b427414f5e470b9b5f1dbd37d5cf00c24f46aa4895c1fb9b2715464b3e150a80c3284c59da073a20ba4c37be1ca183db2f62d22341cc1261a634303c567901cdbf45f4c96bf7f1ef34acf26a29be78436ed002fec25d943a8beac1e21dfd37e00572a50ca1491aab9bb382cff2ac843690967814dbf7b3578bb27f0df4b509f9a014b691cb5129cbf538242078b4197b38e6f5da0907369737958b3dfbf62aaf2db3d71d916ac09a08b91dc5dcc2d1b609fbc12bee47f68f47ed268369157bf78bf6af9866faa2cae528796a98042a78449a142d9b5c6c6bec5b0304c9dd93f7e8feaa019683b4f5ac953b6572fcc5d08d63935f945e0cbfab31513dcaa9e758f2d263e07173764a3fffcc7e75d2c7be6d6f454225584c846fd5b83850c6a7c606c8b1bc9c36359caff7fe0481a38ed3b4ce9924a97a6ada23fe71afbcfdf047704ccb319cc6fbf4547191d8a9882e744655b2e2b72cf83426ba768dd5e99d4888095039564d5beaf236de845ec04d7fa0b3d4b59bd488a7efcd00350c47e212d1ccbb8965c0a45f60f7d3fd69ac8a95088476b798446ff259b2bd04848137acbae5c9c59fec13da823d476aa6fab052a97e26016c906b4c6d2beab5021ad72893110d26bf5c61a9c1b060be7334f406b83fcb09799e6822fbaeed04a54cd71630632e4635dc8285788b782bc6ac0cef9e83d05849e782daad69969ec2972e54f256fc892f66d98a0b9a9b5e550af7d5633df787bd7d84ffb7f08a863ddcc6a26905f1af85baa3f2937a73d35e015c0e82835e3f57a605b674f9c6071c6686598e2a286defa4c2a4bb13a19f0b2dab672d4f59e6fe9064f9acf8c628d8394b60e949ceeecb7d463375872a2cea1345387e4de42228408b44848d524096e5d3718aa70d7750f669088a9f8dfd5483babb7ffc72455154e491a99fcc41bdd65ce64a7d46a374e80c8176cb6cdff039636094198d61b0a837e127ce0f0b9ec96b3287eb81ed7ede45a3c111e95c838c218a94bd1dc88d646fce58950bc3e16068ca14ca4a204ad0274f9d131c030500c80abcd8d85bc2dc351a1bf38754e52f8cf18a68559ae0639c28f64a57d9071948905a34a5b78f9f743aa7a6333d6e2611939ae99c80f5f9a261af80bb5341db8211f588f496fefdcd4a1d05bab0df92f9f3f7153433c1e9dcc5dc18b3fa137d5f456268b821f4d87a87148e521078cfacaaa120077d1568b4baf9bd8df73795214a8aa0e088d1415c7042787dcceedede402a58a7d8bc2a219f69ae5dc65b3dfd8d8f9ebb109392b41b37282318b1fd23bf00ed9f00440bc402c2b0c09a28015d8ac37ea19ab059066e4d0ee41cec301e2fc7291324071b4e0efdb4ca2322634da7ed7d83f0fb8ec7a51641ffd26224e6c3d5d8b5e6191d673a0164d29f40ac0f6f4e88523fa01a2a72a86e5936c9fb72510f78ad6707c3ca40332327fb4fdda8a604ef05b5c9412bc0310aa29ab7a08ee36c748a889935fdb322aae24cfd5c51ead43c2f1be69222b14cf9820ef05449dc16bc4777c57ec429e199dc577d96ecfdf07a77f64d9b1580faf06a652d253274267951b0da4571f3036c36b712339e2fc1e3df5acb84dab2c192e2fe3dbb7b1b3937ba3dbc55d2992985d0bfb3ec913c1a6e8b12ed533a7cca0d99359debf3ceb15568a5e328c373ac711632101660f2ee30ca85f0f868303ae1504c3fce0a7a5fb9c9e68b2a21d68effe9c8ff62853a03c4c55d6d973e087183f9b8c62996c3b2a4259dcc369e9e8a12f2d7cae4da6e942c410461a3ef98d9442c6917165ff3a1fd6bf8472bd47baa7f304ce2e2a6e371414a5f17a4f361e94d321df9341c608842e840150cd1d6c11ae9a37e1f66d420f4abb8cf71a02891750e7d4dbbfd8155a14a5f22ce73f484db9d358ae7f06607a6ed803905e151cfbc1d80d19a806ec33bdbfdcf4eeacf1cefa72872c3b84166dd5be46dc896df3a7115377fb82dca865c963858bd441f5709e68fbc6862ced4715057af9378ca4435b1b29c9e33b7fd82dc3cbe8c987453aed3af605d14bc293df7b5ea123ffedf680d3be3be41ea2bc914717fa27550ffbf7b3444d64bab2d845b5bff1f401e9f907b71bf3f909c0a4b60f5d5ab015d7fc349175e340459cf63c4ef847f8ab441dab73ade4132e7c339ca341c074012518045d136f38f11184ac63e790dfd2bddaa3cda3cb4f9e8a1df5de468a131c9fcc3d03a3d7e8026f7a0b4906f4729f9b9c6fa25ab810473cfc9e2ff392080e013e1facd0e4f8f5e82c4d4cd0abb90f808d36a16bd30e0cf23df007e1ea6cf269f5e72e0a5b94f13085966cd4084050f1e999ffc990e4ab91780b3abac79c58f8298a3275b2b3bcca46e1f9084a1519d5cebf9be6f4e013595c92a4b3455aa1358a5f7221881d9bf23194e79941a6f0be822aaa568302bac5eb038a693b9391850a8a611b68fb66feba7626633ee52538fbb5b7dacdcd595803d6af1d25e632f7f618407e7cbf0e110d643f5c283a9f64d68a22f0028db68046db1906548fb178ebb7874987f3b4ef0e4f4e9be6ae1870dbde65553d6a149e0e2f8b098fe881046b8683137965868466f8be7e06ea64f3c259c692d940079fc6f8ec77fe7e9b7efd037bb9e6b668c90cb71ef0e36b9faddb73e2aa1374673d754d28c251415d9ff5270f05b8950a8a26a1551b3000c70661d8067f0f080364faf78b86313c26b205134b1af772f4c1e57d75c31f757b675eeb61d3d2bef19579d1b8fb949bbf93f8b66ba9f71034a3a24c5d2a0f3eeb59dba94fc26c377fe72c0a2442425492fd58a6dd9f92ac7f3a6aa9c43b3b3038c2f451740d1fad637e34ee7698e75071e61147cd14353e62b969a838442a1b8dad22c1a01af77cedb2f8d23882cb2e568175c93df2c54bc4bc4c72e3498d83165804a4b9871ad7bcd955ee1c895d30cab622798530b656d53dfea4c0e73a921b038ec0ed649dfc6ca4d0347f891abe80c1a9ab4c2b6e7ea32325fb67f5831f0097dbab668679a47a414bd6198164a289dd1f7a3d8154b1753b6d34355ef9bcb4f1b748871aa2ab08d587fc3f270f5a421aff02c1539813a575f6c78559a1d4be952396e5124d8d5977d968981c0f37a350a33d3edddf237623109089119ea975a6302f11c7e5bfdfa6cbbd537da2feb00b534896f6bd8b6e055f0e79596ce419a57b6d2cb483196024a2a57b0d278cbc9cf4133b5dabe328a2c69d2b6910cc8dcc8b05d6333b03ceec4952ba525cc4861021ffc01dec085e3d8c7972bba4a149b9a0cc2f748accc94681142eb8b3ae9b16458f7f4f75a935c59f7517481ff113793a0a7ae3a3a3a1990981d32d2c00362df57c874e2fc455d8b004014fe84a6543020ad0dd5aba0bf1cb53a906acb85f24873414ac55d3bfa23af1520d1f0f656387375b6bddd908456cb791bc155db1fa79a93e8a9cd115aef69afeeb95fa47d04b40eee14bf956a24b88906383d31c2d306dfb819d8610723fc86fd1c86493bd21258483d590f328529fdcbf824b6fb4e12202d2e46dd7980ce2fc2f5e2454c141ef5ad46c8f281034378807f16040fe1000b0ce2f0782e6c0eed6974914f1d4ee1e49739365e71529dc6cbb25f96ee138abde932fdc3f23763aa3f7e6bb7ce9e8b8d7f5027a93f37a43162cd5dcd9e9bbe487f19dfe414c84a68b181b5acbedfb095e5c146f5c9f6abaef914991ed7f6140adcae0b0fd2ad8a1c40a65ffdce2cb565e2a07eb6a7a7d677b16a28d5ccb81d036779ac7f3f19429726f1f1a6ac3c5fafa17aa7cb9053eed27473b72206a29a69340360b135cbd70d7a9d41b9fe87039b50f083f522acc6cc3a51fc90b9449ee99186147f915bb41124a4a4abce4d71e685c335363f6f53a0781b10f3904c4e201f75e84efba7560507393dce678bd7aa5a6a9994d8ecce49e7bf4adcdfa2609f20325592f9c3c98dbd0db3600f979086e2865522eb65bef9647d882e5a8095f0c81af1a66ebe54193e6151186a755d216c261224d6aaba7a821a2678e9eb0b45e71db57aeaa7d76d5e4cf1f2b473df7610802bef805bd7b0124883577f554388572f21ac6a7fa7bd93bbbf791d0d91377abde3d83034c0d088ee8b816b28ea163e5dd1d10c3260bb17f5771ea3c9642c90ad0ebeb47a456d686cbb119b5d8729cde7500ef565cdd6c777faa22e09fe3efc1dd36d6faffdd99669d2309fa40a89e4efb2b8ec9311993a6b361ad177f8df4cfd3426250918ea337aaee90b3a37eff89ba6c38fbc4b183fdb16ea5d391d24793507692934193d981d9afdff8ba85989d5ba0d491330c7f8c2bb8ec502bb9e7a847a3fa5ab05a244271bce6c5d5930f8da6b47a169a8c81cd2de14a69bfe07b5b71db67770b4902368d4780b84792a7c1cb18446439715cd61d19edfd1464ca65f600958e997eb39432479ee5beeaf54534dd947ba30723bb19a842ad66bc9b18cc5fec4378eb8e9acc4c4d2cf25ece88ffba5fe8a735ebc3eb8a19bde796fe027692ca34bec7cfea22f4f709f59020f33c415c13a267b3b5a78a8b5cd93d65f4b3e1df63a14b90d3212d9962cf93673fed0443b4c2649c5001cddfc63c2401d2faa298e64e4b58d0a0396011b5c354b6beca17504816e4baf4df51f7e30240101f8d58cfb5f4b7026b7ef1653cb9bb2d8557f32b93b4d606ac8dab5216ca7168927b5178188c17181ae2d79abe44916632124e78a6922d5ea83764e0eaa7d06c866133960b77018d9bb9c5abc104876829bdb3d09b8f967d9e9d87859bb2f2481a79f13998db6461377d4134bf851df5c56642b892d94d6cb2ba9dbdf72bf602ce6940213704e6dbf69a5d7dbb469a9a387ebb3ebdf4cb97c99e5854a7f1d746cd8bfce3bcf9d7a6f039ee6a9a3ffa7a51561fd07441be4ae99fc598a56ea0531569ae99cb337314cf65204aafb8bd5196b2781851ad0f7d6cc29384a3f86a7f9e0f7ec1bdda46fc98455d259807c3c58e5eb9ab8160da2d74ba4b871dde4a48a4ae6b69d16373ae170788092382caa034f866fc7c5e4b93df020a5872499561af78ed96136136d8ee7b9687c9a993c4fba81894545b992ddf0cdcb56aee3bb52c8dffeefa2a33674e89fd4ba28b194e1ae62ae00b5e5e2d230cf358334dd60fba3f0dcee4a99c94eb9df92556d32c7bb14e2140e02d1fbc42f53034d48ea0789e883c060d2147312e187ea48d62fa3ab73fdc5cfb65a2c909f3e79cc32995c7e4549cf7d3f61f225457b2e437e4adcedb776a2c6515a49d3d02662d8aafdf5bc2a5a3f714c79f3c8c9eae12a08b3f7e964864213305c2cc702a6fbd1e785fb17e0553836462a97ca87dd627b7df838e8644ccd216cc24861940527f8b331cc7b00c9eb2cdfb70a4804df996be51bedca04268736f9603e9a57b1661f833ec692677abd3d533ab43f740a6fe43793101e6e0e69dd7b62ab9dd27f05d05bf565e22a7f12328bb5b3aaef27074aa7b3750be111c008a11b4b4aebabb753c948c568e74e9148514bf49e726ccef8699d964973a625713ff19f2edb54ae677dfafdaf65cc334f1a7afb327ad23104ca3abf294eb8739553d46d525fd376004834b2751690b75443625dbe1752e7115ee17a7b3e60119f4cb46b3caf514691377740ac11530143ebbe8c8cddcc6ba7d1832d49588513446d86634721abaddcab16136bcd7b36c29095161b344e009adb806c3e87837340ab36481ec5ffb618a33b591bd792b5a7473498a08e97add3f1f10bb807c781b54972f64f666a68f5f8c4e8e68ffb46299f0f46b29644fd4020001a19ece234d2cd20be7533059fa7118ba9ba4178364a90b7bf8b18500e083f7a1b24fdcadab487dd7426bf626c9bc865429f4fe1da8c799dbd4ba4f71bffdf492529d95b895e64987e872c7305d610a1bab456cd1390788cb007235077875c44ac528b32254bd34328fa7cff87bd5bdda8124b79c3205aa4a631afc86fde1d085d1aa66092251e6bbf653655da1a7d64503a6ae234ecfd8d4b2590e3c8b32cc116e7ad41865e50f9cd662542ed2d19b3d80c6402fc59fc67d0b0b942b95888e15fd0befbf5f778ac72c679279c40721cb0937bfd30cb9c7e51311f8d8248dbaab4fe785e474eb0ced0a7ec776a8aba0f44e6cf28540be33e2b1da2277e8af350a8b72222e6327ae617f21edff032a34fba53320e33ca8938037745d11b9a06e48dbb314c0a984ac69d60ceaee55f4d58bcb75114c5e31d98eab7e830628bcb0b0acc54b96e7aa96bd48f581c92f36a875d3d09ad476ebff193a2bb409f0c46d0b2118aa734dc465b3273d553a8a8017632de0838bc35333ebec23d2d5a823ddb9df33c83d32ce0561cb7c04431915f68ccc5593519af003e8fdacb804ad33d643f3b266c8adad797a0ed6cd95b79fb4272b1f36ec4f0e9307243472ca7ca888617ba4ae52cf2d54ca80fc370799170beb34736a27ed661014b535a187d5869c2d354ece84883313129b2a595e122815691c6b90dd2cb12410a135f1ed6756f148d9eaf5cbbc7fa25f742a7fa37f9a6f9fda7434962aea7f1493136edef6a16df942b1cc1c6500b3670a7de1d11d178d1f778b14f7b18693d1c2e653b0f14dfafb11f618caee9cc76efd9332bccfb6328574f3caa7e0b5ed39151233b7ff6f62f77bed019a6d25f6b76ab47337c9786521e7f08ee9bb0180bc2d96e19e2ba54c4572987dae9418fa74609d5b9fbf662b48a7f45d3f7a8da431f143980a56006a3643023f30c83f20961520ef915167a6af8923f8ccf3796df78376ad4f7fee32536f8e5d197c6a713c77eda272626a4184bfa5425a6c0346d6766debe6949ec8f79bdece129e70c63199841526ec50298ae7221b2fa56c779d18a9b3c51f4db9fd5a361c10fd3e3282d6b40635a3a6915aef1a15a24914bef32ea07439024a829a6d6cbc60516c76b1938a2888d61775ba2c7d51cbad750698725be431d99feca91f7f6929d2286bd7e0dcf61bf2c61f58f005cae6cabb9807748613930dfd75c29ebfb2f2a25d4ec02213e073c4a3e8f0ce5793282fff11db0c64421eab5970f0b28922c7973a7e64114b9959ed7e1aed8b0d2732aa99280335559c1ef6f5aed3fcd148bb4a4fe2dfd301baf5aabcd4e8b690bb0fc5b31f9d31a82f9345bd33789ff77dd8007011508b2beef04877f36ef2cb02d1f7eacd169d8767d9fe670084e49f2aeac61f5e85eeaba5458dbd3b0f45ce917c5387b69f1ae955e4b5d7c28d875169e6d76c0da427a010dd611ba361184cb7b0d065e4b110ccd26514eaafac21195647c490df7e71c9515c00e0286d42f19095e01e7dd8c727dcdda7cd7a99d833e2005a9593f27ecd3ce06cb500fce4e072598af9d7de43e861f56d5efed9406e92a8279bbb5f8c18dab965bfbf0c6b9680eed2cbee1360fc6701a64ef6ef3cb0bf43cf33d06b5d072b02905d8f30f6a1f469edb5936eb2b2d7007ce65a3086e9887c8df2320882f18ecc22b44e833bf6dbcf2b005dc27fbf3be15dc866ea24be76faa946b7d8422733f7956601958da769f7750355afd4e7ac8fe1e8ea77ea8ff75d8964bf0cfb26063f9f7d2e5d1166174914abbd3518427ea73d211b88ff55b47b148e84c36c02d57ed89e1db1d59154a035a51bae415ceba55847b8bc4491270bcd44f93512b5418731fb566b3d5537f9d8fd51b384c24699ed8091d3d0052c1ffa4151fecfef7e7062844e71e36fc4551f4d0358de97096b1176a245fdef29c5aa0ea48cac9bbb5aebead394422f1904fa84ffbabf0f5e06a077e99579ec357f7c638b1c221ea47c103a8fd588ca4d3254e9ed61799d93252a0e4b03f934ed752ec301b7c858095fdd5921dc6e6ddc1f5b8fece76c2b481dad58b0892cc11b0a5591a93c8cb781689baa446e8e04227dd226c5aa5b148dd919c6997f5d95aad2c66906bae2b410bc331279e95a8fff95e8a5e45c46e9bf2deefa35d4d93ef22ad5d7fed9a6bdd5f631d554321262b890bbb7f1a380b6c2015cb0dfa78ad88d837c5d3571de4c736573fa909e703ae7366e73176812d7ee2b8cee3999384fb2d0d630e3a2ef4b2e1cd3a4fd41fe62776e841ac0f1afa4734c32677beeb10a0249dfbd20e3bf212fad89329a21578e608563032532561e2582f88a38216e7d6b464d1256afcc41490293b41535d36fcbc69a21e07f355efc406a09c856c9241eebdcdb0cb66bfc9a7d1683e1de90b5c67eff47b82da237c8f065277cf42b5426cbd35ec528aaee3cde03ed2bb8ca53bc31d644a7380d974c44bbae9d513487d22ed3aa6a2bc0889e021fd75c3860e294cf3ec54fc6d3f3ee2455772379acba24b62fb2019556dfe55a51ece24190607f5d0c1119f9bb8faeae185b1f96e59ee59af2a930e229005a1f85ae0c908f39d7fe9c5387ab7e678865bcca87ce21db25474f28eaa0f76971dbc24c80e54d7d9a039edcb282894755d20aedafc6b3b22b3df14fd6bb5807b68711ffa74bc314bca7116bd1f39f5eafc2e8186c506beeb54bf34c71537d2405c55b668d7d2b952801942b7efafbba755f4c75ddf59b674687b0431201477ed28da36943ea1a395c27118d1039ac37d83d605d7c35a93fd55909a4607de2eee36c38d9e76871ff2cd6a967885851ac97ce9a14c7fdd49a7b50c34ba7d61e84847d31bec6fbc596c4c9ff020ae463923857435fef6ca240054d7e2f2756609ec040404a5b8c870270ba124c448a618cf5f69f967e3d040b6b7bbd4f2cabb30f2f7fdce9c60c9f9a7510a2f406af701ddd18cc7607e2b1e2be4896b0384ff7c34ddc675d2b1564a80ec88c67065ec0b2839d2c85668d8c38964476f9cda4e330eb1bab63baa2e0646a52f615cb96ec35922a7a5f0e0913bae159bccd2dbc079500dfca72a347d82e0bb060bdd67a23b4b8005840e9a72d6ca67e295a588cd0c57d39a89bce38313e534832c67303bd7f7736e97aab2ba614579b58ef7e7616ca1895ba32a682537db88cc0b599e00f761245d27e144ec87982e6b23eb6207c9fb160e5d626254ab7a9e7f96b110e3de5761bcc5e4d5c297944eacb2f4f2dc7ff3f749125ba77e72288556d3d28f2e3dc94f3b39f8111af7c5ee47ac3cd7875b1f4378ce9a7f7ccf930f489f91c7ffc1f4dd3803d72f089227cc9ae9bab2b742ff72564a0bfe75ec8dbde127e15b3b4cd0e58766df4c2f17417bf8b5884b966591a353aef3297e9523e49f69aa3520484130068cb6bb1db22c942360ea371cc73e6efa8bd14d782202b14dd819ececd62e91955517a2db8c5a2b2d9dc33b1797758f91d8100f671c98b02217556f8f62494406c66902dd29b6334f2051f8e2423c581f509c99f3d4fa233a7836e7863941d5bdbca9c007199a05bbd7656d835d9eec86f459d27146daec079cda571e058b9de098b6bb7832f1dac0f3c0ea08ad10f03ffcfd6f2ceb5e82e63a12fae46b347b3946bc620cd847253432dffe5a186f09e0b8620fece37e6f4e2167f729d49a95e60fb2ffbc3a1791112fb989210eb5a605a5bce0725c9a257653f2daf6df7567af628d83778633ee6f9901bd42ca5fde659cd9c22a7b842ab9b19903524558a5b6fb9280751ff9e65cf8adbd32c4df6b2db3f04e93595f3da6a5273ad73c11f0f3732af7952a6264523179b3ada8e0b7e1ac797584ab13e47a744aaa817fff42d15481fedf1172dfc970826665c79cd326da4dfdf46abe77965c56b23fb6f34ca7e036db1b61f9cf2272f1358fc401d7098b3de439fb3f0f5bb966ae4141df2f66d126c6c74af282ba9a99b90d25948514179d9d374455f6e02118e7029bf7f296df3ba1dc7607c996e9e9979910066d13087d4c6d21a6d87122fd98f89ffe64684fa6756c842bd166d3f1dbbb929d3ff75c2b095a57130e576c5abd560643ee345aa33ba506e2c5a17eca611f3ccf3ce149cad9bae5a3daed7397b1535ac33e154122f71a1c9cd9533f425304be36e0ba689a0ce9367f431b3cf14b22fccfc36a2193e52df6ff9986da2eb0ce8eee1235ab6776368c14af3cb937e066183d923c8b05b53b554ccccab83a139ffc8b802121903473a628bb10cf30ec8c9482ef4a6d621657768347252872cbecde57e5460dd4c4be4ee2cb67d995b5f600ad41a8481050ffe19450fba90766c4c2b15dd80f2e5cdbdd5f7545bfbf2792ee84006dc7249625f0376007d2e7de7bf09d6e69be4713d40f83f12eb340df1e05e1af74201a1018320bdcfce6ed3daa815bc88fa3fedcc37f2c318840cde86cf7f13beb8f9eae6e8dd35b31822aee9b0a1c99b5f63197ce326b5f7ce3792d2e887dabbf5ff232bd17d1057fb883b9b90973cffaf2177a062533192b31d516f016a4f866d780b0a68daee0a5ffa27a24dbe00be9c9c29213e0ac2be3b44729a79058c2ff0c91a30c7de25206c8a031bb0aeb9b65975e70aeeb6f8c6dadff20637f0a055e48581de2c78d51360d2432ea4875222bbbc047067f553f2edf3b7a4a91ff440647319cc1b74baaa11ca4d1fb348e49f1a7a1a05b08dc765b1680e939d88a6dd5c7de64bf914d6867ddd329f528b1b131cfbfbbca421da03930e71b41d6b59afafb9af104312f9247a47540dc6f68e63df0ab37fbee1b9c55b72aa894cd905d21c9ffca567ef8088d2ce99903eccb21dc6103304b8267d019630ded08f68568d30992f40047a8809b87fc9541c18476624571be8c320d1b3416382dde7d4f80a627628f96f0fc2f331d16491921fc0fc9c4148f92ce3c56121fdb4ab44c3a9644fa3b427e15fc994712f93529555b6f5ebbaf8786f4c6a223bb747826a5837a35bee180079c3cc6c357ad7e0da88d18effb060f2fecaecc5d1e5cc94a1ce1bebfc447b82a7abca73ddbcc5d04f730eb142be7596b29ab3aadf5bd9c69d169c977abcd65871a6dd396aa5c47d0b3eb799a9453a41a30e778f1aaf724b8629dda313237683c38b9fda5830a0b0e93b596631fed8784d11e3ceee9178a0c301aa2e702bee46cdee5e4a5374a0df51bc4dcfe4a9e162ff4797b5f6dfb7e5377835d3c79ff292270b1d2ae5395175b9f1ab4af542bac9d8d72707501e29769915dfa403aea93a08f663bd14bc4c19ff3bcd775c58188e9b28cd869f5a5f578ee3c2bd9f8e715370bcc7b52d07fa1eed953622d2ada9475630e5a5bbd7ce30fa6ed3d38b393784e2ed0895209d43146bae9aed1ef3d3b35a8fb0e0395d91df0de4de096d8e686700043fababee712a7032fa4fb4395d1e4c57e2150158652790428cea9e935289fa7250377f83574b41e1bd132f0848d1a927fa332b2daf2273430e5b696c753c98f985b5821af7dcf10384cade783632653e8e13abb840d709274c6a20171a312e50076d086826b0bdc966f48662e4d1e4766601a387c2df2948461f8e7f6cf5ba33a5d861394c5439e9d6317d83021cd9a763810080287032f181c3b7057a615362d327cb572ea638aa3df0e32c080cac3e7653498dc82eccc34e13758694d8c9fc1bc622125e27c51b37e7e109a3c4ba1b1ba74638cd118ea20f62a8b74ce7892746bb4ac3fafe2657a8dd0af8e890ca0d0ac559b26b7497d81273860ba805a36dfc7e15d2a29a5c6f37b83368855c429c4d715675324575ba52fda78afe65f5b1fac14eee77b179a2f9ef6e5dc579a97291c9199c64c98cd4fd39e8e6b5a0aea26650df08048670b07ff271eecce93453af842efd94c74b75eaa135a768b00a6251537607f5c7a7320ab0a9b2c6cf577ac2268c98de9b92df8acd26bc3a6ea0751d0f7b0bc3c3966eaa89fbc2a2f61e201d64e822b53d673d64a97106fe07c09046156a1fcfd1aa4bf698167144b08dfab99c3b749d00d72183c6f0fc6ff67adfdcc77bda79edcf0469c628ca548d637dbb5921a765ec287a98a8bd6a569f77c2da7858537baf189784e7501809f977d06fc9889628bd62350bf29a63b9a719e60ebc0cb16fade67fd77c135724596146d68c47865d9a3679886833788f491e0aa3dadde8ba12328cb26cb056be12dddc6080e785e3caa9c58fadbd46da9c5e31cd675dba6b0b154ade51d07d9b3be5682909e23884d081ef3a1966e7ce20a3dc95b6d797a45a0f7e0f38ecd8d2dc3b22f42dfa19cf91fa102e4537e9e0deb01d9f0357afd0892f5bc1385b44fe0f55cfa40a9407f973a4691a5e381ec5b18f252259fafbc6fc8628f350c379c5e6902e9ca5badb132a423828a856f089cf297e6f802482d6df4c5acae412e8fada8ce887934b76bfa01e2b8f0d774d6c1626a67465df1a0f969c2308ecf391a4dfbce7f7416ced63313e71bfad840ddbf9a28c7aad065edf112f22b489f34550262746269ecc74f6c30e9c0e067420c89484f4606374f697765acef952af00254dbee34abf9c7284816d78ed7eeeba3e9399cb40a9d893cfbdc33b3d1c844c0557d7a34d4989c87ba0baf53531f8660baf1af70beb9fdc123c58bddb63b23ce8e9701eb0f017969cdf2b1637704415c9f767d1b6ed655f95744af470a8fad5b1ad5526412588b0c8bfc7d4c00aee09471ee842389f2c4d7a74f36ed75e47d96ce9da0e199c85bbede2cb0fef654a169df1487dc80a605f7f123422b741fd131bea0a199ecf164a05baacc1f93fcd183ffa5731cf439d0c5979b9c2df372e214e2d04cd13664905d2538843334faa0cceac1ff40779320c8cb5ea8c8c6ddf764580667c3884960071b3344d724abdc12e0cd26e0103155beebe8b1f5375fbd11049877ad8327fc73cd668eae3d484d27010b2f19084d4f6c0a406b003ea08205f6ff33324b8e7d006be38265402fb6482c1e3d44371cf41d2b5f1eba41da0252a935ef5980da7554cfc483f8556d440667da3938ff907473fa3a0ea46200de8def1352be202bfa413eefb198f2e5717352b997eca279c9e78f58f615ce68fd46f1571ad191de00d29c3ee0cd5885b67af7eec3698be227d0d9281fdf4e436c3c3d6d17da0aecf926dadc931eacd95167482745ef2668629d57ab07d6ea743cc02c38ab68ffc4a1b4928df38a6f3a4b29437afcbb8419953ddbd6294507717b7bd5585d4942e73144fd8f6e5eaa52ca71e6a4e4cde7b8668ef87715a20aa928b48947a9a7dea791fb37f7d7570ce26de2801cb1325f9041a93ada9444d8ff349eedfccf79c1d185c71fd4a0fd2d0e43bee6606dbdc00836329af86ecdeba9be439bf78eb4f60a8af109b4a8e640ee25e3013adc518db11d5f03a345d2fbf198cd248792ac7bf6a2f015b29e167791316958d52c52c0eca55940ee32fbdf40f3c34a55716ca2c37fc764de010821e15fc62c507815690cf1b2f65eab5c5ae9c0f5f243b93ceceb3ab13db4491e657336389bdd3c06e9dbc7d70af7cb754445c99582728eae994e8e944adf4bbc1d9dc36927878b15b2bb158208c2bb99320a237583fb216fc7585e29b48b84869cabd934b7653011602c770a70ba15d6d9d8bf38dd883633941a4f5a3e5540a692f84bc4f2fc22f5139bc4fe28b382f74d0685dbde877448e8f3f07a134c6e9635772454cade2d91ad75a4313d7359eaf0c9e60af5d0606237fa02dd7dcdc71fbeca3091dba16d15f3a1527add2fcd09dc17dfbd93071492c86e5e48d5dcf25a081d053518b6bc123bf1a7f6dbae6da70934dcdda7a33e588e48b391dd11bc989de1a5475e73bf09630c417be46e632df5c22b339b2fb1d48a3d2043e2bce4cb71e02ac8e27c413182076ac05bfb01cab1416ba4c20fd707112d79aa97fda8ccbe0eb5be37c348c221f54b0da40ec2f1cbae092dc898075d6d572945d8dc343ff1ceab467bbd1c46ec191759d17f7a96ccfc0fc267dd2d0b1aea3882bae0915afc65f3d4dd8d879e257eb4e6b0bbebabb48b4b9b529e7d66f9b8ed6d766f01dcfd9dce1a6819011133e038b2ae33a5f8470cc8fc2126a8836d3e79fa4595674b5897aad1247eae5cb9c204cb364e60afd875546018bdc413103ce5f9809b7e4f5e576c438aadb0e8feffc405a9409557b369d5c46a86eff1a7a3e30d1e38d13156d7a93bee195a43e0578560b9779b1f1b2e2b16b0bd6beeec600ff4174fc3899d1221fdb0b186326744a9adf6ed99c1c4c3dd240322e76d4997aec461ffc4bdf5ac89cb5ef45af2a76410b07b727d64908e39f9df53f7a2a8f257d5f63586e6b72cda95bf146e509ea44facd829c5d1ad8cff32fef97cafe62ad5af99bd0d8afd9d2dd3620d0e86cb7342e643c721325cd7fd64bcb657ce7cc1c3aeb284d71387efadc19112670ba93781243ba0d9c3885b5dfdc4d388690faa19bca7e6a1e20229c47472759e84c7ab569e36715c2bba3a546f00c37d7a8019c36b79e63bd8ccff0587c46c5e46ff51879ec2d8d3a6834178a0fc9b94f8d60d8451c1780f36742d1b18bf47acf05a1515a245c95185f032d2c340d9f69495b02631340fda0f2f3a337b18401aeaf0e1841790155fd7ab7ba6e6b376757d338aed038f80f2750a2a9dc9e9d85f03cfc6cfc8deb81d698ba90349da3647d2f467bebccc59d35cedfe20b6385d3580165f114ca41591ff0c567c2b7ac8fc9bcb0dfd9c203c0535520d8a1dbf37cfa3e5c6262845810ba0eb08f0a9211c9fa0fa95128957623bdb9ffdf3e95ab2da5bb2e2c78a43a0fd355ec08a04da0f83a4621902f4d1b42a2d7d30b9aab9ede4bbfc702bb50a3e4f4d718b040e875cb43eab35357b8499fd2d5f3bfb0ca97e9c012a1ac576d9b012d3e6d763155b339e5153f89930c02c8b903e01eec4d39aaf42d9841a9c3992e2c37761d4d8ddf939d02597912e6ba15655b6fcfc8392645ac8f5760028efabf0eb337a3d5d0434ebe4f7e120521730aa8b39edee553861d4a245b0e21d7aa01024d3c27799012a2dafe5f2ec2a686ba8d8757c7c6960046c6ebc2e86d2561e6d32fed1dbc59a889ec6e1a2d94bfab0143d8a4871657f06fe7a2f420669667aa58d7f2a882ce371c72b1fd6b8faf89e15f32fbaa66c08843df628a0a48cece9a7dffff8d58d0e592073e5f50fcb9e352a71c580a6b60e41521f46b19726003c6ee6f198fe11c6eb04e9950ab20c2ae9215e3a2324d1cf1b178efd73623588f9293bb386aec5a954c90be6db6ece6186ff1815621eddae77773d0f1ee6afa8cf71d80aac779ccdcf8e2efc41812990ee43b330914346ef0ff1c332a0dffcdd8fa60a57db192798384cf54f8fc274cc578ab817ec573c91234814f5df4e39227a69fc8b007c4efb533a7f8a14dc7465d9f891b3ef496e1b485b9851cb57e7c7d79fcfe74bdf7d6ad8d0b664d18f4ef0dfe9920bad285774a5b96f2ee71926f6334329c67e83da10d49bb4d96bdbe9b08bcef57a9ea4778478c9830f58b3c5c4c485771a8e7d847f4785ff886124c327dd45fe09a13820799d7d8bef979903f9377134c2c93159d690bb1bdb45d9a5735d76ba160443adc84b9db78629f8e45541e18532d6a8b38b26f3543761b35320937d7defe6e4bac32d40529fcbe4b4ca9774e6121be99696f714b21b0f0765e7486206a87e943cf94fbe3e8820b49b0f94f0c87ecbf2961502461a33354e389257efc02d6bb0ce304d83ad2ea640fe655287eafa407e1ddbbabaf3fb745b21ef5073202995f42f313b38e02aeabdb8343d077f789252dece0a5b18838407870e9af4674c2573fc872559970362596ffecd85f760ae5409460b644e029e7426e5f5dcac1fd90d866700d7724bdd4935955c0eeb64b0b2cc6fe86ba48c7a59ff74303efefd5b2c492f0f96a6b034251f24208ccf275aecd7d74dd816762d5bb75bb0936d3dad89dac7d7d4c6bbc113cf1374ae29df1ef18b13356465de3a1f5a736ff1fec9eca9b679b3705a1fab096196e42a1d2023ea409e30cd3cf404593ca113e3d91dc8bfa80fa6a6be766e3e0d9065d935bc2aaae3e64e068a89bf8bb75d93508b817ac16b11b18e0750f1b5aadf216eb59f478ac0ed064d1a3c989b5cb97a88b87dc94f6d562c28fb2a9b9a62d3fca710da070e860cecaced686ed04f14bc00216997176934775085fb3c7c1a78d8e1bc53ff63e6344e4aeae3828b2f7073ffccb1eee7648a3b4542b47188a1c8befebc95da6aefa1bbf70a7ff38c7fff5dd85364036a502a42a5c3f191bdc8c64cba723d82becdd7ed280498aca530e57b61ff8c38ddd9a42910a7518be5f660c2ccc7cfa859150e1da33f0e51690b824498dd25a8125e3337dfaba1a4a21e569e241b04a10971ee3e6304cd5205defba7ed219fbce2f2b5e2bac14d7b183d50a9ca210d2259db3d1087e35a6f85240362a15779a6850ab2ee501d1f0b512aca7abfd1de7942494350987459560c5640180f520fedac6bd5f7375b1630ac692dab7c686d14f41fc858373ccb16c0cad7f3339f39cc42565d0926784bc38231c2612f5e23fe62d7f86b74fe657405b3a7427cf55e4330a0d7f6b5fb4d12fc87a9f9b6c864dac8a9b128906ae29a3cf1950e6e717fcf2815c5facb1819ca2b8ce4ea15edc5da1970ea19424233c2fc99f35c786dd4e7866a7c08f52f5234baef1ee273ff2eec55e92cadd86e757e514a800b4d85a393818c0e01f61760ca0e1bdf492379a5e8ea9211ed193ea5c7f35eea08253bd561baf4cc801c87b45f9496ab2392da7537ad5deaefa51ab1d3f2eac31720580fb01a4aa74dc8f92c2cd1a73a123bd0d16bfd015f0092c7ba4cffb663f34c308b73d93a4f9e1adea053c902240de41600d5bc9a10846124ff67a3e6c4bba2a5111756338f03e216a79854b5a53d56755dd0871b1fd69f5186b353ff74481172223748d8f8b24d268ad56cf114342f6488e7bdde43e165f3d4bf7b0cd91937681ea96c562bbd7f9fb73fa95b2af77203aecbfb619bec6502fdef641346bea44402ed9887ed0ec49905d31e46cfa17fe0ef862c5ca5093b5bd17c988e7ce6d56281fc9a9698eaca1f2d7508050cb1284a8a815462c87af9dd5b98f886b8142c124b7cfbcaac2c094c5f22282a60c4e21f68a3605d0110fc6b53dc90553f84e371047cae32f45a0d94d2658c886cec3e20303663c59171cb19aa9d049adcbbf2caef17ee62de1d44930d9eebd5b4643eaaa160de4b04cdd911686cadec10966659049f801373b1789647aeb21664c448299ae83754ac5e67e2ffa878ff25bcf6ab5f353a5a5f5fd515c56e3237bd5d3231772173dce562df4232518b4e74f42d72dab2388d92e176e8206c73b25dc1cdf3588948aeecba128321d314ac152b822b2000834a6d8effc333c8611a55f7ecc25ec44626d107f0fb30a6e919d7c64b416583a2723ec5f8a3ebd9eff7bbed15584b328ecfefcb6581921c982d52948bbcc548205a7ba0bc7dec86b1b9195bdb2ec5571aecca87fc71f796034a80145e9b21a3348e16693ae3cc2ce8a7b024cb78e7accd9fb36124c8f3787241cc7da1d26b9bac5a868c7d4ca9598e8f896fbb6fbd801bfd371957da231bf12dd8e3e117e109fc57a40ed7850ca456debec0420d3e06c0bc00cdc81d6b13845ded701e1bb6d66ce149472f8e902ba92630a58797a733b49b97f25050f6eeb6a4f15ef8e608f89678be98e9b63f1fb96fefe5e56802151281072d82f9852d082e266b83a2d510ef020f3e8ab17915058c7b7f3522aafca3c791204c4f1b98ae497a765061251cde25ccb330a2765513ffff5e7179ead653267bce33c7ec29cc4643833bfa26fc8be17c2bcc9f1c3d706627a222523dcb76ea2648d10239673f774c2738da61bcb358a3094d110f4ad68935df593d0443c8d7d4535620b5b65e855e70be9a2e1f58f0902560c7ff5a6b65d34efb9ce247d8f6e7912a42c035fe69ed9e74418764a18fc18c3ad97621c092a09ebfb48736cdf93f9b228aef426a5f6ad7c20fab68bfcf932790e985238004c86dfd84bc789f49f1fc1374b1750902d5632635bcd5595c433b68d51a0ba33fa45f98424e34a630855bb65b8c82711884ddc3d7a7eaaef1cdf76f8e4477153e29f13b29d4661d32004bf567bfa76c53f6856a1b3ed2b85bacf368b99b4505206963e157ff3edfe51763408c5cf57111fddf8fb4edc2f89dfcace436e690436d1f2f55f6a9ca5db2da3a2af3f810e35c22c1f43d310e4ef0d1bad412d7bf6bdac18624363c95f62708b46c41ed702b90c4b0a1f6dc164a13a6e3dc24fbdbef7ab88947ab27ecd8e9e4d1ba4976333bb122533e4ff494f798a455db73fe60c445e7c97b8b56cf00064f5a8f6f2d1ea5b1a0c935184d4a6b64f8c3de3748d8b4d70613e0ce4f8996ca411254d9e9bf7a62741bc957d7109dea79100e84e1b10be35aa5e91e83fa4c6bb1663491935026cf89818e869b5c35af8fdd912d984c4b30fa2609d52cea3379c14e3672337fd9862974042c89a9d83fdab574b5c88188f6116d2c82da39197318ec785ff465660e0fa36cb6ba425244f19f58d89d736653318202a38482a6ccdd7ce45925f5a058c68dfd364399e15b47214d62386f35b82fac019de7eaa5c15dfcb2331d580f1220d6c2b9d0e2c98d1def25dbac933598e453917386f73746bf7023a268ba5bf54a92affcbeaaf260bf2b597dea2bc0101a170af8add8907f8633e44f32acf994f4a69bf8fd88b402255a51c29ed87f3369e51826717595b9fc91eaf67157aaa9d56ba714954527c8ed645f55b00468994991fd6aaae0f428452d234d5ef98c72d53a622e7944eef5808f620770a5b69d5b1c5afca0385f2c6cd5839bf9743b532447cd3b371e37cb47a8b9474c4435da72d9b5f90d5fef07de81d34256fcf16356f383208649d918bdba4a738e9cf83669a6688276600f6075853a7c4bb8a389d84929276dd252ea6771301aa154cb2a18b871debfca4c66adad3cbd93eae56e1a5162304057ee069fb64c4beaab3869558a90ec0ccac4d9a1ea86f4cb2d3bf62f55d8def83cdd223179208dcbcc5bb370edc0c65a2b8262fec0632386b7760110779dcc3252d415cd607a7e3a1781e70e867ac4079c9899b7c7ab2d5ce250858e824aef8f7198afaaa2fd50fc1803d5083c32618ed355120b9cbb3957355f9d75e7a7d5187e4b381b7eca359b0cf445cb8c516853ebbcafa1c27787504c61897bbcf845b50a41f5576eb7f74acc86c8a76743b8d16462a573b557345dfc285a18b50b71656893b746d57caf941bdb298d339151e85ce24140f3408ff3830b69af3f447997125fd05f3a5d76239a56742c56a595ce38bcbaa78da2eff50f439a4ad1727d300b50475257a828958cf933c7265b377339e89ddda208f3dedd7400504489a03ee28feb1d9326b30cd0ca3b62f9a7d766102d68b8d46aafbf9c3afb8b5e468acb257663bfd41b8b9e9b2519863a91eb91c83292feee58dc78453f7e93927c3415d97a6a976254e5249395c117f04d89f1be51ff4ab5f32c08059eb634a7e9cff3346f8b6b4f68ac79c92320a1b9a4464be39bf135aa10d61ec6cd25ec9a4d6cbdd8ea97c0041713673e4a5a48db284ac568cf78077c8a85f86a470b2c7b1c8fe31e289f3b0bbd4a124eedcb78dcbef61e0006782a174e8446e396e781571b2756eec4c156e0aeee359928f59f8d4424d9b103ec617c9925ab65e6581e484a168f86b1c83a8136a0f29029969dcfe7013b872610579c2c1efbeb7697f8c640343c664872b3ecec7db29a9ce781ea796b3dc158b036967f392fc56876ea6fbf349b537604c74b12341090cc27bbab1684a10bb431728c364578b575fc2a57c19355de94e72a89bfbb650e680dea64d574de7f1ca63d1f2253105251d732eb651543b199ffb82c4444a4e79b1862540ee6a4223fbe56721fff80b74c7723d788b91d6f8ab3c3f8b36afb88914b35e5fa1f1ae9158322356b096c8b887e5bdac630fc7201e8d6dcbd0c504e849fe38fd764a171e9bdf5b99eb3b853e625c80a8c8589b2265b5fbda81ae4e85eb605b357ac0e9ede06b51f2245dcb5bfc2467f7d4042707d54d007a480ae955de23a4b23920fb4fe70da2f1ef73efdb5a96cf696ce6664d09f8d7272f511effae833cba0459cba91bb4dda4f7b34dffcc7f2e158bb3d8e06c03668b36bd24295937745b83fa5408367c9ca424fa2c0b65a55af032619b70c4e07a3cb9a96e49b71852968edd1cb90c3eadfcfe9d1f97a4eebada5914a7128f3dcf8a51b939e1e618edb0b5c889e1fa0e75144913ad57d3ae2aef424bcfb8fa747347e6cc1791a3dfaba6b1b7f9092bf54b11d6e48f79089ffee500bb631be6ad56355714f8fc9bc1ae2a082c6bdeb9ad47ac14814d91ccfc700db5e09267fa4d16d448ab077e42f5720ec35ff1bf1a7ee5cc73178a0cd54560716dec4da0e839e1cd2a56b8def62021e230787d21ce57d6b2a8500e3858633555dbc7bd5a9c5d7f01dcdb26b81ac06691ca22d069dd9774731a6e1f2de2f83cba990c9e88cf476c77e1d76519cedd52cd0a6a0da332d5d676f163d2ba60740271c6f56e38a2cf0781eff6edaab9aecd2d9387b4f1b2e5fa35e361a7d38f2c77ef5aca0528744659722d7991b5c5d559644a230392162a535aea68472315ed2821f3cfc9d3caa2f3277c6eea1a7162aec80560b827a7d14366d4aa948a6d1565b4fda3e412334fcf6cf062dab70a9813fece586d1f6388597648c239c44aab6eb2744d80ac64b4d730d288b2b42626685d88b93c78b9a2d68827fd30b781c2c335dc33fa4d403d4fa8eed4cfbdfec717a59473a94ec60e61d379a177ae4e31bf3115074dd769b577a6ea8dfe012822f01b7e8e920c0039b39403298b0f9e801b2587580e0cb1c278ad9fe076ab13bd44600b1c2b919611b895a66bb5e38f6ad3578aea204c3bf3352b4aead27f498a882cc26abd63ebc11e2c8c52bca7e455cde4be7f1a28b6e0d5a8db04ee44ec77329134d8d101b73835c5bd14948df1ddf4275c52302850002c0e1d4553758fb92eb319a42a1ada19efeb174c565862f8428d9c9d81590fe38506707ed3d82b80d9cab9289fa1c26384615e87c23f368aa065a9557b41f52b60d460a6cc79c5959617506d4fa81d10f07cfd6f2bcc0d5e16bf1843e372af26505f277f4a1bdef0a69072238d5c7a1418fe732933ad2b44a31f9e47eefcb7a8d64b69a8ca3b07380dc8c7f7d7c13a3ffa64c43b17ec491ec40bb1dd84725a963cdc22591d6fa74f800c0ff88bdafa6e57901287799978933b9946aeafb5e1fbc5a1c41138a4e23d6a7308e24348fa499ea75f88bb3e9cf51d388847cd20b07903147fbced8f5293ef5a1dd2fc3ae819bc7dcc0965d60dad6bb7dbb102e7ced748c707792c9ee8d7647f0bf856951134537c63ef57bd65f6b44d98f85bab90f12cce3ac7d2695b12ce37f76a92f8bc56c9b4909c57e5e6bb1d143d1607e236dde77a2ca3fa075545b96ff69f64962993f92b6eaa5201c3ab4c475310b8eaf394728de0a2e450649e28139bd5ecdee12a3756330785f2e5f1712e344b315e1ddfa8bb386ecf841dc89b6fd3933754e0afde0a499436ee2ab2ac0287b8b313256ae6018f49dcebb21691f6667f120d018a2d2574ee720f16e3bce5dde5f3f07147cfdf386280fc04dcba86bf97ffb3c05b683a99f3f86d3b42464f2a8461b0524a1590568e9819e71f9e3a727d241b8dbbc4eafa7f0661d62988a84f3e0ac38e45d8e01ac7f7aa839195fa41032a9434639fa0cf8b207f92cc29da764a94069cc6cd2f362fde8f73eb2b9389207c5e5c73407a4e279e11b5f6725038c6c2aea6b046a021b9b3b6e3337fab966893775e802e59bd384a72eda8c41a2a5ec8c3218fa1792e54ea551ca5c3c82badc27626e97b310321f9d1a76ed93713a86c0780a90f538cb60312dfb5ef228abfbcfe9cc4d08629acb8629c802b69a80d763428e671288811b6e572c8c9d815d6eb8b700ba63b0840b380c98c708036398dc473744b2f4467803baeb1bd5f7382e4aa49957cd9c9bb730abc13fbdf5a6e0776b2c4648eccfae74a4dc437d3cec5c1aea95021752e8fb1200dd9b054b8e9ac8b0b7ebd0498f07a2ded46c6675801d925fefe09923751f54aad1bd83c61922553db1ec55425e58b05a985812aa43306311d8e959e200dce88560d444bdfe574c9a72ec4fdd5fab9c611b0ec37e75a51cedfb9b6703041630574f987c186ee8f29f4666bf623329142e7d1c06c4036ef1d4845f6a720f13d0038c866ab60a6572156afc624da60cdcf85fa8191d670d81cd9ed413ee4e90651f5ea92727c0f9b20c55d55f59e7b2aa1b9fe66d5ab856c4109d681538edab25d9ed0229ab7fa60adc0226dd90173ce7f95ceb28929f44c0e3f3d72f86ba729d252dff63c1e326c6648c06923b4e59157145aad1328b80c9bab4219bbe43fe205cc8264569cbaf44769d952b0a86c6205dd23624e30eed43afe099688bbba3da2a34bbbd95da5e37d2767fb1e56d7490081b03a57dcff8e91321edece179a48ebb034efa59410ff1cbb69f643f06f27b95f5941d9b74a2f21ba7f328c73412a255c6a7a7f36c0a9051bcce01af2678e1a159311375316fdad5b99b733afa9aece0e6e3d7e6fc42faa921425bf3c9df321876672d66134453d0de6f13fb2af65eacf4a822d165971c7900eff49bc92458581e0cdc2c6e35190c6cb3653f20397b3f48fb82719442d09247ae1fbf3653f3fa27d6c6e25a04f97237c7eee9674bc6074fd33d64a7d08205049096df51dfa1bbcdfeff1f872fcdf8de7d003a235550d08273a5b242203d223380e6ef37349da8b670bce54fe6b966ba1fbb41a9edebc8992fdd35b32aabf34dec2a74a02bbe891577f24bb0ebbe21a28191e4ee06c7e29b01ac4c187e692e52d2110ac93380309543436a15dd7a15a84e7b632a94933fa6fadd0d1808a9fdc8faf585d009b25f5e24de830b1bbddfcc2177c6882596ca35c5d44938ca5c950ae76e75b61a930b9ab678c6f3570aad95559b6e43f7f9cd221da36f7aaa90a84dcf2fccf6f304887fe18ff2c52a9f21ac33a0273c98010ba3d674507573fb6edd21ef6c6421c2fd462ae7543d39e0090a905c851cdfc4e00e49ad2d65bd20ea7725b69e474cc2b4f01a6942ebff43531f69d69a47d0ac0c95cc0370090681743f8cc39533598fb3a8f0fd2f73f656742328fb6af0d3c761745a28fcf21a81db3936d64e6c59398aa37ae8cce7fb904bbd3433e037be2bbe744b50cb243f219ee55c402a82de6e19ad6da9403e1746672c3e1b29171a91a06ade8326b3d12e2e5c234f3d35aee92987dbd75cf2ca6094e5e0185984695abcb0ce9b2c24b665528fbd48b7028a3b84d8523806905f7bb507570ac0592fc822e3eb6f4da0ecc5941bec3486f8097ab5264ecae2b47e4f76c679a4a421ac85e08c67a1c12c89184e97ee3ad81a2d9154e3da752fd160317b75117cbf186d15ad348a24a01b7644995077e5d63bfdd92a03038551b95eaf2f49997d3abc71d72b87631291439fa263a04a11c82f59ee26aa63c6430e5a7b08d7d0ef6836138f6e483860c78ce4cd739018f09d58c2b7327ec0b0fcc27c9297b198f6ae9de6b6c4bb6f1a5cc38cd8fc2017f1279e8b7b583485f1226dc6c667e77da2ef1d5b9041e0e72b66dda742ae95d5615f253c08c1e93d48b6a2834871b27632a11899c866ffda714d056b48726b5b2094611c12ea30c63ca5aa9f560893640801ba46bdacb26ac7f2071b2b22770d0a6ea3f1532725781fd9ee3925fc33f54c2562baeb5af636b0e59f6cced9f0ef39fff00a3205c8443bbcfc6c5a90578ce02688f5ff88ee93a83123a73d05d35b1f532c1cb1169f6fdab74e825a4fecbea263e555974292a7f9aa485f0a0cafd898fd122ddeaa3caa5d113731a8d3710c3888064c17046291bc2633831405bae9b990b3d5daed7ff90aee98911d5868487e47e8aa0175ca05c9a2ff9069b05fe3562779438ddab8d47563f6b71a5233051f42689cf4eaa1495822447443974c4276e3ae4486afa4eeddba11b688f9fb937b45510df047c427cabebe520bbdb41f1eba8563a91e93f038d54d8ed1d3f76d6f2b42a1aaef43769c7f9b1b90bd4dd6d571e2cb9eef5e2e4850b39ee2aa17bd8a2a02de84b9231495f511fb863ae5612fa9f051417e69d80c88b46396de5b830b71cb3ac08dcada54a54bee25ebd354dcff1c0778c753337cc5eae5ea1c9f767fbd3fc35ef5664f5431c91f00a07d2313d499f9e93db81e601da92106a4100c7149734778ea37e556163b3d994a1e8d186383fca5628a72fdf9eb6db868ca21943650ca3b21b42a237be9975398a3fc1c82ff8c8a61d3877ecf4f6c4f325cb8a93040febc19cc7616c3b3fe135793a77590af27aa5fb24605c8ccd6e5d8df67426d61f349551adb0d3279b9531e3d6f3c61a24746ff1900a1755231d1f96bf6db82000f2ffff9386b4454a7bdd365f68ebe73a25d87416b4d9a8bf81515b7328636f27d98d01d50ddc4c2d0778d15c7eafa8c82739263e12c69d8082fb405baca5a579a4970feb9f4385095b85ae82de28345e3cb08ee7ea4a607efd20f088c3b66dbeb6da43518d926e786784068d8c0c894aede8527f9c02237090bde49830912bee5e687111e9de233976547d3ca7bf40cf390241add2bd2539db70539ae2f11780e5c0bbf8c64d71070579397a9151e261c9c5f7fc2990b8a74b145daef7b2b7c6533f6f2d4f8434d6843a0721310d3879cb2e7967306f9411306f7f9f2f5029b3fdc821fd042d135d1138bc505671a494e672036c24ff83cf4c6caaeb038f8bfbe942b1962839b96edac614e9e6932fda5695fb3b677148d1d90d8a9f39c4942cf464c261aaace27337daa68c6d20c2510195862933f782297732447ad7585a0cbe5ed37ca06f318c793c66cc86f6adb4d32cae180b843c0504eaf49fa947a318ca59803b0633d0c0f2910586d4bb803757d31a22c97d02199dab8c47a5e4a24079c87abd31eb4d7f4db6e4bb6e1fb185b5fc6b0efd0155e4fef42eba0ebefe1c5d0e463ceab55483fd719c16d3cdc97bd48374b2b771e39457f8bf45b87287cd0207ab9ba185e76195a8dfe7c372311ee9028bf466b5f6b3fe166bbb84a990403834c18e53878d744ec9bd7a1d1196922229624b809ccaaff592034ec467c29c4de217a273554d4d28a10eaa8ffec75f50d2ca4fcbd9e32ae5674132a0a837259b27962c70635249c9d13f851565f22bd2ec826193bb6419dcfaaf990f4609c18d5c339941c21c42523f65fbfc094c6cc9480f4119d61eaf5ebb8a960c4b7092b5d87ee69c03da52909d38912fa8799ee982a10c21076c5093c40f0ed30bc61b90a94db3ec4d78c89b01968939f64dd0b6f8d15c2458682a98d23e4bf1f62ee3c58be3ff7bb5db2c727f557a6ccbc343d0a9f0754d5d4d41521a0ae7e7bb68113298def74b747943c316ee4a8347a658b06079d8be5f8eca27d83ce66bfcbf888042b741c78e10b2a32713c32d4db46afc7cc3b47af024f674964b596ea15e2ea565e63cd780e04113aba8154db9c9d1fff6fdadf711fd45243823127d834f87f6f88ce1cf2c3208eea64504de00d99d7d5c5e1f57ff9cccb4e77ec0520e0736c41cfbf1a09870acf3ef1802caea5705a7ac09652ddda9639044f74e124c4467d0c3c3c63df5f46baa254096d748ca84f111f61579c6a3160a628679bf6ebd4a2d68af46689c58247fcde80f5001c6634b625a82faf0a41d75e402c04ced8b5b57f9de62109f874c54f9c51e7eacbbd18e9e3cf32d95fe584d23c0374fa43dedeb8f3645f36c3fd8598ff64c3c762ea96e1d132ff5d910460633e9c8a12c80a5eda9382efddc9e9af5023c4db64b7c96636a15bce1b626ffa90e1937ce95a3e104cc12db8e9d431937b0f5629120f0d79658d4a76bdcc64cc900e3d4a3dc3b6021241291143ee0635d0c16bfb7739ba07552165a41fc00031eef32a2e2aa1b8c0994adecd3a22bf053ae4400e8dfb30aaa8e34b5e21daca9cd3c8abd0234975efa1e0ad357470f8714b587d04caf76e8905093715ecede7ff885094ad2f414d848025f33960f872a314d19f43650d213fef489473cd3d699b7ec8effacdeeea152e51d73e9ccbaf5dd7f9d9786606bd2cc74bc48109ce7c5235800024cca765c7c0aad075e5931d76e58c6e36431aecf1218bdb3517fae0153fd4a50a7ce45439be6837e06d3b460b69604b9357655e5cb93bda1a10618dd907baf656f65769027d75a3ca152c521a246f9f7ac9a505b048828d9d45f5752cc2306c177e9374340c06a2353e97942bed65bbddc1a2467b2c2129932b66db06cef0d7a3fae80fc403431140c0cef5df39253c3b43384397be71e2ca3271563e185138fd34b8a5d67f6f67f9e09d006e44cfaae69cd559bc9302187e78026f297cf5c30e1e5037409439c9e6866ae5c055c70f6406728a40bcfb122a5bc92039b90ef1536546ff0a7ecd686897657f176f5b18716730a52a57c47a19d3e723eef079f05bb03f3f8bdcce58d5d225979b69a71ab71db865b120ec4607eee5743851f287422e83b15ba3625ae7a1b6d5c2c8d7a7b4f19259d82058b7c761d58985f2d800c82d6dfb5bf12147421d458c96b1b795bc0344af9682a56428bc18e08e99cb99727a14f5b70a922430dc221c83d956ccca12bd9777db3418eadc6bfc59c0ff26ede183fbd58c27b62e797d9148c447b6f38fbc96e73086c617a4875f009d3649de20c31ed8cfeed507b2a7ee9f844632d92f3ee591f2fdcc4f1a5ab0b9a2d7db9533778de8467f8b37e122f088afc6a12f5d7b885fabb216323d705e3c0a7d83ef450cce214e0842b3c9d474fc81268b74ed348e10864b3eddd5f254f54da228197965afd09b8f57918b000a9e47388d83b364ec58f07de8f1801e105cb901f14e4149dfe3f6bde8baf236b79a5771af3e231a2752daed920e4c1fae0379c57939593e9d0e719e8b2482b0540fff067059dd913b4a22026980b63ce51640ce3adc5b6a33bb1de81c74d7e77fa403f633e4ed0f9d5157e14a89c0cb07a226857c04574c032036d352a7423eceeebe695a8d93badfa5f028194db1ad5ab4aa87df2548c52d84fca18fde0b12a07d5689b20b84104fff28e9ab31f96f7589565bdfa499c677f3c4fde570fd6e29b3aae4fa951417e70759988dfb4f72d9d748296efd52f2a3d9b1155345bc0a8913b5d68f578e62bb2956727554678cdd7f80e4d24d52f4902d25b165af95f26431f5d313927c289c440bc1b2d3b5da15904bc0b5c7c4f4cf923c02054499ba461ff199d8c89a6d84ae391194d9708c2aa79ef8ef7ca138ba860ae880fd8a25b584f593939de826284264135aafcac28cb5983186473cf9aa31f9736185f0d34085f1a8a3a821ab37e6ab76048e66abbec7234ec65788752313a9f7411577111fa19c9c39cdbf3aa05de661adecf32a81396389faf222c1c02d74cae8c8a23049731aa69ce7490396add1a8d35dc311e63c5e7b48cf2edca0257a9b816da989bb0bcc0d7087008f0d5506d4c5570775c576c277f71a057e85ae5c813b3db844abaef8bf5a1bec9802fe9824e62d018e5c3aee49f85e5460d68ccc482c62b7b8efa4c6fe403ff221444a396958e11f1d62f353104d0f160abb1d6bef3f5af966a34779b0114842e3aee71dac9091982597188fd9b24bc269c7c7be537b7d3e171fe03854ab2878b7208360f9391cdbde90ea052e2f1ef1e39b78582b6ab1e568aa5eb1fc81895e0949c916db0911b42074079f43ecb668a05959d40a779e18313bbe2e6794d48c376ab63f9657012aa046edcc5bb74aad3a391f651e7a26fffb9d26f7e00d2ceca9186869739a940b573e6adc8e8ee276ccbce777d86e2f6eb7df600fb3efff989b69c6fd9263d4371551484212816dc14dadab2eeac8a3fe23776ea3f09bdf9a09587830cab8a00d1f1414e7ca7f516c70afae51dbf86965d6d41050df2ac43ffdb206072713790011bf677b7dfd6afde441f593a35ebfb3823d0ac864eaa8b511fb61cdc698c45ade500a726a72f6fdd90da03d2552697af7e84e60b9d023c65cdab1a5606b71a9a36edbc1a46fd38778ddf62b2d812405cc8ab80dab6f5edd5de32325bb2edfc502d4ccb03c6da222ec54c0e8d1973bd95ca64f2c8de51fea607f378badec8d22f638bf6c8b8383a11b6e36fa22364fc6b953f24aaf0bf7978ed051b7a703fcd214158883e712387161d1953fa382c71a18005c91ba955486e60d26c57bf81337fca97d1acf1d5e3c404134e4a35e177e15b61b69d72e9e71ec35ccf3f0b1855239fc7084a543659f088b523a50305d6fd9ec2502abc083f4af1d6ca44217207b2860a1772a9be62d5f568442368c78d1201bc62b47bc5bbd441708892c8f18097595d3daa4dbb7dd568ad326c9ae79f61ec327f491d2d6d44775095efd182efae55cc722b04dffbee1b4b64ddc692d9f0374b4406368eed2bd99b0d95b94a69d034974086f257fb1cbfde9747a96ff890b029b53000a1e44f964148a52b83c45a8870d5116c0833851c7dc7a2313c188f5a351c23657d4b58fcf7c2b4d314e4b8ddfb17f5ebfe4ac618c7bca8d60fa098ebf6595460adc2765ce23abcbb5b1102386a7a0316af5eca68cda5e4ea41f521635a4ca10ad06768f1eee49cb26f4575700a38ad272fde9eb97b481b2e64f7ab5cf25695af418a47c10496e8df03feba73222043b3335e2c104f0995d6bf6e82322f5d0d76c574721d5d560d82b7a4e43e535a86802ae37bf6846c403ec16cea5e61b59f9f654fec7e5710573b5c38c8942af249e156cfdbc6bbfbc526e5c32707e575a3d67f0097fb5407d915279e5fb305cf339106bed6fd636f532b3537c2dca87d8ddae91a1c9e7009159429bf2e8444f416f8aae3022ae375d6659a3ae711f2f6361560c36e2bf5293955da8f3470736c6f59025a4033df5eb3ba3b6b22a2b42a743b283faea8ead09790496b22714c440fa257609336df9dbcb2e0d150d04cdd686e9004a4db3e0278ee7808e115992bc008cdc45823991571b893366a79e3113cc818e35938f46136e7f60b9e1270ade04c6702a842f501b7d997d3f601edc3eb35a1721a1c7fd88f6dab32d9e32383d633fda17ffdd336db8f0813cf117c48800c9a1663cb25e34f337c6a169e8f23e4e5c6b6d2e8b029d43540d48db131cdf3dd251bada8d1bedea081faab790621f927b16bbfa84b802a90f5b16ff37874c3015109910a157a3b1f192353f4976249be96b8cbaec5a0a15f40484b2382d8e5b4233611e9530466c4850fcd92649e010064b3d370cb6fc2453d774783ad06f76458a2ba22f04b91c7a3d0507b76b00e82e474c0cd6637fd6b214d93a18e3d105c33a5ef40e2f4a756ea21d3472ba9ba3fa7ecc4e171023cdaaa65aef1e8d01cda76beaa2c4d9d9c9e506e249d62e81512bae2fc03294da39bab98edf2efc36e8bee3783a8cfaa555659102a336bbaacd17205c54799a0d2f6fad48cd39ccd966b7ad547f6621fe139b0e224a877c7a94098789e00c7581dc7fa0ef8a1f090cbe3e9e5b9403bb47f1c49edd532cc5ede128b800a40ee11597ba10c08e3822e523a7b28efae3a2a37b4bb30e97a6f7e97d6a2e87a75fb4a3a5f6c41364435e9fea522d438c1f0ca6e33d1925f2f9932ceade81017ba68a776d064def428a1b9826a6893cc446b7b27db060c7cd0180e5c20c2d711f979f1a2f85e08b29ef5297b769235065ef733a9f706c42dad8a10a14c6262926aefc49926217d7603ab67dc23ce337734f559abdb4a0de3fd007f1a620ea17ff8917e13fc0922ffbdfefb36a848b3eb6419299e52ff755337a3edf347b7d59b944b0e3f85e2ca231a40d6dc90cb2a80b11db20998bcda6b4177baccf7a3a1548e8bd52f0a1a4c895446cd89e7a0c3c79e4f511313703ea55c6784ad8a45edc4e323bc865acaddee1a75124fd275962704b9da55c5f9b86a3ad89ed0e7fe8a6b41e725d9ef10a7e36a397daec9a30305d7296e1f4c50c25cddc7333fb509d8b7e88e206368ff667b75e7cc30c5cbfdc96782d99bcb4b947d16ace0a0e86586f2100f1ed69b80f1ecfa68d8cf64e1bf75f23bfb4c40b5fc662f31ceaab1f396e444e5b11f68f0c1457f33b7bb326c519c7da8293a96916bb9a2558d33271766967b7392e6c05923b13919195f47949fde0e568c6d2ef7aff0f41e070af33dcab24de59f48b78f233098148ac0e4f40df6ae1f3658be81b4f48e1ff4db190e55ebed42ae1acad3790b4740a7f5cc92aa9e80cbb6557e6aad2d77dad72e87c66081da1b833160d0a9b0243f4bb6d553ace46b75daffdf087894976dc1a7c36391902d7a1e32e76ccd6413b1bbd8c3a41ad98f85987add1a1ee63273b8686fbec34b2ae28609d667171842181aec26a7df5cf22fedc07ab11e1d0923de1143c0420e4930d7e6f7218b3412f65dc7462e8d0924947eca424f9d157d3b6a1dfc31720db072c4f032e4bf659d1290ccb7e4f5f4e55fe5269c1eebb432f386243f8a2f41d79a42058d3acb41be5800a5f34df1fb86412dbf39de67adeb09f2cab2f32e5712e31269c757d7eca176272d40fb5614e5b7677385c3cc9b008a9174bc67f5cc123bfd91f4ba5faca8667f22726b11f3c4325af298621772004a37a10c21e83ad915c2a64a06764701fbdfd1a5ed7facaf33efbc52572d6ce098e3b11db6ec316979ce0c6918dab32cf62a80d5e5fb9ebded4267149bbe5a881b7cebabda16eccbcc42dd6eaa869d0fed894bf20d93d61266cc2a81b5fe3e1b84856863ad06f2d5104f599caf6fbd407bcaa8fd907356a6773c3b9443c313fa97c6436c6f86c57dcc32dec23d7f52977b911154ae6a65c14f2a683c3ca1cbeb02d7a93196e762f8a0a4f8554018c791bd69fd75682b36a2bf50b77a858b3917e6450c8800ac765ea49ebcde4e124e3b0ba7258b1fb3f1cfbef12a4d3f26d20378f234706e316360fc5f85fc02fcc4da2057d3c986c10a93b861fcf4216a21fae506f4df9ceb089dad36c034f5280a2c805b7439371f89851c245ff25d7a5bc7aa9ffd0011275e61a652398f3e911ff1535da8d6c4982e75ed4c03bcacae2bb131c38d8bb9bcfb5e010790f8c9dca526f4a82aa410ea2ccbb8683c2709db5663ccffd8d1f63611085a565bf7bc8051c3e43249ee0b6debf303e3c04ac022eb4bee3bcf2ad0747d0fdfebccaf6ce2ffd4fdc7d557165ba2a96d9818900cd82f0b559a9fd705d9facf0b6725a0bf10b21786356e1d8071da9598c47ac46f0f2466597349f54f4272b648d081049f597875defa78a94dd0bbefae3ed87f9f43e809c8c87538e2666be43bb7558c4cb47224ddea13d07fd00d039736885758417e6b2a6f93dc2f9005ca87eba31ea00501c3753cb7fe946ff7496750e3a9bb4ed9e3b867803bbf3dc7564934c3bbbcba794fc07162f45b3c97f3d1b5081ae60d2cc18e4ba0a93fe34e3c9505a1fa8aa28e665ec9a3df92947e96b6c2e60bafb1fac47d742a628fe1cfc1fcf4c22a4e39584e176cf63bb58a87534b3cbd44578fe0a5943f7fe92fcba96ee5b4e955fbe3f83d254f05d1e4c2ff52220c2eabe00938b6fc1b3c0983717327ecc4a47fde4d29c1e4351e7932cf7ec80a37064fdcbc7eb1af8e2926abe45f57981f7b15371716f34c3ad80afda11c8f2414943a48c663d6c73e7a5193f71957ce4daf1060cf870b6bb961e5deddd8ea37e809354481c4a834d2fd6f6be6c50e9ccb40fdfcd811ce845197bf3ffbb16c29e67b8f3337d640cdee3fc7914d22fd501f7f68b207819079c29ead927dcf9e0e8e900f59e29cce4d56ca708a1133c7ac7ca039fbd2bbb5ef8adb41d716eb1a645e5217f283c85eca939555249ed851568574e71ed0eb52187cb17ae4bbe51e957277673af4209f743b3f6e2f55192772863f9fb84eb99d82f7974b5bca5581d46d024a901d54be46bc873a7d422529a85b0fae7b82d38d1e3b4eb6c8fd685f928d1f564590f30a3a58780e167454fd6d4ed44d49677afa3c7c39fb8c30b62f7e939dee36033780b260786a1a8829b2a70a05aff97b3edb892deb005b0353a789440fd9a8f5de36473b6b3f951976350202bfeac4398077302c764283731dbb635da7e9743171c2f2f9e6dd51f8f030c4b3dceeaf3d13d3d6105720bfa907d1b8b9fee1617c151ee53e5aba81efe32b6fda9e49c708ac7e8c8d52c4e60817302ef707c809484139cf1cb77ea5023c0617bbeea95c4f80a7c9af33228d36ea6f4e8d0df5c1fe47566c97ab6e7575991ce0171efc366100e80216d9652d1c30cfd7c4d5aafd4bf5f7fcdda145ce76786ebecd2f0a7df5262a532f54a74486cb0bd067a8e3f70420b262a825053e7c3dce8aa1f090b120e51af33feb9167ae61ca73a169b0a3e3c9dbbd9363d53cf158ac15a4846ef78318afc9dfbe68cca27ca0fbe8f98e0929eda829263d28fae0298d5544aa79057668196673d1e00d5fcada56788e08d37bd9573dba7aace8b5cda3818ce0a666cfe4ab1114cf81d094118e6e495ab977c18c746093e50c3bd3761a14de7f6f6a07e87eb9af8032d3c7a55d9f59a48b6b189ed4b9f2c793d10d30d81ff4d2e60b0939babea8e685e49eea590eb00ff047da2550cadd683886369bf0e2975878b9a523b2a9dc2d263fef02448a3488837b018b6b1872df1c4b826be02b5a6a9bdb897361780fcec44172fe5fb71f7a102735edd3fb3d934f7b7e3f748a7d94433cfd2fc7857071d8e42ef7871e7f9727f93c16a367f1f68b0be55400f16ec237830a27a2759e3e0f171c000deb3aed776457a7d7414a58f0ce3c5675d17137d62f18e871dd6d586cd698da1a7bbd3c00e3fd7ee815f40dbcbdfc50fa04ee82e26eb97895892f3e354b2ceb963bc6cb18a864bd69e386d27c9a7bd13505af0df033a332de86f0eaeffa5661f42b75fbb81d7558b6470b9cc3fbb9f60d3469163db69bd22582c571834e180af7281ac9dea84eb360310cb4f1c166d5b6beb7f2c8a46792b6cdc08992de486f81f4d3cc4d71554b6d1bc27de6fae11383b74c5aa1fd502fdab4663adb763d2f158aed764d0946e250f690fc5d6f94d4ee1010c0826b10802781630fe675540d3405b7ac372c6fea336cbaaabcef1ef749dae38af3da58dbbcefeae39e9f3b7ae96420efb43c6610148168cbe6644a9d79db8a82259a7c1c5972fded2b6f8ca0706e87bd468231e682db34badb0820984a70318affd6fc186c815a796db0503d45c5e52b4026adfa6f0c97ef2bc998e84d367bee1ff7f0c42dedaaf2b0666598f16030a29da609027c1c5a4dd71d545a3f6a912a9fcd4b828ab3e62ab8faa8fdbfa56f04581d12ad0864af313d2c2e4960f4ff12fa07c8f6c495fe38df91250af25fffdfa2876abb66a417e2da2241be200bbb4f369737bb4a54cc3eea0642a27e1f96cba42f3a1cfb75b44bd82e3bc953f5bf21fcab55690e685abfa407c24cb492963321e457273d50798de62ad352a6c02dba7d57c5f8f2d7527288a77bb69f980881afc25f6627256d5cfcad945cdcbcdfe30fbe6e9fbe9cdaf6c5522ab0be412e27212cf4bc7ac21d6da7c8c78b5fc2081e6fdc7b012a76f57e23d9b739854766794758b5999f5721e59bd8b57ef39a9defa5af1dd6a66e0f6273b728d1bb0ecfe55ff8d47c607f7db21b7cfea18f059acc7cadf45ba9ae1d0c1655b3c2a0145489f86c051c5c0d9bf071734c73a4a8efa8ca45c8441c5398c4cabcaf220220970149325d7b6bf6f61ab5379b93ead664c6c6c9b1084f2dce6165d20f229145c6931e57631f91b338765106e27758b00b1da267edec0724c732709a75111b430077cd4b043e879430018843b71388f444e115ac1013d07376545fb6cacef3a69f2d3731321266f3a3c2ba7172dbc796067cb29d35c028d2bcede725346e7f08523f7e0716401409e202671729f64c1f686d9bf931829e6592143b1ce77a8cc686f1689d1e2f8f9afc79787b3e9e6ad97b5b0f3021ce51e9203f2d2cff85b2eb6b0303d4247498c43e3087f90863b7e6e71d4b89e0d5e8c1c325ced1a144407b615137839206e8a9a440b5289d901db4b170575e8600d1b6264356276682dad1af7fb20c4715a4d1f1493a939507b1dc11e08ca9902a3fa03794c313745d87d71c8014bd42be87a31179ce6cf3f1aaba6e432b20f77f30d7d2cff593e42f1067b72e9b0a6ee85c98fb478c01576d64c4a9632fc0e80e4b97be0bc2782900e39cff94c46dd77de8b6d5ee82cc6e0b11cf096257deb2808e0c3672c5df3001b39256460c0af83a2b11a499b8496d28adda318df6f13aa0c3064ba7cdee7def742131bd405a73be24eaa535e39487fd3fe6850429d24d9e3d66c4c0dfaae33f8bb0df92330c3e0428fc53c32433fbd528332ce463dd9ce8f0b1cad08adc7a4f8fc36cc45c48c7c37de3f35b00e4bd7addb321ad75ca8177876216f4419d1c848d9cb6f6148a27e9972ba3323b7ec83fa38d2b18cd65f66f4d357124cbe8c10e2ff20183335ae857899eac01077a0fd9f897797fa5fd720d2d46630f6d9f0f60b6740f947c5f11c510ceb8be6f9ee04904ed94aea7bfd6d2c6213e78effe909d7d4b5fa8354ccf609f1b1062219876b7356d5b4e9561db69bf4a6c22b038e6ef102519b32bbb0f704d672384736c4660c639ce2e335260aa79d362936e4b03b14bc860ae955be37ac612e1e82b3e0d792f2ae784476d914a7ef9abe2cd20a08d900fbc90f127f78851b2eec0657df2b6a90944d90d9b65c1ce7bf8fc6841ca390308a29b5fa89276950bd9341347b05b25ae673b849dc414790080a744f7737ba0ce571ed532080a6f7999ae1ecd4ec937016c6faf558b14d25f4d6f2894829f2adbb6e0df5be1dfc17804ff3f8a37d64bfd2073e69a8ec5346e9a5b27d1cb1be3c029cb1eb9e6d5f954787bf9cf987ca724fa84ae6af908720332353295efe5217ccf6c03334497ebccede5e8ba7f7361a7d5ad8b82463a0fbf337009fb47f8846251fed5102e5f52c4085a0dde3a2d0da90b685ac7e59e19c5b76d378673e38d70ab6f1471bf356472c4e8ec653c8082fd34e7ab003350be6a1ce9ac809b0ffcc209f5e2d1b1e993e2b8ba06b54fa51c75d8759bd600f21cd1403d58977d365c4610e4a611c417795364283e3f216771a6a11c41d49a1564adbd6b9e245d218be0b21c2e7aa38f581b11c9825da24ef554687384d0243898772f0f29bf1686b47925390b7eb96794fff1f3e683d6a1374f5c8a92b68139fc25d7e23145b5d360c146d9a4a0df9b280cb3926c49a01e098de0836a7ffb670a686e07da9c2a5f1fea10ba5db47bc3e8b27f3c81b62b5892687e58f780c9f87766eab647462da69e6459bbb671ecce86dcf78c324877b26c70aceba42815ab34762b7eb68a7241809b677b00eb426e1efbce3abf0b14eea95693a4dc52e32d4fb9f5aedc75fc0657a6d815bbc46aa98bf75c2f5de7dc4717641d4968d2e4d8d401ae54aa51b0a07c16fe1934ddc3ac96f1ef36709abaa76af79b527ff8d68d3df8fb8be420f92f717f3bb5ed8d86d6a6eb29d26883b8bb7a5bfa6c761900550d2f5d18087f1b04433d4e177006df98302153e706761f9827559342310d031cc8cbbe935600c9f4cf662b39509afe4f6cd28d5b27848a7458adf6e53281cc81ef5266c5314a952f290718e3d992b6bff10e835183772fb83d8e2594028b42f041083ffad1ecce15b8682652f4512c393adccde052f8be9aafd0bfd41b7979d53130bddf01980cba64c3ba15bba45b9db882712ebd727c9e128db5eaae1b675d93092f25e31bfe89aa55e736141b04ffdba383f833c4c4f48d772b95c84245948b82d4344574340b25bdd2488493a8365dc0a3d33d2aa862960d498839474c9f3598ce8705e7d3c1e87426beeac58d797b22bb454ab50f3565745ac19c4bb3364f8e504fa24134cb5f5a6ed7e5f184e9a2add1bd2a2572e2491d80371d4cead58a5228b8839f93fe9c5abddb4158bc87db61f4406ef1098aeaef201df684e8de444ace611c965e73b000fc0165d08797344e82df0cfe1d51d0000897045df983709a72fdf8230dc71bdf9c34e07c1885765e61977fdebc74d2577a0ef742b740078613ff178f276e875ba4805dbdfddce62dd7aa5616d93e1786a0974220004fe4d69e9aee1d9f8900b872c0ee3c3702b3cf284bf2ddfc35fa770d68365c0cf5569db84e23abbd27baed11eb0c8873de16c3feec76c350f3ff25f26470fb94590e685efddab38db496a9713c1ca367bd53a00e5bec1f2dcf20a3b315004bad4fdd1bcd2f62d6c303c44181c717c47f6c6cfa9ad47d9d7fc239e83f6ccb41f64e15894b663ddd5d12cd70c7ac2a1646450ac37fa446dfc916d5f542c05384e28aff2b7056d42888e58dbae92c803522bbafab2cd98805f8a67fa4d6fe5373c501250a09bc385dcfb2052822a828a09ea494c461c5fd28aeba961149a867433c4a416b7df69b992265b5383708a44f35e145ef8930b29911408e35d1bc4e58b653370b0141b4a243244aafbf61e07b8ef84fd3c01a0eb74197f1d0f4443d80e3909342f38940a33f770788ac8a3c6eae50653985cf4f0db65ce3ddaad542447cd7bb1e9cd72d0eaea1e4cdbcf616ee2d86c8459d10abe8c4d8a212ffe0245245193128a03858be317ae3a385094ddb40b54dd303f650d30c97570e298f602c69df17cdf47d1a30b1a1244cdf7289eb3b8785917c64b5593bd734737346ec9df61aa0b52972f24ef0a58a14474c5a57d5edb7c2ceb4a595916a276364632862caecb9f3749cd48b68c49422d7f933956fab826b593f3780f08f10d72ea4ade352e86fec9dd20ff9c739eadd9518c9f49894765aa7ad33e9c4230f1b2ce0ca8fdc1fd26a2c7959979b41e32cd50d1246caf377c8a391ba0acd889460a81377ebb449fb23179cb87fc1a72671edfe440b37939caae269924cbc9d406c409fae9c2ad330059b225a32741b57db70b327089b427bcbf4439038cbc8fcef2fbd2005c5418114e4c04bb499447dcb2252089e3200e785b9fc996b948212f1a29181d3ce9d05d1f587df70fe5e69d3ea307657127fe2ed4caa11c7bd001eb9bb181a7d1c4bcf7b3286c9705ee748be39586525f93b694a8dfc9c01c83d09542804f0c3e6abf0a3af8fac01a39f6891bbcad0b0ec0cbd9844ed9c6ebb70e415795047e005ceabfb797b1bb4dc442a89c2869c19c100135fb57f3093acc79f57262e78f687ac39bc27e45480255b30c4f567abbccc427383e26eba8c2bc907f438009cac3b681457fc5adf6d9aa23e814f6031cfab8f7740fa996a0d4501c991f594ea7cc7aa46468fc5d75cfa1c44999a91e7d7851bad01c08f710f6b75df0ce4fc004a83f2a52540867cfe482e706fd411d345e6588a4ea557a86f49685e1ebdf8e7e394ff0634e8c4fc60f67be05e9ee84b491aaf52e823105228256d279b43fdce0051af368f73b0568c9eff8c9eff9326afbe32f0db978f32bbb45f26be07953e2315a4471a4075cbd65c3f1b14c8fee7f9e801f22176eb2f1aab84433d9a57d08b86820e4a47ac20b5f3804b538df74c7c01df782b5b55d2f796375664d37881ff96a04bef220f87ff53e388fc985d7ee83be10139e2deebfc5d9630066f26e46c97eba901d21cd80ca3b05b75db5659a4c07f69283033331a991477e4a679997a1e94774f6b730f3e3a7db2b6205e570d8b6993aff216eac028a99755780d12d5fe256279f9b40f01187de69dee655456243ee1a2ef47b3d18c5a1beb719725a45e0fb8de61635671f01e097af9acda8224ab65ade47977ab5c6a44a334e8392e7de311b7b81e26fecf7937b636b30c051509f120a115e09372be9b6dfaf9fdddfca29d56af9455bd73a1cdab46562a59c1d0b4ad6a352908a15e5d1d45a910b3fcbd935887fb45e52c015d8ccbebf5b77e68353a421fcd7e79d1f10bd992fdb99cf90d08a1ef9b3b3a033602dfc5110bc6fd59260dcdb36f073f267aa9ed66a65d898377da72640c287b04bd4aa1c038e04710431b727612cff578b4e42f2826512702ba114e1a4722d9758bc77c4eed5f8cba09d351add85aae5be80f8ea31f72f3b1cdf049cd70cee7f11c9fdccc3528934ea08e57383634fbf50e36c1ecba3f70639d590a11f52ee37bebc122e20f794af1600006d9199ee62bf829a54eab70726770f9b12a875a217bcdbff7a631df875d5eafb9a2b29b8a5854df326f668bf8e92bfc24129676f0573feb25d3bf7da0af391160450b8f58c3e68f4c65a399caf9f52561d7c7e9a58d97431a5a7df91a111c9d08e00a243b7116f8414075c47599a1077ba48ed8a43cc08f92d04a532367f73a6d72acf8867d832900ade9f8bf5003510eb6d00a98ed3bb377277f298373e71c0cd532b2ceda7b3db5fa7fe59468b713aa7c90156160aca6bac55fa0c99876e5382888f99b8cfe5017db4aab5953650bbd67685e466da20744a5beb94f2d6093efede714ecbc0beb983634539b463bd4ebfa1bda6f0d83a27d95774e040a02177c9c9baf7332d2139ceb2ef05a4c6dde1ab019f6b7969e07a81d95e15f1ae3410319cf9a6d84743386b1debc95fa6f0ce4f0e09cfef53d81870e6dd08766132fde6f44b591fe4b80cb0726940431cd527c91c35d66f85085906102df66f646004369ec3640cf185b9d1de56cbbfeb74f0d02fce4a5ac4bf5294da176105788d5147c9eb2b128d04128fa2e920405461f94d9ed711a096dba8aac54c9f51e2a1956afa9290221369facb08b83d84277703992e642c022fa8a33ad3191415b13b1f28ef4ed563e80b333352f65f994ffd63d0f7ae3a99087bcba24a8a2cf25e1d670d3b152a7c3cc16947f2a98d9c46562314e8d67b026cf5f94150ee757b7cda5c7e04fbad6710a53e4068cdc48bbf849b1ab8b44b4df716801009b58baef3f1273ac4db56658b274367498832ffa7005282c6bea8a602ca8a31e62e53c7fe5b141e6d8b5f68e7b348a41ef6a2bdbdf44872b12d4f44fe598be4198ce298e6c78a9e33a22c4bc3f49bbd7e33b2d1f90e3643cf7d871c8c813e959e291fac451523fb71cbe357c81c93207563eb9fd0608e73cda41508acb747ae4a6ae2ce8ce40031b204bf8daca9f3471d1949bdf6483411f1f6c6fa6b48f39b417123cb5ab301dd1956f763a663815026ee733e6a024867d9b8f7ca06003fe8b2fff00db76d9b6fbbb343db537f8245e2c6aee9a983d09dfc656dbfe8f3bab21ee72be54f99f345754fc040f461fa897ddba5e59286f4dc8981ee36e5d71c92c2f63894ffe4dc4ac5530e2cb7e4af8ed23e0f67b7ef4726fbc74c97bd7d1edd9c09d4c8c9dbdabdd7607ecee1ed89ea25a4786aa96c52b8a28f5d9a117e5f5368efad0fa2535181b0e8f2c15e1ac1dfaa0327c244c5d96d06a85a66db201d22c8acfdcf4d18795e8a4004612d414a0ba4fca23e947943dad3dc5c6ef7b9ee9698d731df7b85d30348c88846520f276b009e35b8fedfe6d8359edb7e2c5407a874d20b2526134ed8843ebcb311980ab90dd4879cd66d99e3041eefe0477192aeddd3fed177f0fc498772d0318570b3ca1c32a79697d97379c61b4a6e364e15672f1bff5043e9f93d14f3de54a20969c9ef92004099a7df7bb8c885f9c8fa9783d2447cd27d603de50255448d18d4819918ce24d40fdd363b7da6ea68323f79e42a14067462487daf6d5e3f207e4b0d0a8b5cd98139997ff4368b3bec2963c792f875b36c6b4908c9045cdced1bffa8f16a906461406ba10401f922da56cd14202739e8f11762d610d51a4d9273612392c2766e10e80dfb1dcd3f79ca9deadaab8e7b35676e7ed109e0e2e6ac54869d1b6cfb8507194e2bd688eca03583572e8e9cd7ff0eed930dfa870a36a125d28415844a4f89ea4debb2ed6d351593fe7b3d381cf27ef458064ac8406175c2408c304a155998d8ce834a82fba00231897ddccd943c7620734f067785e96c58590970b5c415bd5e36148c9be4a2f9a4d008785895f11c01e0af10a6d99ddbfe608b0fa9dec987fa99e5b80fce941e5d3484f268703be0551cf4c00657d4d671428e646cd57d298e3840fc164947c7c4247538735e3d2492f36f495c657acd08b3e5cbeff8c7c45e766e84a0447ddcc4911c4bcd6328fe1d2a2bf4db6eebc4874697362d7b2f152b75b81d333bfc2b3c52d524f2e67dd4a0a449992b74eb1207ffa97e91d7de81258c52c2ada1b632fe528667e296dadacdebb7d9db2aaa571795717cbf7955a90007059e4aaa75766a32aa8d05e66ebb18bc422c46832149ca080d9940115137e58bed43bae02e8fdabaca0f23bc99f55f6bccd58e6299210acf71d549db6bc88ab5121c67baa494f9bae27ccd49cf5b5e988597650f94947f35835b88d577f6107820dad773df4fb3cf31a5654af4d924176ad31fbebbaf4138ff465257780b3a8a1dbc1884286ca2a357c0aa91e8c1a15c663d02bb9ecf5d83e4f7e76a7151951d1cb0f757eb0eb1099fb2777f398c152090ee1264abc95afcac409f0561304a7513d7cfb3fffa83aa58494cfb3bba2ee27bbed4a2568b750c1cda87b454c933040e04cedc0e6a240386d9c876c18b3900acc55df47bdcb15325f98a54182eaf5ba158fdc8aac34ff4ac48e52f945212776f49bb1b91bbe828e6f2cd188bfdfd3e9fcb46940e7da9fc48d778cefac4b20d5ff6595811b74d290177a4544eb39e829d291720e25bf4576e59209e78997ecf74c448ce6f053d58bbfd0c7fd13d910d189e8dc83a38790e211edd7b8200f6332cd321788af2f33e71b394a2e5c8ccfced7409d749d955f9628b869619cf9564295f50c143bf4a994948a94af29dd11b52109a3b8888d4281dbe5fad8af8e2c706c783f1a9eab3ad6792a2199c44d074c31cc8336a13b13a9addf8e76bec94045b716deedb1e06cfdbb498557b7d79137ba4801837f194739db9783b780b96013ec7af3a1849f0257478077f8c772fca01fa845c973386299f0e2d6d720ba62f7c2cb34c522f4f6855b3677a0673943e3c571b7deb88b3648086bbae883cf216eede96f59d46c9f4951a5cf80ace3fa762f197af171aaf63cf992e1005397b9e1bb8ac03342e1fcb39aad75143aab42c8e1e814313799ddb7bcf5e3cb23521ba650ead878027733d02088bb10fcb7e664b01eeade62541eabe4eb49709e317c1629d11dd46fc955b88832fefb0c5e106b90794d2d5f00e475a418840444671fe0f923eb8c1f80a95c68e647866475dff5d0ad4ddb57e729efdcd92be49117e78af287fc7a2fba0be4a14466b41c878f709abcc2a3d1d4d20cd65062af62ad55ae6d375d7edba631d2b8adefab5c603acae8cf90783e8ebcf51495eaec08e2bbf3db3cc179536c0c465ae65dcb97f98e1fb6561108c58132c7073f735fe7e3196cc208aa295ed34d983b56e7f5e4425946c974c8babd6a82444ea3fc55e49b9e3daae8a6b1308163ea6cad7f3e52d00faee8d80c75b0c9e06e0258061a124397e75c129e52adf32869df07e0cbb0c82c33b6fe22c859ee1254aaded29b50937168692ba0519239019e6ed4b9b1a5e8bac507d12e08b6f9dc286d5e662c775aa7d9cb8347747faa06d1d4e54cde46d8fea24c495e599fc34044ec1c0b02c116078abb246cb55840df50025a6d39f7598febbffed662aef8e3a2902363346a4e70a80bf9858aa33d85ea3a78960e47bd31588d426c8ff48d6f78fb480fc730a7240b3ce0f3379bced9cf8a58b71423ccc5576f420d7fd6cf180d22e889525b1ae46c8ee13d0d6b2eec9ef286f091bb9796c8ed2c22ab00bf7b49e749aa3318665c845d9b647aa2cfa088e0db85dc25eb97aec388b79628a41cf50fb6c4a8976653a4a29ea32ed3bea16e2b9ab3fa8130936e5116579b32b75a484e1f5abe49e57201b657a69bbaf0a8488fa0562dfcbbcc24c1585dc688abfb3b284d6f39294393dedc749dac181a528f6d1cb03b60c5f3bbac6ea3a65f4a7e7aa9930a4455493cfbecadf46ce3acf012d48ca806ba7457f3c7d3ada3abe496cd3ae4b0bec7b41e31f1fdaa02a4893912ae0f51da1eddced137ce46384b7cf0d4ffb5190faa07f699cc73a2d389863a3115ad14b51299be747d4b6753fef94d106e536af240683e21b4ae7b0a94842dbe51cd3c82edeece5335befc70f08e812f7ee8e4b7c5d4799c1aef0db80dc99b65d50eacf1eb58a76c3d260d291549ee970ea6c7b1c5ac71b784fa5763e6cddbc23b59243e93ef6c6a470a876b3d2bda837cf769da822f7a18ceee6ad242a0e80afcfa575a893546a2b6ced7dcfdfb9d6ddbe3d79b61852f3f0c6a083a99d8fdd6ed39f6167ffaa9dcfc0e230eb833b741a0caace6fc989a6730c1573c2c2c517b91a18a3b99f1d34064d6a30fd4ac1ab13c1b9beddb7966163932ea1c7c3eddd1c623cce930d634cc1e1c00b7bd3a1829e004f4561012cd0b85288adf4cdbd4192b1cf1dfedf5c4d2c1c9866e6003da4fff42e6bcb81240633e01c082cea60fb8ae65ece26c493a536c92e28e731fc0ffc11c768c07fd1abe1f707717ba12dc05f42a8442c73103df7ccf831172e6714212fb14232a2e546f9d1f416af9b81e65e1b4d440e7b696f98b14f3303a1059554cd0f02f9823b7285a2cd5573c8eac5bc02b2f440759a6cf7cdd76a2244feb27901d4e905561e768e738a8052783163f8155c9a1e33539c03d78325e36ad0d20b40d8a96b240ed36086bdafe45ce1cbe2c348f9ca8cfb534c54bc93ab26b8d6668108ecc577f8d04d3fde9c7d13993316edccb456323bf0eeb5968edc8dfaaade83608a906de2b6ed4ee7827f350b15b9d591216322329fb3230ae9a1e96c26c3291897722d02a37761f892da5ff42f7be5f6ea284100f80c31cc0d2e27aa1b8052b8d17999301d049f647458d665cb4abfb6349ae66af5a1cc671102db9f648b1a7fb3762db8a1d9333a27024f0702abf055b9e748595ce7277ba8aa19d49d41424e5932f5beb12e5db624a6fc7875928d32c71ee06be1c03d87448e7854f2db137802dd05a8e625e527297559fd6fa88b8ea970aa3471e4edbb5e6c72b2c1a0a472649a33c2154b4c62e55fc3eb630a4857c9c9be3fbe2b9b47c30c8b428ecd83f5651e3d0b4e32d489b20857a30c8e7c9a79b6e2007a9209c18f15066cd7be5983a14d26c80a1ff824cc475e7f9f9c3b181d816f7515b39e8a4aac736f961ac2b38bfae2adfed18be499484b5356007dd57821116cac9cc3311dc108a9b9b0e306bcce6aec62363756340718242618829733f69f96973a68e8bc011787d7e7bed9116e3f9d37fc8891804c28dc395baf0c67c40e53b431ba99363f5f94e8335c2c9ba165d3d0d34919fadfe9c379b23be7cc38f2bcf560511acd62f403b3655755773d54b75aaba3362e572d671c1411830ba96308990a17687e25de9ad81775c723c9c0584b0bcdb3aa5cbca5b97ed7fcc552a9264f8ba8fb4812191a2fdfabc9528bb4fa899b66f37a73e6e1170db6dbbe43c05e9dc88dc983606095c110d1bf95ac6234cce1a09d6ad27c99a9133d77a2dbc6701c25d564fecd83d2acf29c2f334731445926dd27104849589f64a786264be87beacff2f38204500e8337c2e9eb52889d600b8d738adccbcb7babeececa51c8bc8971fed4998742a477db159e6f5190e8a553f02f2b1ebc490f768f28c0abd4ac5d78651e95b565ffa1a6dcab1c7b33835afa509ed9097d1033a2273fc1fe77496e3edee3459ae5b0f8f172018dd51caf9c78710ed7326b86a4984bd82fd7eb3d1f2992533a901802eceeed9160f263c8dcc455ff65fbcca85517b66f772de9952bb0957570fff2747c544a9a46bb4ea2f04a6570058cacb924bc41b92bc5d196de6f199506393879e56c32f4d20b424c9df26fbccac826f6cb80cc7ac67b0a344346d1392b58332599fae326207828eee60438e87aca638e91f05f96f4143ed5129fd96b4fe07d0ddfc51425eed411030fc4f857c1bf2b3f7e6ae1a6648bd9d74f272ae8734c4fcc3fc1574e7279be62ca4eec31eaa97f2b36ff3bb39e8d9f9b3b03baceeb47a3e1c0eca17ef3e9ab62f3e9d8f281228115835cc987b4cf712013ff06e3b88824ec37b4e1ec3252f1b677bb9fdecf4de69dc964fe4d210ed957d2bd1ff170cc64ac2258f61ec90e62d25dfecff444268293e6cfd539b6fd501a2cabbf72172d8c2b81e53bd00fb0a30d24685a66936595ee15765c8a26603b6074399737f85cfb7903d038a15443e4713c5bb852431a5745540cadf43c6a8dc5a2e10ceb8ad165ca689c8bf44e0bfcf904266d0a6445f6cfe620bd0f0fcde477dcf5588f7b635aad54db22ac6f6c923f5d417eb458747b6ceb3616024da1694d7dc7d6fe3319d7483036a3d3bf89d12f7f56086dcad55fc3dd7f30ad8dec227d8f584e9fe4c5526ada4413d5705c9a78cd2c769ddb28361638f160aaada5efce8ac6d726051a9c2649aae858e8db826849c7e315fbef4b64d46f8812478e1517a65f37b29531145266167988a615c1e05b1794c36599163dfc569c1ac9f75b172ef2088d73107d377696994ccb65084de518ceb53991e38ada06ae078a5b3d2414080bc3f6d4a9fd1e2b1800d163d8ba536dde174bfef4121692dfb916fed34509f0048aa2a8c11a5a11ce02faca9547c5c33f2be74ad82e3dd686f847bace5fb91da1c0efa7c5388e83c5b1d8e7d9f72cba6e3ad34bacb30b5bdfb37a543cf78f738cf4df0c8b524094e36c59514aeaa567a2ec3178307e2c908156c11dc5240ad84a9a9d27876e6d54df541fc35a86f041b8e78426083f8f7a86bf5a33c86cdb32bdf85adc82ceda81ca3c300e27436bf38da3a9d2bdb5a99fdf2e840a547415f833ffdc45e8ceb85c0daa4a647744f3d82c8f8330ccf0098fced5ccae24d42901914bd81404c660c8d171ae6d2ac957403672deb04f7a8912c1ccd22677468a7e6d7275b84b3f71766b8f6fa6be2f053c9fc2dba1d8b71b32bc44845824c29c6844c1df392e155f5bb316f1f5cab4091238ad5ef9e610e1dff05d6c132b53506e4faa2f300e5a059667473fef0de41b896f1a89c78a958f1daeb03ea1e0203a03bca098b0ca8cc167e7d29563b5a94a18eccf2461f2443a3629dd015b00f297012461bf22f0fefcbe9a953673b22244f112bb31a9c09a5453f6ac2240bd0fba6f987c48ec8310c9daafc59dfb44afef1bfda7bafd174460e070ef3da3978e846d5ef7065fa4f0b0ed54d67935f109677a26c64dba964958e0aec4cfac77c17d49dd76f14024a26d2051f11f397a08b49e6c8ba056a980b1c23aa922f276af84de84db9213f12fe5970a80230764023a7e712e9036927ab15535fa08a19e5bd529f24592432f8b29a5c6d55d72943c7e8b0e7bbed7b0711b7ac66f006907f8d4337f1e6b481be3e0cb7c746c4be13ede0ecc123ffadc17d7bab072729ef940b46f4f48e09f9f1e03de6ff48180f298743f92cad9f1f02ed4003bab1057350cb48c6c11447576f0e34f52314bcc14bbcb8c0debfaf5300925eaf49294a8dabc6d5cc9f6bb923178a32b96613cc3000cff2bd07bc25acb74b3546511c42482cc79ab41c0d0a547a521d2e06a93f55158214d7e5fd9b19d963e3ee3dac7bdace8f90062398f9c1f3d09468e9574ec93e77ff8316eaea5e744da9e651f36b685881f5f66f5b41874fae1176594c1f9a6a67b812f9c494e0d94408f9f50d16e3b12b1471ed0312deffdc6abe29ad120524aaf8db1ed9d307234d4b706ed7e469e5a923af1ae8ca8c96928a80fd0993dac773d94a4e894829a17b49cc95b3155398252739406ca453cb10196ac32cb42a16e3e88e2cc0f7c6dce9c7b6868682514f6bb45b55d41f75d4185a6b6d13c983b3f377a1f1b1c5b5ce62bbd144b4ebad15a93252e508ce10877760393a1828a730587daf8ae2ac50649162967cd0138e05ee38e02d8fa11a46d59d136f30652b73a8a0e492e391d392984c2aa7295e4e929bda8d018de84ef6aad3c40d0d5e70b31da2a6a55889ace0970b929b9cdd603e169c7c88073bc3e0e9c9a882d099c86f87e2277c2e1b5529a1c1f978bf2649a865e2563ff6afdde09c4df35e7ac155e48f7a3c39a1d339ebe6111f5fea22de73a8c09956aed60d1f2d15ace6cc5a6598c18e1703ff0367a5bb9de2ea3c2f65215259edecc4250a225dc82c81a74ed327b4de91fd1740b8970cb8c6afa9dbca7ed507dcaeb07abcb5492c142b6aabcea07c935f791f6950cbfddc305442fc9e75e4b452113f12a9df7ffad67064a56aa5caef80fe156f915b8c2d94f485a71503f260d1ac51cb3d19fc43f555cdd2691318add928a690449e4345bb7b9fb8bff5af9253f3b8e566e02ffab8c0cef688f3d5732e520fef999570448ae15f2ab09f80af330d4811429e334c98b6ed5e9ca2c8cafb235ec023c4311884e2c29d260492ac12ce8e83c67382bd9c9cb68ac852667122f8a33beeacf6ced613171c3ff96c99811146834866852bc2f8a65309bfc63f61b1b7d3e8e0395de8cd803d73af1c67adb87c17732ce14326540869223bff02ea9b0d0b23296d5d7ffb939bad990a7879798046b9dc263d3141757aad0ccda4d748b5aebb4cb8c9d3158686cef197e0aaf59b106007423e8594f489220ae29f4cb27933abfb883c5e1af5b405734cfc8c06883d9bf30bd986fb60aaa115de0c136ce74e0d1780fa7fd5e70548dcea385037b16351136a036a8e8458e266e67602086d625880b6b8e9c5112e1ed164b2b680741ce6a7a1140a72c0606e1bd8cffcaa7e9d979685af9fda84cbc245d57f332cde3959bc2e24e742a83b44e396b7645b653ad744f08daadad5f180f391edb84cc1120b98f124dd0558bbb32d5d26995472c858280e7113cc39e806471932b8e16589e92be972378eafdb714914f86ef4ba9eb32464e5e1148b58c05b4698cfd5eb044ba28bd15a96063a1e32c50680bc4a269d9446cbbb65dca00dcdf150846eca2fd3781227fce385752e979c65157305cd6b6f8aa48474c46208b43ca496496ee8c5eefe90fd1d1aa22ddacba12ead2dc830c63dae40c385697fefd2c12148ad5018e074627659860c2bfebf53b604d80d8355e13cec692733aeb60c5995b52160838aca26b3f5f9a6b5383975127b224b6329716bd674485d88aa457d88451aead666d59e2d4bb5892043ba95fd8cbb13fdea34604191f2b9784964ef75f4c18a1b23524f0f9327e6319c4a8427d5a24e01532a3d67f482e215a1ac84f68915a5d21f4ab05dd336bc22ae84df0c7cd637665b4d977e193df3c3292e74ee42ffbb7e4cff8093b6c9aa90bdde2d5a339fb8ba0232b1f4d3bdcc5a4acb03f6220e92604155f84a577c40938e97d2cdfd4cf8517a8e53c9787cdb47133dba7d0aff87ecd0490dfcabd4ceb8823023eeeda0c9b0a6c372efc3c14f77be0dec8c90277d128f95023da4413acea286eeb8f98f1b9825cb57c34768e42e2940e2c318bd52a7cab8c3644428f84910cde68bc0828f0c793b620ec6a0c140dc175b67508fbda74057ceaf86eab9eec7a717199187dd3334d79ed4d475617ad92514b81a030d764ed8b508d0e65b727ff479465597e31ccf2fd36b9e920ef562f0022982d03ad8b32861b3daaadeffa0381576b4386e0ed982fdcd2c950002f4af2e36a193672f49d1a801363d9d2d847537911a10ff1e1468f9e18ebd7e07ec9d24436f60860a76ce58ff988f3c4590a8b77e0f2085c393cef29e0e72b78d741f151825a63dad3e3d9b94f1e357aac3fe97c10e9e8e338f3afe66b6211f1e6df2d72878b96fba156fddb7a0f5cc0504a7a1ba4383933b238d33c5e315ee994603dbcebe643e1c6d48e1cb48650e671799ca54ba90da7f9fd7996cf0dee9941cdae0b1fe7255b823137c671f477a371bedc55e39437d247f74f5fa7eacaa46e0d44af761e1801abbebf30ba588f26128a91214f8df674434f198600c8c5420079a8a4d7444685131e6b5c2ef53ccd5675e9c04cfa932905da64de1e759577749ccc34f5ade9ef2a44e8ee220d3d5344b2b9b499f405112598047dd0910db6ddfaab2d145583744ddced255c1150559031d0ceae09e0eb3afd3e1abe0df4f2cb2689b7e17e8926b54c1699953760d9be714b0b123814c59d3a6d791d491639104d5753ee8c6f341953684df0156a83e9eb10cf3c78a572648dd91293afea5989854e773cd6fd707c5446093874df733bf606da2aaae997c704c37ef4896e24f952c00fc173ee4219092962b9a9b439b274b9031b589ec0467ac35a3d69090ca08135f1e66693efcaeac0dd96789319254177a230551c525f8edc66d3a9c8a62eb8b29a624789f975f02d570e6970a8f3c834390ec06fc3b33891fa827c6c526bb7dbc516ee5757afeef2e141704f3e2767d0a45444312c1f755ec609ba3b76742dbd6c0487b0a988db81ef0825723839a9988647984765b427f8f72e02223885a65b30963f40fc469b8cda90ee5e0bb5945019f3d7edc398473969baf18ded0b1e011500c5a32eb1af61122895cc60866cbd36d120039a1b6220b8d2e57404c73870a58d7c6fa1553636869e584625d863f5ebb702190a5805765b291d156a06a8c4a9adc2008bc085d88e715a8ad97c98bf147b19daea3032aae27d2b23d195af5115ae64164fe64692ec408c101c56f193192e54b80546145670d43da006aa087fc34250e76954e0165641332206db1dc859ab873f02826d812973fbe95b82d3804657eaa00ef1bf0abf9cd97d0c6bba41a273ae4f5610f6fad34ad6989705b0ab653f603b07bfc52dd7722b125b6496fcb6946cb3d66b8d13e24cd0bdecd7c4c9ab699dc77dd292c2354460340e3ea57401b0a3528b8f85a2c99cc981d0532c742db8f8a73d75d3bb541ff6bc4c29fcc4e6d90d6360bddcc17b5f403de203e3e453d3c4f21b70942c58622454c9654e712519871f7970da4cecd529fc8c2cd35589554d69e890590088522b527730fafbdf047d1bd47af1128cef5e60b327a3c96a5f1b2c7462c2dddb36bb240d5479d3c1a6b353efc1d5da960f9f76c6b8ea0a3e5eff83c4f3ed4d046e6902020b07cfa577eb86915a43b0328d7edc1f58631f235e2ba7abd2055e0df07d9ea8a6b02bf0e2beaccf34e75da3e332dfe4dbff5d9d1f7b16933b0e5debd6a68a84d3096faf0937bcd32d05013d882abb31f79d782e450973364fbbb5c4189f375a660e6cd4cdce870a45d8ada95bbd72fff8e085a9032d5b8eb961784a730ef8171215bbe023945611f5a1ada0f27c60a384e931d2646a1ffb457b6b8b4e0b264ef3dcb256953d588a82024e271f20bd6bcb9e0880b60dcc9bbaa832c64d0a2ff6dec49d7909a9fa0956dd47b6d973f1d9a7bac1366be23fb2fad0f669b6e2d4c6895c899064a553ec7dc61d2688f706fc04f685b172ce939db5fb54f5007ca8cd0de1c17349b4d4f50a5c4277668c79da96602db8861d21b4f9d8819ac6896e7c2128be02fa146d6c93df7fee6191758f1a39d94c227a39e8f5942266f622978db11f3d4cfbecdf81171c6363ee358868465060e6b44f99f06f9ec5b0c5828e980e90d2e92ad8837529ec5d2faa5d5678d4907bdb3ce97ef4c394d00519fbbd336c13359354e3110507b60569783e73fbdd258ecdf63511c05336f8f5b1dae8a1643c8528f8dd82f7006709956671dffcf2f58c50e054574f333a9752fd299f6290ab1cfe85ffb7d9de1a6f8100fe022ca51218e5861f4af69eaac6f0e93b21a0c2b4b50d3f24e8f31515fdf6038ba29ec9036bcc7bf1b33d9d8f4ee87ab3d89c4b839a418a4a0c7d2abd346ccad9ea75989284ce32b18779eb96e4f05299102e532cad0b77d1ca6ff5ae282b0e3f1627932b3321fc6a0ffa25049353e92a6d4946c8f0522da46678184b0f6c81f3309f19145b7263421b1814855fc24f13472a229600b6336649dd33d982b3b053358b331e489da3e342465f041f87f0ad13d05fbe743be52349e6a214711ff56f271810a8bfe5826da42033fee599d3c7fbaaf3873b9f0698fd9adb11def0143bd7c5e2d49e4c3498374a5372bf216e13b6b305d6f66822061bcce8893aff4489e3efca5d909cb38260cda42ef8bb696add718595b54fc1fd0fd51c62a9ca6834e3650285be56a2b92a437da0e55bdcf15027e3ee01fdbbdac6b85beb5ebf79967aee412aacab0f49c6b21e315109a02a361f43800fe0a873b06bd893663d3c23647b49636aadf7b8c3cbafaee579e3ff68a757d888d59e086ccead2c04c9db7f127737c641a38d24ac971d8e309889e22d620579449855a56ffaca16fb9ff9cd9b7c01b4652a98a0626e5eaeb5c0ba48b21d5c7b1c58c12602f129cad297e08829e4af5a54b6c6a7ae4ec5d1b4fc303ef8c79d9086ffd6808ed81cd3d57962cf3ffdfee1021e776ddfa5f83e3fc8d9ad4beff25bff4fd022e67b6bef64b7d7c60890900942a62fccdc345578a3c6e9c778423aaa50477f6f5e2d4af1d80082cfba78aa8dcf76dff17d88897582eabbcb9aa392f5f40ca61d7f9c5c4a591536b79f4503c5182891048d57720c8b9a7bcca3500b969d3be83fdcd70f6e85fa2b2fccab7f6e4a8d386caf123a355eb34ac478ce1d669272d9a2649db436bcc3d49910217980534cd685a2993a1c1ea1518651ae4981e93867aee8ede83456c32efae7ee1d4542b6f0547ad575c0a5cfcb7947ce91d96f7d0e255c09c1075f491b17e90e8c4fd57573db1ecdf6b2b63734fabcd1be4b9705db9f2bf7afbd9ef37a10fa09225bc96a7cc26cc39df847c150df89769404a128fa2a2c8aeb5d445600c268c865695b797adf1cedc9973b52d6af515038958161d4d50b4d65d6c3cbd8be4220d423913908b64c1740b746f427218f5a3a2ad8aaceec7ff052508ca9dd2bed2d6b10b44e1e445f58e239f3a05a5a6c77c016819dfc80ebcda88ff14f801a7149f1a59ce55d6ddf7fcfad4f7e2647542593eb92db00c17a636fc117ac01606003f6afc8c500b5d2d92395028ab7fd588e7716e984d3ff2cd8a15c3020e70e01abb34fcc8cf479a0a0dd99593485c45138e085f0ed619a980be75213ac03e9ff35306037e32644393340f77ff6664690176f5e48c6751866cb8f6da7bb0894aec9501975280621209b7ffd08a9905e6f6a04854b198aae55ea233af1098fe6cb3f3c1ae761e50b600ae0c4215d74b8532a344fd36fd093ebd9bb4a00a8ec81baeab08311ba37987255727e7308a14bccf92a08fa6872e14ea33b8f16054311fb76fdcda917dbc95f6aebff8dcebcfa3a8052c7f4f3c21db3530fc951f0e516182bce5ce1ad4761ce17d8c232cca6bbe60cffb54ddb37a3ade0e2b2a9e8e3ab030680b189d5c751f8611f4ea6b066ead818ac48b02f0a0cb262bbdc7317aa841a549dd149ebff9a671ed21f5d7ae0e43d8103b272be48a0e5322da88de78d527a461a50fba649a6bed78a1aa23fe9d9a960ad9d4d18061918f2f66cb7b6fb8ef977a0b1d0bd66f02eeed97db7b52e3014a4792cb5c71933fd1e2047b7b021d84cf58b10d11ffadfe78e643cda41036ec3dce0d15b9fffbe304324b2260abbaba6acac1d56bd7a15d71aa8e91c2d930892b7bbc03e0fcf533edbf4dad1334e4a7de0a6be3e15f05b5cae0381c7bf90428ac040f7b3cef3cf043c645bd7df0728d374a7d4e1907ee470d814c05e81553b4b7f9c5181cdec97c6b86797306640cfb1900b911235ea0c40d0e70b5dc32753c7169a634209e990515efa85c6902cdeff95df464440c4d3434c23dd1be669dd7a799665d899d59a2e9bfcfada02df4ddff7d3557557181613c60dbe661936a778bd2a41486521c68d97258dda68aa2a7c6c110231b3cd24020390bcc7d67ab147635ea8631f0a2ca087a437d903e63633ae890a1b296e96ef4edfddefd86178624230b21e616a36f5758f6ebd9f1ff2f73b800f6a9395582221fb1fa52fb8699ac876a182916b5c82cf7d3373b4bf47a8c0c44628a9bbd56cefda0737f441321f481c7611b59a2ac4aac1becb8012ba067fd58602d9edc755da7f7d75b0ba2fdb98e6f5d4aafa684064bba4988773d5aa00b7c46d6de554ef6ea4b8e916013fb65cfac7fa5814f861fff27294f5d80cbe82dc93fae053cbb13df1a8322ebc7fba830ba568ebb19b08767c2c113f35ef04bc5da6b066b98a252d0ffbc50bf0d639c759f34d63844be3a6a44776ed4fba872f5fef7671cc0d4051aa2f191af579158dd3619d1a73f95d33601b7ca03791927f6656683945d04bcf11fdef06c4e5a52bf8705379eb6dc99736cdc5958f09453f992bc646a6be2aa17b5a6d6aeb665a9aec14ebe193c3f2a334724933a2c6600dc757449d96e9bebc4023755129e26b5bf29e9188d44fc7be55e5c981ff62a7508e693e0683322e7b422a290b94d654580ef57a75a0c6edb0c20cd9e1c81e54582a51c6dd620f238b9dbaa4d02607775ab04c6f3755af1f1ae33376c7448d88628998fd837c133284d009b19f7a21cf55e07feef6207300fc0f159e13919a75aaa45bdd2bdcc95e52681e7f5b683c36c0435cb5338289372402d36e40142506d8767c272907adce660e3750d6bc38c10d935d701641565b02079552266cfa16ffa68c1f8f0adb2fe39934b901f0a974828fae53f8b3cc456a0537a50f260d3251296b8dacb2d268b86c0870378d366f98d7b19fe9c756b666d18f276be7bf0dcbe829b3c435b00deb4d4e47ea025601d81fc7ab6d9ed8900a454697ff51bf13d0800b91a9109f08d3f495e016ddcba34417840cd2e331bb8db8fe5d9d0adb0debf67021de4d8facbff62b5c6e983dc543e14a271817c862fbf27fc94e22df0fb85ef38a3e862882cff4549bab195140d246c0cb434161b0c733a2c56224f087cbf8b6cace031cd9696cca3439af07a2a5292596a66a9453e3ae95f778e6006b673a1138ad77b5ccc5c48ff32e19772a37a8f6c245d29c5c15d4fa2578ee1554ea40424ee1190684fc5b1fac1463d5c66d04e6c2e3ad0b7cabd5586c7dae38986f5e35cd36cf15f52f0de9184c7ffa7e48b85b7cd50ca91feb2e01ff19003f89fcf9bb794cb795beaf8a26cbfb45c2be5030482e3b9e0300dfb057706d5463622369818f61eab4f44ca70ac96777b9cb5bceeb9b6ebc223bfec65fdf2f9331a39787f8fc7979ddc0cdfbf2dade9dd35a68ad641bf33321c65dea71f1d99c92b8a05e383ea0c11ced79d8dba1dfce8d927a103314709390bfed9628c653f1fd64f3af01aafa30fafb251c6d563759456218b034f4ad85dfe75792e943fbbecc8b144f02069f30a0e73674879b1dfea6dc922adacf0651f9f134885f194df5f1d75e2911a261548b2eb272d0c59a0c043a01fadb56f7b22ded6c9301d03064fd8ec26d13a8509afc814bac0b47a148c3fe505e7304cf59b50904e73e6437fcbea714b3c67ff643ac6ba95f6651d685b1277b4f9eb2d613402ca1296133a2002336020a4fb7ff5847c71a92f133ece5e673e14441957215c405ab52954d76d60f12a4c47888f8a0fe11a46982405a9ddbdb7ac66a275e6fb7ea3cf8f874f2b844480e79a9b57142f80060f9f05e6b56c87b5aa4cb5656638d05e79eb7266ef4b44c2c6a6f0ba90df244cc3ca6d22e911aba50b5f4e49b7eb0c78863983fd5a5db9a89c2ba590e2e2e5d151572cd5def399b0e443e2f4c157a9d41486054c8bb093a9ae7c520e78cd51c6c0a903a515e447c505d65e62b9cffd58b50d782cc9b8a3fd7b282dc49826713f57c22677d3312c4785a998a94a33e2f5d450c325b7fa983aadc72f461e1836ce3aca77b9023d7d44efc65644c45ff5919d245cd369336735c93778bd220dfb3b5c3ce03c6baaf63fb9b0da5a54a2bb8c3f89d660fd2f00e3f071648d022e12cb98fb8aa7cd3ca66946b6de91776fd3d7159acd8404baf4e21512265ce6af4624de22771bb8a10625e9207f547687130f428ffb9e55a244bba1b636d9fa7bcfc97dde57408b6f8e249bd0421eba4744ccaaf1e3518d4e932ae0fda7be43f343d2a963acf4009753148c650d3249a8a1671c0398348fa59bbdbf0744d827cd6cf261d2cbeba7673dd7b322c17a501835f3063df2a6e5dd0e185007d192a496a41fe7108b2e97466d355d7312138667f6c6ac066edb1287f9153b5a4d80eb1a9f3b909656fa3b5c7ebf9d0daf3c9bd7dffe1895d0bf732b8622ffbb90209b18a2f1f5f01bf1d75ec1f0e0ede71c3a6d960840ea26545c5c5768cfed7c1461279ac45cdd3da2c34a317f94ce74b4cb86c43c8e08f651ffb8a954b70d0833ee56a393c5cbe770ea65011b3e757f21fb6ea616aa50feb08d8f4f148ac9bdab94ef0ae5a0361bdb58545712631c15c238d57a6e9aa37fbeac0d2adc1ab810840414787d081b861af0ec46e855783f24640f83a13847bbecead1a1e4198eca8ffa59bcd1a364fee718bdf937f6f3323913663d673733f4928a31487df3be1c0b1ae57c90060bbcb8a8d3ee96f03b4fe79b915ebed70f449f47e14f160c2c74007b94a5282f48f5126e348564de8720c114ea648eaa9ec3a9ef07297505ede675664db8457f90f4776dd22a29c5e01fd92c38a374446a75523df4789226307218585ec60f952c8369cc2f3e3476cd400e47bc54831360bccbefb1f2176c616d81532b3327d33d85ed7b61dbf712c7bb7051e85e30dd9c74bd3204bf3a57051b2cd245c594df99f2cb452cde0c33c037269ed7eb0f460884e19354926ad3c3bedb55badfe1c321cd5f532eb17a7e0dec6941209bc93de632225ea5188dac59f1a8ccd44efd083f80e7d2ef4ece349eb4b572680945b831b7b17c08a35b24c96c7eafbbded03d83a9705b8c7c83c401f4eb894ac273e13ade5f88959115f2b6c2b27055f567fb8ac910cc279180c8e6716f68cac6cd916e82deccd18a715ca550caf1882ac0dd2b33bcb2f991b6bfdd7c58c1520b0cb36905ccb519a1a77e7e95fd843ceb8610c22aec8c05a4a9624f529ec9853bbd26057d41f66749afe87581ea9a86d674fc79f6e1b914a7552dab2804ae365e88aa1891d3d7aa44cdccefba40aab3d2f34392f66aae4c7cc4db320212f682b5d7b86fdeb2698861294a4b95de3d5fa48cbc10a7be772fcb4e003c6dd7f48661586e377104bc76f592e07d9abbcc3849727acedf60ce94a84da59153f8e4b67b084d7b284e9011f46f23336ce84bc0aa840635af4647c1ebf427734991eb604ea909bcc1c171053d6843ea2b62f8bba541cf456da68c0cad55831616e2464b7fae386275e2a26d92973286520ca3a367f8a109e7512b37e0a26aebd322647aebc28af33256a1541b34b8b9784248ddc8a36b09664ea5d5680f971fcd9e364d0fdb2e6f006a67942de14a18e0e1c306dc2ad8ffa34f187fb45747ebc5b01c0c06dabbd64fd39f8d2837c9c9f4344e6dc43cc34e79650816f42e562dbd3d14bf2a8d8cc5aba029058878ff5dbb26f2f1c1ad68e3c13bb099caba601cfa2f985d111ff6b245ac6fca8a1a28432ce7d24df39ec57c91bcb977bf795f7b7b204c3f1c7675e4984d8d257833a4af2af25093bb4a13b2673e3afae024744a5b7f05a05f458170e79491d4472e8c0c2abe7adad7b5283a2a7a8bfb7a56aa080abfbe8a1b2868bdae104850bdde9b7840c8bb98cd057d8c8e2366d6a07ba6e4fb53d0b419031f1c9f565f228eb75c7dee303f82a63284a796fa61e398c56a37ac48d25206fd8e0a56de0bc4be2c3209ced64ddec16ec083fc80e8d4b946efcdbb3ee60ab42c9880ca878b1a129d264223c7bfa574b3c6f08059881c1fbae143142654748e44488cfe0d3a69b630f535147323c3fcc9dad4a009e4fc433498d6254ee9b9dc8092030c2e20031c77885365eca61ce5d77c93e607a8814e010a395367ee55298cb80e8d70e18c987a1f1e4f570e2549def43f26559c560e8145e7a60c9120a14757a900391a0d170a95f2fb8eab9335dc45ec6d3187f99395ae75d5cd9db5b57e9f12f06e23c11a7eca11ad0d3f7c5b73362f5d57a05961c7aaa8ebec174f02c4acb8c829ab40f60b6bcf31847cf6e15cf2709da77246e96b1c20460de4046a9dda874936236ffb2b9724f324e317b579a3fb33361184b9a36015cc10c1ab714fc8a79833f5c18f3b7c0cca56b5d150d905782d3589ee0a97ee23a4d960d5bf50586b830038fdb595c5e27fdc56df87e9cfe26550574e61b7fbb050f8d0922cb699b93e5991949823769f1977cca1b67dfab02b240f24e9e42051447030b1249b123b42197e6fe7a3c61902314ca37f51bdd16d79051856abf168b9d14bec920ecc55e66380c3b8b8bebd21e7fab7e7a5f128ddbcaffeb108fa5e960b5ff409553dbb62d12849a64343cd5dd0b64d608e6829e3a1c18a8b5d98535502d2266fbee7336a9c307a4d48cd0f3836c92f74eafbc9d06bf18332330330fa5d2927f733fb19e259ae97d00fc75c62307664116f8d229c32c5692db37663d571578030777cb43867245678ccc2d6ba5f090b8192fa1877176612cb7fc7e09669929fa8c742758483d920ad0211acc44c5d0c6b366be6ea619bb0a3a1fcc74ae9eb363cc3fef4dd611a0916990f074b7895daa28d4a25790f4f3a6894fffbe0de85598d259a2fc8408b8b6b6e693f2423d0484fc719f757b498c23eecd174bbfb53b61a7354504712b830e27ba705a16d5e6e58a4bfb9b2ec06bf08f1cfa218a6ad8f22b790efa1ce054b00ee5a75db13b29261c3ef8485b5962d6c6a3a1b8116cf6ebfab9f8217b8a3761046fbe068a5376f6a0fa88705cf2bc2916aeb73408a5434763ae195cd41cdd489eda047fcb2fe0ba6553cac2202e2ab4bac1eb73b458b937c644b94b128ca760cfe8f8ab1cc6ec5eafa7fe0b92377ccb1536603437f36813f3760e181b13dc16afacbd344273f2e4d3a73e74da71e9754301225c0dee720acc79360761f1e6967028d353bfa38fabd09bcfcd694a3321e444ab193d47402aefd5cc006d1dc97ad3531061bb65a0f9da2b7771a9378d904eeee8771124fdcbf64b2b456866ce10b909547994570e64930b2451d66e8299ac46f52d3b2c974568a87efe81ac2217470d931ec798ec2583a20d0a1e04a3d6fc007b2dad7b6488b84d65f6fd1cbc7eb645e4d5b1c978aa8cfbb01e729eabc5b5439619fc064a153843b4e2090dbd60b2ed9fb85f4cdbb323b44e95551fe4ed8b6f116bcd7808eb050c2f344c313915ab700a8deb7de13ebb822461a85e9e1d2a196032ffea5cf0fcfefacbc317318d445724c970ce118113b69d32e3d9a8da85b351aafe3bddddd8ebe4700d9cc8bd65338f1c7057333c940d466d3b300938a8b5b805965d4be677998fbd835118ebc73411d8b54103f2c359fea95828edee132f6e8d9f022db59d0888eff602c4e7707f77913057fb27ee4428f09e6e5a347e661c9e51708a34f9a19c4dfbd7c8416094f8dae88c2b0bc41966816075c0e29a1bd3a1d6d22862f3427eaaee304947ef953ff8c0bc3f9b7b5efaab5be5c959603a88a8ddf4284268fe4be723cea9eb91f95e3cd7189fd3f43c7412f393ccda5cc1d57da42618da22d9a28c793064eb3a3daf925d946b683f886f2d5ff93cbb5f17dd6d7a8694c090765aa665191957234e35178ec85f9984a008f21ed804e3e503ae1557083bda79a2ce13d0c9e46f5f46675118f352caf511d47e8ff37e441d6496b9bc95d31b5cbefa0b48f1caebebfc40431a288b2b6085d8672a978ea4b3f4d32908cbe9f7dff8ffaa9c3fe0c9b1c7acd0a15975a17d5e9de9cafbc105c3576f406070fbdff2bc96517448e143419c798ad69cffaf762d8e56dcb492b89eb886d3c2dc66df531e88551a071c6e3d77f5f100d8380854c0c4e49d85ee474dbf2fbd70837a8149f1ed840b4a0a8d08673d85730ba4921b250b7c50f26f32c4993dd862418bd9f6d4f490f911d79dfb2e878960b2f9193a5e0d12c075fa2da93f811ff4668ebab40e6450b832baba315d5dd85d2b19d167fc83bd9c68ae8d61b9194d8ed02e3378a7d8369dc716fb2dba5d896f4c0c36fd646b0d8d588403697bf5290b9e3c64cabb411da3a1a43438395ea1b9b06beecdc7405512b27d115146c3b530a01cd463c54a2413cfea12504d4707086638a28c6a0e951027d75caf90e296d3bcfb34af8d8feeb5bbc9ac2f33b317491a81f88d9c19f2edb1afe399a08accdaab0da32fd8750712228b2fc7b3157518c4a461da51022b7dc420633e2ef8f48e4468dcef5ad9fe9efc89ea9b8724f2966e5f034a5cea4fee1672c6b6c5830a6debf2450799ea2c0481756c772956a0aa2ab7ce6f4b1a159ef99039e02c40ffde749ebeb568089ba01c3e13854fd4dbc3f1233afacca2d2ccb8b9b2afa8213a58b018bb7f026fcf26cfb14374cf6b5e5328599f5ff3807e6435ebe0033e18297e5b4ffab9d2ea4ac30e843932a8152ac95bfea0d141ff02b1effd4078ae4f50255ea56650314b24e9d1beab6d0f6a46055a95e8bf05649073bd150ad1103394354463c0b7368f02ca9f66bf2cb8b2ffd1692d8905c06b48e0d402fb10032ca0f05c03fb821441bbf89f945be602c37475ff23bc96299d6a173048d381c727ce383155f431c07c52aa21bfe4169abc0f6de0b2cb4ad0cf0fb133e19e3926d696e2dd2dab2656c31285a23c36c453d243297df905d19071d8099aef3dd009ed112e893b63c46e54e7a588da0beb4655466257b1473201eb3f89bafff6bb80660dc24d9786c050181a749217dfdef9c47b3672ce36e667cf75e4038a653ed441c13e8b209930c9a9f89cbca16fa14801c28c088b83fc29d19d8412524cee113e4cf3013ab70f727e97f21c38f69fdfd6e2d43fb7ba5bfadb7b9ed96a8c10051ba26fdcf4ffb089a68f0bee04d09e803836d828b971b37b76518406f2fe6e8d15b5bbaf2a4207d4da91c8460eafd1ad706384d7c311c2e06d4277d5865ecf9ab06b14a2e69ef19cd19e34d8910152cdf13b3e3de3084b78b5b9f9386cb2270296c567e8d466b1effa2855b9aab7793de7a5943fa669824aaca364ff03988b52bf9832b69b35978bdf8bc9b62053385c06eda11b7d977b5cf6e890b68b21982171f0ff4e53edfb3439cb3b0ae5c0d3f64d85d2af8fe4e265050e3e5a5b28ef7e7e4353f75178f1f5e309b5d3c70b8fcbbb9517081833fe21caa66d9f4c282553b70cb1ea8e8a3089ef448776622b707e988ce34b0722ee117571f07c352fbb6d26a5018d5f3ec7b634e2bb008c86388256347f54ab01234fd60150169d83d69956a4afe2458bc9c2644e232abc41bf341cbc3363fff8dc3c2574f6be87c7686a000f5aeae487b6fb571a4bf414be42072b67a34508488dc919afd3c9ea67ebb54ce063ad3b45b8d9f071b90795c5dcbfad6e9789088c1ecd1d87857bffdcf7366fec270f09e7e672a1f55427c23f54465dc377cfaf810a6c8a52d1de37eee371b65753c54a0c85ebaeff6d8555411541a67f4bad2bf7d822a5f799843d7b21f220ecb1e4701408fcb4372651252a7bf241169bd26e5ade03ee993e1de2ce31b11eb716d5feda97c0d41e1419a61beeebb64b069b8d9631789685fbba805b9e1974c11bb5434cc39c74eaf367c9d6cd4f177af5bf16e16bebe0db721415fd8d366645146b59f703136614b7310685d2a3da3cc8cdb6ab3e50e1fb44cd3dbc20fd5eddb2635b8a236059069f8c0ccf32f6ec642045af54d83dd003054e26abb1f68f08a44deeb75514d07e7deff9b21290a6960a429f1b2e030bbf56662e73f73c4f5574f68a57f8d0d5923a4c566205c7e208f5a5b5d70b021c9d8b8e26bc962461eb0f3f809815fc8dc3ce3c4d6210094ea274b915e91fac6320ee1e38edcd5e09340e689c9fc949b2554beeed0cbcb8b31c77afd09ddb1763e32e86f550b0a6037e4c5e95935456b249a7629c6b901c22656e12d9377b9185a0c1d9b77486bf7e2267741e90b81aa495f61a860a042f2c549b8d5317b707c243320526ff7db20a2103fe9e28e22b76e68161f2de6b447ba22e904f7940b7bb7ccb7a1a287d9e64710ab2399c513426b66888732b708492ce4821bc6b186bafb268ad7d5d0fedd8d03faaaf58645a05be491cc5cd03242d989ba43035d05aa1b1ae9a94228d3660364873d3920019b0cad741001f9474d3f7085335fd128ebc76a7990ec0dc4e2102c5fe013b71281c3bc0a1a6769b46f160ca0e9a061b28e97b5c07625855b7e7079a0134586ee1a5e99b25509cd3914f6a3017898e51e4c2fb07adf1696ee46053f448f83bf7fff3a5ea848d8ff03035feda204e68ef1832a8f64f2fbe1e28c64897a738053115aa877b5811fe78aa3e7662e6fa3aa078b8abe0946b048b3f5679db832d9e43b0a8b62fee050ee862fe27a7807c2f2b67dfcccf8dce457c24e497d830531ba639be06c87a704d9534094c4f9b5a15234eb2e41600d8df9bd4d16f8df59bcb18453281b4a41d4560dbd0dba82080b031e77e6844a5d7637cf9754c431886dfa8c92223b1a1bb058199dd3d1fda26fd77c1daa03137df63243f70fa9d638ca73e892ab373579263e10e83a557e78670ec887502ad24c7c307f1e7aff50e3df1a69fa2957cb03e3f167f6114d7b4fdb66867f3db2df0929265f94f80d0a52b0825b33cd9645f1cf635f749a7613c90f2334ca644bb1050ad307a8c1ac46d65cbedd97f169440b477d126dbf31d99d359c0f74d4203e5730cef395230a9ae45036e4fe5c6ec815321d2305bd5ad804821f599790d320b73d25779e950d77ff77eeb945ab22ddafd68a15e638230cde2619752358db26247697fbd9911a5f1879f93477381c7c9caf59899b281c9f83064c98d1f9d7726ab8f21801590f30b53bdc9eabd08169417880b70156fa0a620c61a9b2df661e97a96b8ffbc524621fa2e3d47d87e30f232b13232b282680ff8e733eb9eccf5168bac24a36682bf14aea89419ff0b1c2bdf0599c36e4400498d705b1e4a3ac578f170a7aa3519e64485f2edc876e2f1b3bb467f3361501ec7a11b58ad8ea09ff454050b08b39ff7670f568a1ad9b2bcd983c82452cc6ae322e2245d0f3d81ce50f32ab2abc332ecc73de4dcc24e6d8d13e3c749d62d9ae174a270807484f65b725b630e3b9e80fcebc59ce350bb4841157f70e6bc1a1f242b5d57e5d917a7e0e5b7830f63ba34e6918e5c176e89bea12151538afd26377c374a098ebc4dc976b76ab154155efbc704c7eea9e5ea6a1536f01c4c335a1af015f1e901f9f29238ff83c22d93da1aa0836a4a04eaf9963ad13571490e8625936f7b06203bda9f3b9f679a00b8d4d509744f0f5d4fc827a53217464e366dee34bd3afb730c5ed453c27d83a93047a63aecab43f64a74f373c6d40adf55bf0e7e58920481e92645e08d84fe337fa198b7c89aac93a4ea710a11c37205d774983bc82da40885b411d857a3f5813e81dc13601f28d26d1679f6278a10fa589cc011415f00dab69cf15d57a1741c40f4034a97e4bc89ef70021083fcce31980aec695bfc6a7bcb94d0dbd7f74c7f1322a8e99adfdaa3bcf35e7fcbc8117f9cca3706dbdd16dc7bf6f0590ba3fc4aadd7d027b3eee9e3fd7ad5699a041297c61b18bcb27b99d4c3f001ca1ea6bf7136015a553fbf8aaf7ebeb419861deec72a1d56c4db54d35352aff7729fbdaebcbff777dc926a52d8332e6c8b99ed6e3845e40fc61c9ea3ad6a87568561cfc3556bbd6c68186bfc479191eacbf4a71ecd95887e423e9e327587aa6aca144bd11d38a1fb2c0f22a705ba8a47c9d217c15706c699a8e0595c1dc3fe2de5cd39f677eddd8f87a17225a4023c6db1e9d3dbc0c9b01b2777fe7dabc7279539299dbbfa6cfbd735963f291cd807d8cdb2641e309297d9a294ba3c75c0fd75236364b736776513d6585dc28d57428ced8cd6aa8225fd8afcc9a275f06cf5ae77c9aaafb2708246e769320107e69ca2f7fdfcde474cfcbae2fe75a2876afa45f41f77a1f96888da603d8b0a3a3aac9c9639c5401967c9ff846a03187115ac1f9f1fb708bec6d28df7b5ff11b7b053e037305e0a4eb7d1eaaa5d0b97ea1ed6cc6dceacf78c8f22fcff0b43f32b615903f5d50d41c9c542fdfe30631e14427b91c99d8d829639562f772ac3d63a6b3f9ccf2a11aa4457b6876eb3bf06a155a09b991e20014d7e5c3d46dfb5caed3ae496c5d179934bacdf7c89ce81892c80ea07f84678d226cafc0aec913f4848018f9979a83da9c56ce0a9f2c97948c0e2feea38aba4466461d890653f9870bfa7271a21f59dd01f7ed3ad06c0e379f0fde0908d406a0b98eed3f6e778d5e6b53f66cc8c2313cdb76d489f7d3c62a721363b2d6c4657f8b3e37932c8a24265086b0c859d69c9a5f0bbf9c38ab4ac3db8f33de2bb5e5b5f3cc325f6ce48dcbeefc025bb034c5ea9624f16311f0bd5b76b13e55b10828d8950635f7e69c7f96d93cd59437e649c2e24d36982da692123e1ded8bfa94445365a0bc7c8a761bc4fe5682c51dd56a579cd3a4c18f33b981e94be1c280ef6b09e7ed6539e762279288a3e20b0088bc98fb328c4f0873a31a5d6f2a327676671798bef46e2930c8351955776010acbef81fb13c6540bd4a2ab47a527fd6fd284dd13ff5d2de0103451894701dc81244bf620c65de06c98f682b21d12a33fceb72bfa4efcba2d136c2a0736ad48f1ce76d6cf5dd4db4a11f6727ba228b5e603555acef5ae5c03a42ba72501f67963eb12e43940dfafcbed0868b662855aa97edbf629bcaf297aa4eb95898f6a164706eabe5decfb9486c54bcf15d7b6d5f3ef45c02e65652f439b97755e8bc26f3358c7b202b2cdc608b0d81b5ebb1e7c6471373a717de5caf998c8fe6ffbe0d64e4e8a5969351144435c34d53dd23a414bfc9d90babac0375b222f25113a884d54c22efc0ce13edb06bd26fdb704b5dc23d5ef2d293a2875cd7cd064d123cda38adcad14a56efa7837b8307f599e3ef499552a94d1ed50a761fc4dfe7587474fb6c2413ca70238567b4a853a14c7650ef66f817958e1533eeb20477e78eee6d30b2425f7bcc194e6f8254df72303eaeff72ab8e38c4cd96f2dbb648bf8feb96dddd452cd6c19a55400b8bf67a95ce24482bdc757bb7b31f488b9d4cfddf1b81ea83300066ae844130e6ffca78ad451f16dec1843f6bbe33ed21b046f338c6d7b104050b39dc211c61ff29e0dc13087121478901a600db3be7afff303606617ead284258c887a524cccb0f2da212bd1a1e7a7f7b3d520c399265e99428f117e3cdb9fc10e05b93f97f89b7e6a723ed200c9dfc1bf24f703af0ed4a9e90e75fd6d9d44e27b707202f1752b3d22461f6963a5358371d6f1c1f1dce00c0e20396e2d8d9c480c4360c96eea0d9c11d6284e38e8b905e5d1d12c349027797d4a6131dba83c2ccf11a9965c564b0311b1a5fac73b636b40752d1fcb959321f1b70dfb0bfedca8facc6e7f1f111bf073a5be54622bd6324673577381ba532396e15f2fcd9daf98dec57f34a102fff0f05ae85b433af4cec26d398c209b87eebd389db8d29ce081598b2ae8e10716cdc292c10a68bd9134fea8ca2f61d8843b6a17145445a595ddd27b96b0a9ba2e59ba8f5e1bc7e557dd84dc0145f5b26aed4d8e7f3166679fab134a6ba7c1bfa1c32474b3911d8036df8cc430e682483ffb0910b91f690c7c0ae7579f4e3e2edbf2c214bcff165508ccccbf7b40898766815ae6e3449420ff165426bdc89c53fcbbf168496e2ed56e26aff583d1d08dff1c1182738f5f3b7b7061de9fe92f0cb4dac0019606c3dc617b32737c9fc6be56356855ba6aebb30af1fa1b7829df36baff2f325e17b306b89982394a4f7fa790d58bb33047baa2438f800345c55a5ffa278ee16bc7d400797ba4735e5f5b87b4fc53b1df86c8bacbc3b383acb029996acbe3dc807591c12ec1a3fbff1860e2a05c50433c034fdf033c12c2389672ec9d5ee34388afbecb8afe8313212aeae8e47d50af342a34f8aea2969a71e5c7e1fcdd64827db332f50006b37d2f1a1e6f86d8831ee15bddbd312e5af32df02e44c89c9895050897660d519a27050d1bf175328585fde115d5f5914f0bacd168e15334c4e79943e719f4e9982ba018f91e02c6aad4e225243c87f85f437613c3334a4c769965d9894913835e7bfa65f987f50713e7083ba767f585047c839cfc4a3bacf3729b7dce89194d5eb17ae2332ed5628615038f97d3c44a71079fa86bb78f42e49cb8ae7c14d7a744ff398aef037d20d5cd985e117ab3d191efad7e6e946c7fd1924c2246872fb6c40801976750195a3fef2fcbce90bd5daef13eef31419f9a9e46152fe19d583fb02ccb06bd30d8a4df16a53de70812b0b299ed2ccadcf93abf368b54b54ed205530ea4dc2c4bd486e734d82c7b9ce71fe58238dd5a6a3c9b803343bb74d52562eb6a4337479901eeff6dc7c54c2ac9119ca4a5a1e65654f2bf72b5ab235df889114712a711b87df02361358be09b0a5c568c253dff3ae6cc13a12c551fb99e6c104f01508b6ab6bed32fdc4e0d69e0c3db9a1c4aa56943576f31395bf9c45f2ae1225307910c73533ab72399f036c37b9e886e8176a5d3947be7fabdd890f1f7d019b16e6b48bfadcd10fb0321d0fe6d2ccd1d34e93e270a0f3a94d151a6a23150ae68889e7966ba2ddea434f6d31eee12c4f877c93b36dc1160ab7bcd6f87fa87bc5af9682e88e69cd40ccc6e363140841e57d0dfc43db5b8fe0fb0b25bc1cd7be902971348fb411f85af031b8ad784da6b0197a415052b06a75dbf6cf8ee82526cd252cb3f63dbbdd2dec1f413ef1fd7825c1051d041bd3231c63ef6bd27407c422f58e64d6f8e05b62b637dc3646c5f81553d4d63673be00fa690aa2a71141fb706ca6e72bc09534c4f27bb98f2985500a28dc5fdb49482d8d8e5100dc6119b2b06942e359fdf5e59bfb771d3db333b47b933befc8587fbd79c039e77bf5770aeead73fbefc05ddc34944d0dc9616af49177a482da9216aa2598e2fe060fa158355da1c57aa5fe6ec4f4ffadaa940d36f25928c1b4224ec707d036bc502290960570f00d841fbcd8e2ae451d1279ccfc922c05ce0d8ef87b008fce651c427fa31a127c827d8f0d35c792c5dae4f9beb0c4a0e8547cc0c80e250d2565212a9595e9ce1de07494f47ba23dd8790d09e3160523a8b5f6e6c2b724a570bc44c23aaba7e36017b910f11d6df08471a686d2bef84386abe75bcd5c7a5da5d9decdf959a2e140f58e8aa66989e41cb1b02102bb3ec0e29a7221def1973b9a8138e0b7fb7546b892151cfe02b6df3f0a6ee296c92e2a939803ff6b15e420da0e74d714436aa0eec22c70e2db830579d6a2d38c2dc77f497a2c5ef4b3a2de67f4cbe4a44c43b76172c198c41078046c5d250312141c727e7a99a8d8007fc85bb8b31034797d8e3edc95f8082fd262485242b53f8bd8fcd6c1ad6189c3b4029094db9e9c68fd99d9e5a81d6491d48997c99fb610c7752255ceb587fcacd0e94deae3a38852d6e3f603682f774abea7a14a90d9466088426fc8b2613af8ea833bd4c775b5c8f5fb7d2a04120d8ce26230d0d9ceb57c17749f7d6db8e777e39ea6b5777790291354fd3402c43ecd1044035e531253f8ec3f6d3ecb0276632dc9a3c55bafa8a713e9d48eb5d8db4963a4462f8bd3f7d28b2221c7ab257260c45caeea34a50001fb3bb5b5da032c0ea0da91ebf1eaea4b5153d89635bc73e7633262d31497c0817deb065056a5bc54f3b21651678b63887f1dd7f61dcc357ee9ae0be6ba86891a4b997d96890c6e17608abaf38510fa8445a1c1b67c0c44538a00d445fc62629452f57f46fcc078f0acf18eabcb2ec7c7f38f7bc4eb2ed39ea5a890b24b4459419a66cf1da8270538b6d2ae852e959b74c79d8ed554728e5d4fabfcf960720cf4018a76936e8456641fa6b6a1856202bd99cd55432c78e18fd2428eaa34dc76d1324e6caa153f0206e0bb9d001ff7211741c0aa1ff733117dc906706cbafe8faaacb070d4e38769b4ef967f330175001c205e9b8a577b0f415f03bf1d90fb36350905aa7ecc39ad7743e2908767c8f5143b0016b4be02cb6c72d52f2959dfabb69708490dbc32cdfb1dc50fcb0bd02cc7119b97e37f9d44b80a9ce99ba56e1e807f14fd5f210eaa43ab8c3e5c628f525a2c2626e58fb5897d6d4ca7cfbd7e93686c0514257e43f1b4be805951af950fd8622cfc1964271eab4336bacb8fefbeadbb159f2c673386be4cdf2d8c112cf4b27b753777f1e80d947eb3157ab12951265522bf0009c0584c52b4b8fa34d2e65716eca8e801576944d5e3789f371d2c21f28cc18f0fdb9ff68f70ed6dcc9f1ec64a99bd2fc47ef777666fc1571d6a6e5f6dfa2cf61eb4a73b76a14c597aefcb76ffe75312161f3f8add17b7d041207bfd6e9e08b9750fdb868717bc6a33045d1cee8a46a84237ab4dcd1bbd6e8b9a416c556d6c30b6488d557681e67e7e56781cf70b8e1bbae2e8d346d074dbd4b7accd58714f5fe5b84569fd262bc44465e131e98f88efde606d464c7c42d9f4e453cc46aee021ff4d40279f59ea7c42118bf3741c81a1759c674ba1ab5a4fa4792b93f26bdaa96c6044eb98873bc42790f14434ee548c1614c613e96ac261ee302d4e0b5dd5da3e2955ede05b72771a95e3bbeb56fa88ee281f1db7e41da5d67639ffb0722bd7f43d4aecb724dc91fcb043f961e27882e1f18ccddba4151a4b9a66233e4cf1f572a041e9051c46c9ef979ba0b0dd7d003a8923768fe86a80a6b41bda2b4d158bb8fa4980bb6898cd225f2e2d77027a1b9b46837ec43b9432c74057633bee3927b9b456353d5b63d03576420fb8b44f1101603caf406d33af24df7110ec29aa7861e10f2df5f07ba3ffead74f55552f6c7c12f1b1f1fc342f85adc4034ec74e0b31546d2f04a495cec7e00fd1e5f386fcc87e33a6cee17e90d685e41509be6e022be0fc75c7d826d219c00dbbbe56731f4f993fd426214d05812ec2aaba355fe534cb33ff6473e74ee2c453d604608ea607c9c0b646a920b1eed0de4ece94aa521aa2a0e1bd8396c28913c9458f06b72bef578bd91ae4d51f2ae630456b4fad920bb369717b5e63949110fa1178b1e048935d0ca609cc91280a3f8ff750e8487804185d85f014398538fda9dba156a38674e3ef5ec5c1cc41447f5eef2f0d3cb718ab6237265d8c85c8708296dbabfb3fb466791183a5862a5b3fb39fc19046320f0f35a258fa8606fc5696626d56fec4d34fc78b21b66e8e29041e6bf75ecea7e7db7a3076d712a75f1e5636d210a400eab454cd9ea2812d7c642020cbfd4b9e509a7c1e8ba9ab4906d89fce3eb46da9fd3880ffd8b939f3a14603908f0651a6913b16c7ec316263463793e3e5d4ab45023a8aaeca177d36af3b7d0488972dce904ca069e7204fa3c0639898a52b30c0483077422b358c2e0ca48ea920201692ce9a0ae32852e6959cf4d6fa7853957f437bdd343cd4a9789041f0e8205fa341607ff9bdcddd59620fd60f0614125e0528bdd6188b2e0cf31922d23e739087ef313be2a6034cb8c2f27895a757cf2fb5a7e9e40781111cdd2a4fbb3a3983052d3fd56d2b15995b58572281bbf7ef7e4a2ac794a614008e5086f07a6bb75576706075cdf6e5dfe7cb96d677b4bd1286ba178c327303ccbb30830324fb4c34551a2b19bfc618510698530c4f4b8e429d37031cb1b757541647797d2d87f0a377978c13be3cab863e18ac134220a6c89539530f9a7694fb49cd33082d03d82eb65d975fa1dea319b8d96ddd005f323f9976c7751afb50a16b3f42677edbca1514e4caf5bcd6ba28596f0c19cd1258bf2ce1f719f2a96552fb81cd66f4daddcb21ad52b98787f1ad953b6c06dded627a031ccdfdd4234963707b35536dde5958d232d8584d1084b6f2f2fcd42c729b6c98f3a9223f86199ac21dffcacd20f3ecc9533be79b7a3f784c97cf46e52b99eb6860d1f4d53de82b3327006e988463c7aa2f1b649f9679e90d06875d0d67e3c4fcb9afa37d048cdc230b9fce0f4b1e08fd8cda423c17fc33d0be35988e09ce7754b1911c53acc68a771b79d826d3555156aaee9aa228c695974da997583d39874daebf9dd8f1ebdc81402d62d414db50f541d66e114674628f54e8f4200e9e5cb0f66ab3cb7e3663fde124bb0935fdcb6e86a877cbf8cd89da0a1e9333dd088e6f476ff54901b61e2d289689e25f556fca5a8bec2a5be4597f4b66b82d1d932fecbbcc7bb820dada5b10c17678c0c105dca9259cece321f9a1ba8eb40f1efd6c1c5a739fc9e3fb7483eb363bef3ac1f27278f53f7cfa23ec4ddd92b9e02ed9c77bee56117d997e58a2920f852e2c249b8c6a29d8e69960d094a1e6310b2cb64161ccbe247c32c1c55bb167d19e99446bdc58491e3aed489614e7527e44d5fca2ac3ae61060a4d0bba01a5792ca0f9a6922bc27e40ab4c7fff80593e050c40e94a20d4cd5dd2e3f33bd16d9f209b19218be0abe3bbf31172a6ec073530d27bf7bd134ae6f0f17b8a2113883028697bc262a82261a8aeaafd6fcadd4ed4f83ea99d5cc33ade040661a13fbf5d3c7d1d6af3e85c5eba229fb1a759463dc751740278d6543915172ea5cb7167b734246b62a5ef229895480b719869002a85fbbdd4f57b6816b5077a8a5d3e4ce2eaaf1a72cc6165e5fb73c737590c847d1a2057b362a449e4d816fc4f73fff555c545776dd0a91fe3627088d21f996537f85b9c5abd5f755bd9d8d8c94841426d9c531e796914da63e4a2ebf2cdb95f0dbd092d5bd084fef031959bed632b7bf6a37b6d0a2c9a8a82b13b94da22aae8b42bad22190ba939e040d982654a1be4f336a72561b10ea98868072bb5730a67525f1e2b2f275b055956b5f381b2bf8544fc66641e2f94d2ffc1666cd099db104619c9915b8ba5efdc5e2a50f59a6221aa36c99245b482602b289281ea4efd34c1c3150c3184497cb226e4b4ad365ed8e904d1974dc3e5beedbcfc673b3d0dd6e71b9aff47f28d45515d8c4fe8a7fbb0a6afaf433ef8be68e5728b1cb085c77121465baf50b578f54dbafeacc9895a4f16fb34129ff23421bf37b9853a15c885a6f440cc49722c9acf804a12ad0d9a4125c4917fb3edfa2b5e75dc12544c9b4c7bbf4bf94aec93b5f7c2a6507dea5e0d21d2820370a315f17eaa224025fe8c223df98759c08ab4da451f37936c75e8746622ed4f787e95072a5a680ba648e320a9166181317723cae066c140916fb0b3eca7f75f8cc67f4881fd093a225ff931a8c446c5ceac9fd85f12329bd8bc72abae7aec314833fa0e7418bde4887ac960d01fb0f222fad9bd516b8d6a8c85a3c0e77185fcf0bbb49f988a2cba1be26da01c9400b0500bdcd33f38a422963b07094fa0701f811fad654ce0106ae70a876ef30ba344ec310d9197c7d2b86a7cff580265f3cd8c8415c7be7840cfa9cfa6b65abad586437991b86aa2971e7720fa5a19a1a2177632615d0e3e9176ec171afaed698a1e8cfe60355fbe32fe3a171ebd11e10156b86b6287faa28607e8181c3942692c8c8217e3347364f99e972ee421b5b9194ec40dba2d8247ec0087b11b9811cacc87e719be7ca29bb52f19c5ab5e8e62d9c975cee9e798745a52c2b169bda55446e0953d4c8e4a0e12edaa1a6e4de8e24e3c17dabb3fff33ec8b22abeffdf23f14a288c5eafcd700f38d297d8390a06d8b955207589673b150d43abe8e71c37cea2868ec35818d05f47764a92971998fa7a6a07ce33f689c6a9578d05eba49a3523f0717a132ccb9d12d44b5ba2f90acf7c9869ce52d1d4ea758f0c4c017059180b3875a8cccdc9f25cce454a92242d29be0ad72d931677c1ac041849b3a6ee7d67e7de6e2b973217c84e1b8ad133ee12e2b83dc35f898f5d7c5e06a594252b7ae51315455e2de2e98596bcab951cdba9cc6b208110984b5c23220999c2f42538fb710174e2b8599c94f16eed718b2ce6e56e0899156edf5b197b3fbfd600b1ca5568c9ce9746b4be9d6888c655a1d6346f6aa25f2227d6f42a007496f220ea50f6ac78c08bcce444b3713892b01e9aeee089767dcb5f35151b3d13f9d7f736252eedb6b0b079469290043a9449d11f94ec5f9f1bd94f799a783ac1f245875f77e5b129cd611e215c2349fc51a7df9aa86a99ba4d076236a7ee78a886afc04df1eb6d92988169fc30fb823d3e250b7ecc83513f74dadd0fe93d067c09c62af46712fab46468220b68fce23aa904726d0d2b2eaae27777145f624488a69e16276c4157a44d51d0ea212ee893c443d959f670886349e1931ab1ccee8a461f349fe84a4a35ae05ff3492c2cfe103893a920f3ffd46b53c14eae9cf348d73005cb4c568aa91a2195ac1b2da37eebae0c80e0c056bfdf6b2e7473e4c0a0be5a302d445057eaa66a0d13bc2dd2c571d14c23e12d75c578f2ce6c8f1bdda1a0d9e8591c2668f2118ce413303ed12fe7bf610e882b23d59072deead5b6fe481456e6b60fa5d1f716bf05a70230f4689592684f8748d8dac9c932401e599f0da4eb16239b6c9f07177274621f10c372054f392b2e31427ad7ecc959107575c37ee3e0927ba9dd7ce4862f836f7e0e6b1ffb55e903682b018db3721dcad035362fb0b28d1a8ccaa4dbc1230ec41f7caa7b2ee35e489490555844a6396e76c89c56032736f364fc5f35964e910ad1659f17bbd8429f29b3b98499db59b29a0855e49812d01b6130a16311e591701fae34447992c6976e347bc8f3afa8943aa69545acf1f192c3f31bc6d7eba8c0c9c1c2c3612e11a092561c762116fe3e86a156f3635a1b17df18c9d67cbc68023826afcd17038e9dceeb73bf53eba523fca0a9917ac380796153f6e8631b3de164299b7d997161c4e560efca7db8f5e6eec0c4f322bcc1161120f13fdc8bd5d0cd5a898561dd50765d769dd83281d6eac48b66b2148056cb04be40cb46902aeb6b91d6b61c97715f78c2ce6456974fd930f852877b286fab1c1c193cbd3aa8277072ba018b2c59b4418b59eeb9c72c3fbe5e39e5997d7653881f85532f5df9a2a7b6943343e6d53fbd818fc7dd9d274e4aa6d550e7852d926e06b913f7a771a4bcb0739d0d5261ed21d8793cf5adc955a3ed15103e5eb8597b1800fcf12418a39944bbac9cb36e0132f28cb270b9936123728040edc62d059bebd7c3a531ba78e741ad8cf414a337587c877933f84dfd5845e6bb541ced351dc908e773d069c985a6dfb923d0ba1bd4c6e7d681b97e52f404e54d8475b67e38169de985688a4fe9439c3e3c4489243346016bddf5bd8da1684558ffe1b4675efd3feba3768648dc6bd96d88b307ef9de41583b3fee070c3273039e982c5720cb3b0730ad7476880248bef8dd5b20c054027d12ff97830fb5b52af035b1f1710fd299c273d9bdd4e25d3efdb88dd786533c9410b959d23987ea7601f5da33070a853177217c57a6b0b98a72edb013bb552ab0e9594190bcab888bf25ebfa3ef1b703f7be6451d6df536bbb10b38cc6d8da6e1ba600d4c6f4cfd04e066daf7d5c9d98362802187aef8886803349e90d28add06fca1dd46b143501312b8d2f3c10805f8f3f874cc1277375cf9110f2aed999a0d480d902b73de591722ebc39c561da2eed4b9ac19acee8626558d93e9c3ace592593271ecafba8288503a54463bd296f0c6b2445c1b83766b376501e8db3bf1a3704565c658ae09f912a3e608f7b7f754d6dc48d8b6b9f948cc4d9c0699895a6dab0d554b68d0700058547237d817c7b1d32e76216fd88db647aeb65e53d35cc84abdfbf04e60f88d318704855670f39d072c607cc274cd5f44902d2f9bf785efb0defe5b791d88bf7b7af95849ad6077872c58b49dd11f23b902e6755ebc801f6b234d996a97cbd60a4ed4d64519fb28a5edb68a257ba80b8bdf05749799d8e41adf2bdc44673150c2b06843e7a4ff733ba4373451c6020b3389e8242d56cb3835a2477d0af25ee725d6bf45772c298e7f75bb0d997f8dcec5f94275f910894a2de28a8884ab5728c7983335e2a14b0cf951b739cb1db070c344cc23bd78030963dd2a353ac34f12ef833384b8e2aa8a6e47e1d3a6b3aac44c78e5ca90992073776f6843103182202deec96182b42b0554c45d0a27f7c5e0d479cf860febea17f56d7ae8f7920402bfe4c72cc80669988bb06d089d81a472d52f4bfbc11d47cef6dd5937fde4ad9f8ba8fd178d60d50778a5bae69c647e0e4924fe60d2f9b74b8e5ae8fae04b1ee29d88cd44e21ffd2737c9445adf20a9b5a6e3b1d2497fec0a2254b56e9e2fcb61ac43081b597106862a317816af3fad298d983aeab17c1f2e916f58f2f255b2a732fec86f269423ce2763c7a02e4b938c0a2b9133e5a95b1869a21b7acbf264f6d66ccfa609fe4f9c84cbd5e7a7ffdd3ab534022ccb53389fe45c99dc0cbd677ab266d3031b84610170d5c59e874140889c3302e499e06e4b97c425b35817529efe32c0545cd11e08917f971fd505da271dddc3fa4d8fdbd295ab9d49727f46f5a1e2b57daf445cb2154b597a7e24f78cf6bd6ace9ff974eb6cfb51af8398df8d570af81273340d8f7bbba55c0ec66d6421800273e6dd63978eec7fdc43704dfc1aadf5c9f10fbf8b20c3ee7bc92f6292fb2d0e2632b18826b4ac3da4efd05bad360c288f71c1dedaadc9dbd3494dcb0ee3ca8cb50ba714a5d7d7a89c89b7980f0e4567a2d34ac82f9a4ccf4d181cce347101cc040399448c84fffa2c87485c3e10c01b69efc776809cac63c9910617bdf2519457130b07df4a0e6890b9fd16bc8b572b08255d793d7704aec57e138a73eba239d783081dedd88932a411b0294f3fc8fd3166fc1a42f7c351b059d39f3c5903be2d0c45c048c7b2145d613a9d77a52619d5e74b8c76cf62bd8e23b6db1f0331a1fc0411548a097fcc8947367b8eb46d3d7e1b322528afd42ff5141388f6bfbbe762b9f5c9d3357c3db251c575e3eb5b8559cfb682907bcf27cc30da370e3f23a343b83f7b7585a8e0248ff0cd8aa47ca5056c941869044c2bb2162c0463ae105341ceec80111188ca18429be30a7b998504f21fb8cbfa44cac966a1834d7fc80b9b08cf6845b7d350e424580e541fd9b2e633c077ea1234fbc099d8223bdc71584e85bbd271c503bce05e0e401631ed2869f0e3275eed3232213d4197c5dc6317dd61cdc01f915114ed083fed9e75fbee2e3f74f288011f7c06b910549c21e6cbe77599e9e73a53dea803c5be62a24d91f15d351567d592d4ecd30dfc1160fd236aaff68fdd4cdba4a9c693d503d02990066b974c2fedeb144c7a515c26803a6327f8cba6fccd86eb01c42a2db3d9dbe330d3303f1bd0aebf5fc0e0a0c0bf7a57f84fa0ba147775ffbd3dd7030d0946039bfeed3bf915f14fcc5a107c0f14074c3500f04faa7a1f5a8f63f9082618d08f57965d343ef43e429c01759f71ba916fdc8b5ec2b21d20efcfefa7ad7c9dda54f96ba2ad2811aaaae7c503ea0538442a99055e9bd1c098fc4f794febc22679edd8e5e5bf6144a9bbf4dd20254d7be293d4ee9977f17b9ee3ef0429f3f6acb0e050f211ae73d2a4e93fc95908cfdfa07eb959d1e7a93dbd3d7fca1f2a36099db09c64e54dee2f3736979ab2322eeec7df6d2f454cfe7dbfda4210fa4b7a1bbdf140bc397f03507643bc5abceee769610044e25bcbe356116032536ca87982d774a1ac9da66af6c939f1240f414ec3abb7d5ea86f42fe194f85c37a5f3b51c552842a6b71ba67429d98bd10a3c6b5c27c6bf7a2d232beacf0c0da1679d6d3daafd1ebc5683c89acc214c184a6c7a6bb44fbc0d05226ee64a420490f7db333144ea4c0cadc031eb0ddc7ef0637a0eb48195a4abffbdc3e99d0f0bdbd55661b5b7c176296e893825176af03f8b56657e135630c0c91ff54f47f90227a64348f66d6aaa594de1046ed44ff66bf19d8c8c360cdb5ad578fc3d533cae7e9ad487262cf0ffc3378e175bf3fdb339dc4906c4937cefd8e530d2cfe42b86a12fb9a4b63db4646707a61804313fa832847b713a6c23d0cec63b63a632fc4cf66231e03782bc27ed17270eebee756b4218444320a2724b2b81b5d6ed0aa447142ea360c8929d7275adfa8892cd5d654b6ef167bc19aa4a2d6dd2eceeeaec2fc91420fb615513361739763a44e121a6d772c82acaa4a28f22eb9fb08c1b07e0b17f8610918fdbe90d9bae5191df0d7a92fd45d21a5c61a49a4d6447d56d80cf32867a29807236e137db9010a094f64542f7a8fbc7aaf2c394f9ed62286d0423764b54dc85527b27ecf741836f5b7dd4b2b816fd1e064fc6c0f102ba891dac4ab0344934389f9fb21d9b258ab7bd8508065d8a432a73366b40b58e05332a0b2ac3b5c5392f611fc3a674175f7e566a2db8fdb6263ad15427a46b8c7852fd045663b7eaadcf6f7c9a3cfe5e9c58f2022d7a0176418c36884ab341da533b9b81289df518367db39826f60be89a39099644fbeec45541d8d4aa444454c94743ae6b95fe19b246c62d76c4c863bc7e35091ea9e1f4282626dfe0475e05db5a0189fbc24e3bf9e6d201be8738aba97f3d314a36ea7571c6fd2fc738b067ef2d07d6687b1bbe4a73ca2c3d6007797fa3d0e48240685dc73f4390b778b4f82c8e4bb13f701a6060d3ad8a6f6dcab08396cbfc5effa518dbe4d0420324f7589c83034576f06ef1de4e393bfd169034e2f141eafc0d86909d0de5a6a9d218be17fc2dec6cb71ec86197049629472ccdd152c258ab58a43ae5d5789c11dcc2fa4ffb066a0145cceeb907466036a73455903a756807be4d94d19cfee1dd7ea9b14ada5fba8dff26ad883ae362941349797689c1af5e28feec2906fa3b91686965b287f53ed3233f3bf02224536c68733f74a71cf75b78cb48b0d9f7096605532c443e4c600bfad5442f37da0d3777f8b81382771e344a205a9821408dd5d7817780b04229e9c9965cabec9d179e3546e2fdcee532e4db189f7d9b328600b2c43dd54086e4ca98f2c904f949eeebd7920f2834571d192f286867b0bbdf6fba3c702854200c72bbdf24116a1d54467ec7c54649a7a37f094c94ea4fccece96311ef5a1aebe19861bb47e38806d9f113649e41a8bf8d73b32c15f4e18d6271d41d37335731c43e562b41973bf3aa3e476f7959e400a7f1017328fca40d4c6a20a9f6926dcdfb68faf52d3bcf61f9037d0c51152903b39befd05278b31625afd193b9e645088a735a7328615d9a9c6118ffe041e2bc5f69f45d2cb1f56d644bbb4fd389bc9c18c089ffd725d1ee29e158cf08b20985f1fdf66e0be72b4ce52f52b3c7ff4a3398de7a1e362a5a08b0c41ed66d6b925d422b549b12b2a97a50a1d227acfeebc644a1454da50ab3f9b7f71d31c19542d4d5c6e276bc54e07c645a4149e2b34fe432d5dda2fa88e0f5a9cccf9fa1eadfde7e2de1a9df29599d2c5b7161ba4beb597fb0d7fcca55caec259db58f4a11a50945d2bf9d305bcac8dfe63e73cab863bf32e01da97458d55d83d31ecd8f9edeab2b6ca02ddfc7a3fa79d2bc7b6816f7d9f1bf86929ef9f8968065b5db9d2e5b009d2bd5adbea8791e1a934a1fb8ee36efc1e29bd4df8265fe67562ebca4715857f354e7c343d6c4d53b92049e8e13636d79898b5a9c1743393b634bcaec83dd7411695cf9101c35ffeae163a9d7c1d18d659610df7135d66ed07c54d11fd82ac3ec3f16b22ddfd16cbb3bf1ae3bc1f9e431b1ca4fb556a8d09505675944f3c63c75214a77f65e22a292ac9d4d09145e5641f409f454e17e503fe2602511c2ba441fd1eefdb8660888b1999873f94b18bcad4018eabe02cfbf9f9fd06c9ab53932757b0f1304f98a41250fe59cca6de4306ec5359336de6471ebf9b598926578965d8e7d90ab085f0fa440754baf97f7d37e1f7f98ebaeca91da0cf0550f54db26aa173acf472832427bd3648c5a74d1408243157ce5a910703bf3ad5661be4041d18f6a1ac127ab928ea02eda118b105a3fc34e4619bc1a61fef9675376c4819b63dc6ff990405e5c15a4afbdee650a803075c6efac4517286b77195a1ae9689a8c0afaf2eb81b7986adb51872957f4bca9c389c01dce39957586995c9db785bde434dd98a2abd86f7938478c64c4fcec1e51c68ee69c2b373626fcc4d7c2a973797ac7bb39215a9c9445bbd54d20372b1a97e29a3e4cf6db72630520277a92c17418ece010d2bd3e47def9ccb8ba6d9c397a52f8da10c3f060b4fdb5fde15335ec96b7ffe8c46bfeeceda6016a7e3df20f957fc855113d5de179226124a825b270088d48c51dc282aa5d296672d93b013d955cce6162dacc181b103f085e472fd78066172258cbf491341ed5229a2cad7d1fa99259df1f6d7cc754af876ce506897ff089dc082af4e007364841e20b4961efb0eddfb300dfa7f693415cc006c8f436e38db2edff482f3f6f936cfab1bbf8265d197a932667f900bed4c02ce44ce1790d271c6075e331de1a7e38864c79a044992e0758f93e0544f76823c6170a760a7ea758e380ccd71ad409049d57398bbf1aaa7e836cf9ec7a2723adf3e213e048e029b4bb3eb43bc23b23d1024ab53c7e2be72675846dbe8787c9ea77a241441dd534c867e6421349c6a1fe02929f5d2548cc6f7112b4425991c486abcd5501f874aaa936b83ad519257452eca40bc9c88da17b5d0ba94566f3225e73ea471df5a6a34b4d97334fffd241dfdd0d30167a15d4cf9cd9e39697299f7bd9679a497e2deca5c833ce6a01b9b92cecdafaafbfcca2f5f3a096c5387fa0d71255ef5fe52f3f55aab0e26a7d8ae8be4dbcc7e3f106f2cff49a6af045c9845859fc15324acf921ec7d35b51881b665bf559da77e80af9f406dfb5e1e95cabef209864bd25b761457c3835dd1c60ce99093e7f08791fab34995b3211bfedd1100cb4c04e3b920ebc8ec0fefff867cfec54243518f4832fc27fdd1c69b92dc447f10de6c95cdbc5d3a9e57614f6a7479209510b49a88224b1f430a05ca9d2ac0e6b492a9cf1b28658ba1b90155917b0f5f56b77aeeef6e37b684527ea18583b877995e210133e3ecb35076cb9c211f62c472eb83dcb159f868213fbb37e6079dca0abc934bd57177950e57e91b1d0aacd053724b8b4c1961fb2db6fde2ddfb21c48e5497b07fc86f2685d86cb2cdfe4cb4a413f7dea9fbcaa8c9477e1c3f1fc3161a668d9611c2c858960ec7f3ded09eb940ec6a4db2ea128569877c3b785e41f9cb0fb1ece201d23f56b1fc06f2f0f5c2689b522620d3aee09f721b143dadd13e1dc1dcd7f0a59be21c8c99efcfd93d5efd1a738be98bcea40a44f674c6809a3494d7e133966d7e5557f757ff662de338421e3915fde0526bd54f4e078afb12bde9a6f60c8d868ec2df4ca3157156b154f9f939157eaba78dd2f3c5f88ba3200c3bcd4ca8e1f396157bd2e916b70458bf2577b753113d0589f55ef372a133db2745ec6c5d5b794206b946ded4706119d5967fb118fe1596a7b9266fcad7f1c1481ca6f776054f904ad447c617f2ef1cc0398d8874cee2ab0931789a8e5bbd35bc5ad5ad5d66ac8da2713b591a7048a8b0efcd382a1e6a09f768ab49c7ebfb78e38ed8b4de7627c7abc1c694feec81c4756217e1d1236e36e82e4e58ccf0f8fd8138642b5004e660f1c08aa88ef7113e37d2368d2984b9ede33e9523142c31741a04eed0adadc9c5193393cac040492a4628786fdd115a9572bd6f7c2df62caf9d23d4ccbe940fc26a7dbd6f6f02409b539fad2b33d0b0048868f09708a1d6864487720b0d63935ceebda791b6fdac7d43f9e7d4dfa1e57fb86f0540ecb38a13fa661dc7e5dfabda7aee38254256d75fcfe01030c4731779e71f1edcb5366e143631a68f9d2c403260b6cf69d4487cb4f2bc9e26f64368127c58426bc502feeb3126f89cbb1b5f4d8cfd00029691d163eda496be22d8d6c93fc3d3cd17465f6b3b717263ff53f9a0f4519224ed4efb0a003ee2c309762a7119495b7ad56d340d89ea806b0f6f794f1efd529408696b6d489e0cd54bc6f4f25cc1cb27cc6a53323acf28b30c32e3ad898771272eca5251ec8b7f0424c9561a429865d6ec4fcf6dd38564626e3692e1b4b67806a6f1c0931fef85f9c1daec876065f6ce951e4539743b297e1b5dc08333e17a7c60bbdae63d3de8610a4daea461320dd57dcf2909b9d59fecacd4bd552df1257c0c1ce3e279cdc2950e32b5439ba2e4adee9f869be30b1acf6ba94e142bcb3bafaea184624e7d1f783935fe698c748708849c9444d249e74623925bd5d6d67576c9d9e4bfb01c949c05c95ecb99e25e7a842e81af6fc6503d3fe7033c4280e42c9a40ea88c11c1e10bb44813e29b0845c2f38bf832c7b366be4f1433e7d4c6ff4686266fa36aebd9de2733360528d53ea3df4d4bf621a359a8cad4bf64498765c88ab3d03d551a64879ba1433b28aec34bbe97e9cdc25748b495d90557287cc70253679d191ff25b4f6429343d5ec16a001030051651583731e679df238f21725d03303212d8338de1c14057d2f95d59e70c42812b47c7e93a0dfe47b0e9c0aa90edf5cfaf3bad29903238707828b98de6d30020cfde755b9a7856d954c05f1cc3c0b2640f692d0d2a0067ed7943c3d4ba80197dae74cfec88b8f385569c02cb68c0598573bd491d1a1da14b3be75a29d404d1c9f65b9eaa7ab606134a71f2803935c39a6b4b1d107dd9eebd3fc26468f6bbc4887273a67ac17656a15ece3e6a33dfb21b4077adae58db6f8d13db7a4520423277a6330c91ff490965c505e92d6e11a0b51c8739bf1b5ff41a3c9034abf2c31fa43e9f0ef87dc2c0dfb300551073ebb185af2275a721d97a5e1bd9399aa0e48e38e5dd7e38d6a5ffbc30f3fd905bb811dbffe79b0377ca3a2b2f3423127300bd55544a1bdbd5efa5248148d76e09baa4923adc79a5d4fed41cc2c7dd8bdbea3979b3120c9adc59975e23f9268f5f7b5e7dbce9cddd0852ecbf9aededf409c042b7b3738a4149fe416b632a0712a859959318b2baceabd220c4db13d5aa175d78e901641df0610c199661b6a7c116da136f84a13c2c83b618bc11ed2ab71f36bb38a89dd671b5994556e999a13447b72f557aef712b42dbd5b106e45cb579d47668b00b73f2192e444ad7bc229bcda18addac10d9639e727363aeeabaa7256cb4b0bca6e7099b80a934f90837d8d63e65b02cf4d9a1c7bee6397fb065d3a26770744a5d5ffcebc77e4298011b5a2e4af3742371fe3dac3cfeceb14bc0d356b0fcf4976420f963db85aa3ba32a331ca382395adf4a5002f6722f7260a4ac23855ffd54e39534348f3c8ec4db3f9baa9ce3e288f0d9e2714aaff50623dc163e7ce44bbf785b6fc9d02b102415ef3b9dcf6e3e29a538d911922c71a37835d27031f4c52a685025e4de74bcd34ec999460c831dbf91e8459c462f2abe8b13fb707ef8f43862760273687c1f19d579a043b7ad0440ce84df970158dd1dbe58c3a0a087a0ef44ef0ca6b7c4d294eaaa7afbb6029e37d4a5489a9d41436c5bd663fb3e921bc6098a3d9f5843ff2f9d3f01720fb361dcf9faa86a446131096888bb13f8bf863a7cc0cf08913506daf6d5fd1732eebe79f1e2e8c2855c3f76a54f272b95e5c25c1296b843c3cb49633103044f21bbd4d47a2e1b5ad1a8b822e4efcbcaddbe2efb0ede0eef8c3c6dd490bd367413ab1c28c56e2de46a08c75e47934e5e3cfe021d6dd6071ecb77f17d0733172c1e8001f1b2c498b0d5baefc9e58a176d4407679ebdece90108116d52a1123780ab67b4d5a92349f0ab691250334109aff3633c6ce8bcd0dd2e7d9e765dccfb47a7d37a93b3b5bfb9d8a9f69d5962ebd366eeb09438b2f78caeb18ae3336d5dac372120d25c60c64de0f483a9712a1a8a6e0cd9ffdbf4a382348aba5a6760016f61c744d741c17e5b447649ab220049f8df25d6f36ea643a6d9e45ea6cc5425dbe728280435c89c58ced66ce92b7479cded53b2c2176ef8ce0ccb6d85e93413220f5ebe82292da554d6083b2dbdf8cb3ce9f136c81de110d935b4cb4bdd889e581b81fa67924a523ec6094bde88bb9d7242bec7a6c29b410a4f933bb7980d28083df514f4b5e00f60018eff5c081c3157033a9f62dbd1c95fa9161b9d4d712592404545d346fbbbce79bf74cc3347d66cc8ac2f7050df6c3fec9539fe4bcb37b516a15042394c64bdfecfe18321834e2b076f0c6a8af0ecec49d452256c34c3d42ff956c12715352905a9734862ab2ddac33005720c87a7e8a16f4f71946ee9493ac2b3e1f96770f185cf4c09a706a57706bc4d6bd83ff9ed41773aadb4675beda50207e4e20b911609eef5fbae8db77534f4b99bae6d45eafb8b460dbad00557b8addce69860c28e5c9b17558b716b550ec5c864d5dcddd17f0f8b0d17f95722af7e9ab26a8a5b7776f54164b3150ba21874f231d330810ab3b87567fd9d25e9168ad356d243c62e381a633a343b113ca65b69cd95396ef6222201771a056de9e74bffed36c5630e3d64e1e19114894b5a8dfa3ef5f18ea590c14fdb9dd69891e05e77f03541e054706f34188bd463ae51f8baf628a65ff8f613d094ee00c241fa3320b70d836177fffdb4b3cc8afc3209fd6a5aecc7c91f2277c15b9944ae1c8a1861fe7b4c24866c80297e1f904cf76d71438abb991e4096b28a03823c614eb2f27de707ab49f4494baaf0295f121087711c296fddbc493f241ef5c50795d7689da278f533920c387a97bb6c4b79e7d906dee778d4c1971d894abdda3ab26158089481c01932991bfafd0abe488013300f96ff8c38d1dc6b505a21970463c5d8a3443979e5495209ae2238ba094303d2a6b6fa161af9ec99ed7551986d2125031f039a810cc6615103cce004804e61461923589ea8be8189a8f3dafbbac10d9dde587dff988d16eb2efb2ba5be9e7f3ff58dda51f5ba782ef506effd7a2f3eee68f8abf2723196e3f13e4f860cdb7a7b9e7ed93bb56d7c2f5f9351005e795eb58a18e00567d0983732cb51a89d95dfd81513e5376a0cbc0adc0d5758f43e25a3675e541b063037061bb1c2a70f44195d9a0652b855e9095cf45b4d56500c84dea265ba3cd77fbd398ee596e3fdfb4889fcfaf750649c7ef91d47efd55b8f439a3743ea786e3c6d3585508546e334f40afe3f0f1c9002e4db20427b2c0b1d748314db2f464e61fa05895273cb2a9c124b2b4b32883f0353aa6651ff02f2278ca2ac5e10092f0f7332881ed2ab06eb8751c89a521d73ffcc09f3401e0c50cb17c246919bd27e9f27ca491a0bf300f0998bcbf664ea16621591c272b9d9a758ed91671e6b40f4693ea1ba648cc7cf0c4368c3ecf074487ba9c0d0a7145eb3b11fa32ce438c67e16e3f5927a7e6a9e30f292bbafb079d81e728a50493522bde669ace9214d472746f19fe8aa2e337c9c36abb2507e966c711e45a5a25096f55d59d7dee2936ddad44061f15b5d4aa4a7a54cb57f2bd9ca95621bec8e1944da7d771577bcdb3fcf402ce32a9068631e10c9feb5f7e6e608362bc4af5320723f1957f609ea35c76cbfb1fec64a37c7b6eac624233ed8d7de2cb0b41452033614570ac1d36182923427d2f47b789a880e1fa10e5579ea3645c73d9b089d4f3c1f7ac7cf8f36a5c5d18e861730915c70898306fcfca405d66b07cead48e9ff50d2153ca205758f5adb7d1d5a279caff69cbc26ab3a1ec1520fe99afac4405291800f35e7048e2d196749e3fad595acb214fed4ce27af14de1813badf288d831a4373718329032394dcd7801247df27ce214a5b0e17aad421f9a8717190ae40025db9f2c0263f5b825f3e37abab5278b5057d6ce3a52522c14a5279ea40fa683e35d64abae982543bdf7dc9d8a01ab3b834851bd2a25c4da1808ce70150b1202454c8646fbdec51f2e5eb43e02b2f3ba0cb76268871ea0ae5453b73e61c606a33c2355d99dfd5bc3b9e0e4768eaa6fba29a092d50270eaae7544df8094e1553fef171a693c4e54b17faad4f96e6edb662f3417d82803cb150b5efd8bea08598d07bba1566d5e0f09b83326865a7ab982219b016bbe6a5665266e98f9626b0e89c3bf2c60253446eae0144fca2680203b81e3dc0e9b1990e6d465dadf1e57311d54671ec1f4d7e3443439653cc961ea3c900b4ccab91ebac7625cf35025cf691924d557b347473a1339574d7be46b6669f3b4be5c4f74ab79ebcaa3af8c0aca5055c77ca1efeb79d75c7db41aa2b6cc79572500f9d578766a4f2d1192b7291b773d3f3b4401983a95eaac8152cc916eaf34df41ff3432aff53299794fb6ac5b59d42a1f50eca320879fd5613f1bfdbff5ac10ec487e166114cdf6a11a3ea1ae274836b1aff13d60a3f7263f8a96bb6a282f6e22367d0cc885a01f359d9768f4e3a3a3d59d2dc4fe4676c64a1cadce4ae8d68dac5a91f74c775bdbe503cfb18610f44b9c83aa79ed448edb359c52bb3766cb559f41e8f5f89b9b99b9a9e8118de7b471cd3fb03abfe7fc321f201094dc2015c4a5be1bbb3bda1f2e980baee2c46507173135ba1453b66aa451f80ef1ca9cbbd1f608d370af105dff0eae6f245054bb5bdcc339c66fb2ba5209aa1f6998259101aa7961af57dbb7f31a085241ae4b35e59741e58dcfa24dff4c3974953b2e77a1427605be9c6cd499bea41ca46fe35696a439d1d6cfd1ff6b592a72ae188837f0e5e4034f56bc95f728bb19d119f52bae4664f0989c69e63809e609c6044a77ff5e2a574cbfda2f3c324aba16d6c58e75b670a1def458e69a15e24ce8bafba559a8a7fe95decac0fc7747e9e863ac1d8664d1701cee91297edf3efa3d0007e50aec76f80ce371cac33692365d34186f75b848f8fdfc1bd245bebb235f81146b65fccd47f4fd0577025c86173d36873058d04075ed115078c5d383e7ed87cebfb8033e66e0df85c316fb49a4e560b9f9db9e37239172081a8ee2f06e3d78197c31f016f4b125253989c0b5c58208479b915f8e26d807cab0dd263bbc6f1f73db645bac8e8f59e1e5e928a2cc6c05d0efd3453be8b23b213caefe24720f8d084021638959a11a2391cec070769d7b37a2de6cd2f9a7011dc6bcff56c6b8b191100a4862adb82b35e883a6268207a3274d0998af3f5b4a16b17e8a9b4d5926da8fa453f50a29b54505fadba9ae58e07edebca014e22715964c5941f770452eb88ce1a20960b3be08fbd39f9b29ba8779c77a86e58b79d97b30e5755fb1daa62a60da53eb14b131c5cbdb076d6c802f9d0d967bebcebba49d46f36969292839ab3e679049942aa4d303b6b20a267e76ff6f30b33f1c40e90364c78d8b7e7fd326405fccc29019790b7f2b279d18aff189a7e1f74fba6c9b27b68c7ddf3c3b77c65084f51ebbeb713c85e7357abd1f205aafb45e4e6450240336c14d031b9b9257fb7b74b4be35278c10bd1dd62a1cdf0ce555cf594fee8484f682e6c9498b0d3da6c524a86a8c17bbb881ab2e7ba6ce06e8d1295ea032f4b63bfced450b4e5b7f19e910c86bdefc6b6ac3f4254f52e811df0a48d311e520457f5ee9b971e77d3f1781872e88dc15b726b76026eba6b82a2bc29553c579b2f1e17a6b9222b074572caf72d197d5ffe4008d1f54d89eb557a7df8d09d4028829d8f3b51b4f4ad50590d9c28d0645dce579eb14315d154ee10415dcdf734212714d283dbca841db091128bf9c5a4415dfd0b007e5e1b8c57e72caef28b270d7bab65632e7796116ecd5391c41593385000e34153e5a6035685b9b2fecba7e3539ef98709a0298ca789390d7e65ad90e153df598d958c81ee67bfd05e716b75bff6219fc3ed1d0c768ccdb8779105908fdf058a8e909c611797fe3003976f4aabf59a1b9ba23981f1ab38eb5d738c40df0b58b3173a39136e96e9f1030fca2aa044653fd35f00be3334244dadc63fa194d5685cad1ce77b07d53d69f822580e48a4a43aa3c27c974c77fd75cdee2464a9065603d77f469581859619b3c1cff70e725481204908c79a045a407b932f61d9eb9df2caa11c7ef4253971c5e7b70e2df261a2eb84cdfcad01fe03c80b97a13883fcd8304ed2700f033f74f1e3647ca8de00d75b2a127f9cd1af14f25b89c85f29b7ac21f547a7947195be2b4ef98398e6901239580bfdab4e335d4294874ea608361e0b86433b27b8672917dacdcf27845c8a905375a7ce39f92bb059952273f5f8d933518c88f28b70971e19acd42badb4a353638062d68cc747cf278ee7a6edcd67f9d169fd5e5d8038a796face9eab74528202888b053c7214bb764b8e45a87d8d9197152d10e87ec5d14631dee4d1396cf92e29f309dbdd5aeb37ffb7b9fedb687e944bd59e4e795f88c9bfd1ffe41bdb32447ae2ac3b8f3bcc378f29d18a6205e055de6881a55dbc64c4c24278ea3168e04130ef864c1818276c19b7ddaa526e7caa72c4b293de47780e2bfd0a1ad67eaa084f88025c6aa319d92e97504cf31b9ba04ad4765edcaa75a28321f0bed8d2ffc8783ef4ff4ac0262d3b47f06e2508724230fc8744c66037779be5ffe29275154dc5aec76390ce1dc89c0d474f3a131ef234979ba26a2e990389ebb42ba33774f9d9b9bcb8e1fa158663fd8de26a43a0848b7d47544126b2c562057a3587d01ffcfdd7910e2b42a9c2c439547a6ca89376df003a8c56ad93a7cdd6b2db8ce6e135b4d72df925ce85b1e28dd655e628fed01a25ffd54b5894e875e0f4032b1269a103dd5174dd72f73e79e426836c93659c849573269b3219d87dcdb9063e9b774a6536e550df4dc1d5c1e8e71f626df89a94a97b05d2c5cf7b6b7962a10ee1953e2c59a3b97b35c4eec32610ce6fe25fa7a17a5cf923edeafbf1db981c604eee8a47cf1b6401e3bfa13ebfcad09a334674f52b82817234b98dc7c5d4fefb66330589c22b6f59d6048523cf8ab6055a8575a94e24a50eff338485fad695f7faaed4e824fcf4d12d46b9f80a349b15838d77e0645e6b804f7de744f058dfe39ac40d12e6ad451797ce399c9bc9e3dda1c5ade74d6ca1d8e3847dda9aaf1b34c5effeae2cbac479f738c61bff3d11993efbd8a5ed82d7230d9cb7b51b5c09c1aeaad314ffde8b77c9de8bb12c17274129905fc478d40ad2fbeceb8b75e04165a2942fc432aef7ec88ac6f32148e54cbdc3aa77798536f6ad0ee8e145c484548257c6e17d29b8d6f64efa87605786e33ddf1a9f52156c5f2ab057b11835dba9e8598900a9826dd8f08f2df9dedb84c2ca68af4ca604327f572f582196b650cbbe428353da4d3f944dd5c6434c6a21b845e19c9ed62f0cf4247083e8f4b4b9ba9bb18a333b0196ab84ff9f18143afa1608361c3397f0c2d0a4bd165d3dbb2c0472484da3298d529d441de0c2f892da56828bd7c3056bd3650f54c7871086770aaf131994cdefaca18e5eaf48490ad4bfcbeec0228e176bb018a25bf52b33b8b095a2fcac584993dcb33d1e13c2cb1d0ab95fc3205b86288a9e05437efb8f4344cce60d7d139aecf3b0f3b8d97caf6f9bf0a615b8ab04a6fed6b88e9424fcf6322b42d2f20dd77bdb75711c002687656ce1b095536f4fddbba1e9724b9ed5899c5af1c11e327bbbea3f2e7d7bdc30c7f459b57ac958aecf075ec3c7377b2e357c1f7588a57576604e5cd4fe865d3364a5096b68141cb95bddc23348e9fd690da1bbfbbd31595ff6f77f922bf14ebebaec3b6f84db273305b83a80dcea251ddd981f0bc0b23a975dca450d2b1890fe8a773f6c86df90998bdd41c576d137ca692830d17140587827b9fa58924210166b65129edf5e4c58214cac289ffacb8f93bd04f2903b4397e716a15d6c64255de5ff8c1a31d6e3791da04387edc5d861279b4b6500f3fdcc32d9969c705b690bdd4b2fced7f3fcf9a5978f98eb60359fac69e52f3d6edc978f22b9bf612ec8e3ac2391e2468a9ef270b694e11cc9e6552540f88aaa18f7ae5f17bdb831b96e22af5af9257b0717053077b0feacce1f658acdfdb385ccbf2126c1d372a5b2525ed7f557c55d9c97bac8bff3ca9959251209a69a38aefacbfcc25b9d69cb004b1ddd887b26f19f199de8ad8dd3b4f94b87010a6df1b838e9375bb05b40c8656676e9a87f983256e7e2c5065dd6326d15ddc87be76e8b0e01386fce371468650bc12039ffb45cfa26f291bcbf9e146ddce058a381da83ba96123ec987955df0090b51fd6fd0a393e138310267ebf5cc98c2ce14a9cd05e407cce58200aed07698747c6d897a0c943c5898e4b38382a09ce93958d5b90a53f4d08348ab5a5abe8ea1662104a72acac85cd1b76822ce0bd6bb463269914c531951905c32e040074dd26e2885bb189cb175f0d5f1d01499187a8f33ce8d27d62783f2a93877c6a4bf71c488e6275804b25a55c228776ccfa15f011fcf1f6610c37cfceec7682be63dd5f4007f47ea7fcc83ca923b9a7d3bc53a33226ebba82c3822fd8f1bb67aecd55a643342ae0b84135139d9b82dcde037d17a3a96bf3b07b7945267f5b736713af16753c2de9b2871b2a028d5d23aee893ed6e858e71a73303153acea92b4d53ae626000bfdd7a560829a83329eb031108a0f68ffe68be0daf926a3da2891cd5667e854d32a4df173811424b04cfeb0a0b56b39acfbbcf5be3f1b6c0129bc102e6ece5db118494509a60a9c44f13c97908b00f14794d3960e9155b6ce3f1772a28fed8860736b096e03c4e48af212d38421abf437508cfa9c4b3aca3dd3e496ebf689c48fafb7889ddd6441639cee229a55197e162fed51d1cfc25342c036f06b32d2c3271eb62333fe1f6b2a442f6d0fb9f2e5c6c13c0169e962fc1f1586bb14ce2ed6633353ef1569d9b8151b9d3cee3eda6d4d41c022efa2b07d7e8d0866565d78e02ef79bdf60fc830bc6524002c2f82dd5e58de80a82e6da240fe4075225116bb83b52e010e430b7c73719ff645158a2bb32a31cbb6be90ee548c012f6fdd2e9df024184398adfcb5137f43403fdb96b601ff72f1d74ba2f1ad7269aafeab31c5c04194429c499b8b09c579b4e27cf7b424edf1fbfef56417b38649ba648065e6f8a7f530375d9286aa2117b2158d6c2dfda54aece6644c64218bbf6bd8b2bc08d650b9e23703640234065478a392a036644dc6826ce6cd66f7bcdd78bcfcffa5494e5261a1ac16e1499d0e404d176f3956ec4f027af29dc3a25e1059128a03f70322b5e478899476f007dadb23dcd781b5c595caab21cb66c35ce77eec0f37f514f818c36848ecf285c5e11a46550ffae3207ddd51b8bd8d05a32c074efbfab3987a9963cb09b037be93a33df76bad8da6420735a2a9ff911727212e0455cc3eebe96096c9708840c92474c179fe41de6089db8d95c3435c1bbf423b21353caf917c2bde2b92b32e4d3d1289511100f788f495aba2f5d45ccfd798e4518d595d9629616780a6c7797b5517c412966aa72f80a2b55abaa1e7e813b6971c236a939fd91a31a7faff5201cf180c77883a4d6ca3918070fe7eda1cfc15cdca4416df845e86756356adee50ff559759c22515fb669a3a008d6cdd36e0edb9555ff369106c9f8cc54b1f438d1910a89791f120abff8c60aadaecdd0a9e462414cb766cc9e8523092ae17bedeb2517f250f31fd28e1a9c75fc4d53fe8c719e945f5a6b60816dce10dfcec866cd5c8bde63522f234ec83da016ac21cd4275722742f70dc6ce6168afb29e298d3e1256153cbc8e83b3ddc2e8dd9dffa7fbf5b1f2bb8e77f190b61644b862f9b1f82b0853fea4237a6dee4408f2cf6562c8912d92fca082f464b10a701ea09642e937bca9874d4470a4bf8e7674c2e51ae44c829da4f9ab2858ba815cda28377efea743b2627eec9b07e24f967956f92dcb6fdfa618c1bb555fb85b3988704b761bf6e711ec0c4ebc45895f9886a29a8ae807f3db756fdfcdbab15148df26e4ca7a947db00f2fe1261505b3984683c6a1f467d57da3525b788fa1ed621ae0d54d734ab59baee963f91112af31ab19d11ca05398f7553b52be3f2d6dbbc84f0da231339c0e58dda643dbec49e7f9cb655744c01a9632478a6362d5978117ed4fb7b64f32017375e9d8b1a1a0bd654ddabdd5fb830aa6410a5603a8fa77d553fc500fd0ddb96d9f218b012818395d04e433cad898a4d80cdf0bca098d6000cc8616dff50a5c3a365bc0b18e9d1ec433a766c1ad649719b6d7914bb4462c66e78923d0833aff0d42854f7ecd775e40eea83f0d21f97f4218f5197b99c57c1c52c5681aca7384e58e0050fc958786fef6d856645fad203a315b1f31f76b993a5d7917b6790696a89800d035343c0387844815d20457b86ea9accf1ba72be609054017e2ae97b542c08dfb371e387690ac2a89011b0b3b2f2cd1c5d3a7e9b97a19d59a5099c875a37f08d4fa4b5420b2393629ea2e05b965151648eb026f6e33e2bdf7be1f1b336a179787f8ef6af253b6ae56d4979fce9b9b51b2da24951c20e5480366051071fe4601ab6414efe2a5a4912294d877ea786ed6b29c6673cea766681b33de900097cc01793cf53c7c30df184198ca2f369ccbc0790d8a89808c91caf637d8a8d824846501fb35e2bb8ddff5348b51176f2fcce19da72538c3ec251932f06ecf63079783878c20de7e3c7638e147db13eed7e3a359ec46d8e34db821b6c476b61bf416f5285ebb2bcee4e07c47c998197d81e152c4b0401f2a9aadcbd742423e1158d27ada8eb87a303dcf892bae1f0809bf20a0355da80b91e78a9c2b5eb77ae4b950dc9d230ebce1a6c6694fe115af8ea0a24d918469a1c40aea33cbad05b2a904dc75d6978fcac69311db453fd2896ff78bc9450922b51ca2fc7ef13ed310c38fd8f2f75f480cd4007ee088730487c25ef529a9929009b42ff181ee6a75774683b1f379364f9ccc9a7dd31926e86c55653b557d5ce9b244266a608dc72010dcf77931e0ae84d82cac6228b7d670241468fb1cbc7ba27882b49dabc9f35c2e3312703d49438aef7bccd401cf8acbcc5d677e72c839a226cfd3da89c7284ab66307ee1fa86daf6e3bc4c67e05827f818849540ac77064db111bbd0d2b7084a3ad019f8a70ebda9ae30235f045e741ce5a5822dc144ebcc94ec93ed40ab4e7283975ae84740520d0e3ee1eeca56ed7a252e665d9718095c8f1bd2d57758dfef7572e68c8adea1ed20a1127fe76d2e5e56ac330102db0d63441272f176d59380f8182a8b1d6b6f73e80be15cd657cf0953fadbd2b8b2b7db02f360b5d1f2675a1ff408abe0e248716d294f5aea7611fb49744141e6860874875a373eb1407ccba93d61d86a8b2cda9510013b70bd99bef431568e86072f6435ecc5dad60dfbc3db54fecec3a09ecfe7b39dd5a72e7b18e9ca1dd21acab338827fba6a5ccfacfcfa27f7bdb7f0a93cebb0eb8435856cd51ede9769f8c313ea15bdf11335a57d1e1d95abb1d731e63fa6e287faa91a0e32e0ec9678d55bf30485f91cd959f5db300705c460b14676c4869327d0bd5ecc0862c4d7cf4e0938ed769ccf4b2a82ffc6ad59334048cb2a70965e39fc066cdeed3651c9e71bd8e415ea3e9616b7763758e9b07da6db24069b204f6e7cf31aac03ea1992789b3c5ae0510eb272edeaec671d00474167e2eca38184d25fb5ef6c1902a0414527209d7c8a3ef9616132093d0107f129bbaecf7986ac1783624c6c8bed13402631e964dd98dff9dfa7f4fbf152fd2c809bda61571664a796716a2fb805011d4d81a860c9866115f44a681f337300d7823790c7ccf9161da484bea3ac5cc59ae68e1e6bb4875ce911fcef47d37c1b9131d3a75a62c671b0e3978333f7620dd448834cb79573f1dff4d9ee89cc8a4d336b472ab2a1a888d374b4592030e88edc4cafd1bd9a7d1851ac1649105dee4f8ed52682584705b4e9845a368682b1c5966d582ed9d0b8684eaeadd7dbc1379769b27035b21051ac8938127f9df8a95d0d88a9dd7d023c3992766ac761ee09a6c38f080037a40c345d7bebffe542a602f2619142e494e5db4cd70c4c5bb4fff5c1ca84e9674dc633cd91aa2257b6536fcce2c13250da1a7cb86005c5a611d9b8623f789ef541fea905a0e359afff93461075fb155041ee7d483639afaa7dc43e014eaa9a969c0387c17de08533f2c836dec1a5c2a0955919591be1313dd18ba42affcc70d27e6a984f57b206d9ce54ee8c84eed9c86b121cb33a1668aef79d912ebc096613ff876781e0126ee2c71ccd1a05f854a7b07879dc63ea688942a48d99ee49965d9790c977336d025a2f6fa2e9cc601ce21f4e70c3ff0518056949e15c717b00eb28b306c33f859850a6be27c7da43fd16c6ad0223ea76d956ad3d3cb980ca28f500ec2fc5f28ee0e8429faa281d24da79ae7d2db33ef160db5aebe793a4fb2367171008782318410b1ef4233dd455ff9eb55926d411688594c2b113f8660d7fffe93f9ab410cfb74eed3322e9dd4b9e652c82053db59bdc80eab3ba143fae04ed1f6a00baad3b4a472be5797a6804919cef785d630b86e8f6ea079ec282bb49f6655a23c38428733ec4310b666dda80d68eef195ebc92e893c8fa6ebb11529a02035bf2dd9ed81fbc6bf0ea8a5b9f7fb479b2aaba5a1b81f415f6d280ba628641926ec492b65dff8bf308cd155baeb39428549cc33e0b3cff69ceff8bb89607efd4c781530c5beddfad8ea0a9739ffcd3556f3b31841e8a83787199af6d920251f3ef39b1e8a54960e2ef908719ebfccdcc90f17cfe1f35c8ed4a8d7fdc1d18f05177e704c9f0c17b1766c700098659af522b39f8f94e87f077eb8dcc321522d1d2f9278f4b4694f1758a5af37243ce1d8552a2a707faceaca39225b2ffb9a9a8d6e5e9ef921b676e65cb170adfb90bc1622e12336cde37ddf25aa91f89ba2caffbac63f53cdd998d8375e23dca55e8637cefc65477301a684cceac067a1312be688bb79cfdb5f77f048b16bff4549eb4b509d2fbd72e61d5f5a75d0acf803705036a098b47583348b3a1b26afc4d2f9108f22fc04726ab3df27929743f4aac086de53bc48296e8b76785b1cb6cce9373b9be8ce35360358887500b6df7a4a3438989beff97a72fd718b3807b914e0aaf917ea2e63ff689ce358339f15c89cec74b24a63e7dc37fb4ed4a810ac05220c0cd2104d73fde8c55d679768d265b07b413b693b8e592507878077fa556b25bac82a07764e4ea53e7b5b768cf6acdb2a78a4587072aaa1fa0dec4ad5cbf4c54251d9a6c94a1683372b225fd6cdc63b8c5cd2871f9dd16e1af15429be3ed1afd2f1b7b987b4b14d8a0bc04b52e11df2bcfc566133e77f6d304a6aa8a4a9821a85972bc5aa25ab2a894e57b456c791ca17aa705934e8186b0109a1eeecf24c1ef7cd7e65d15d7a6e2c039f9e8f47409b93bceef27148f5228da066e2a75eea9a6dcb331ea8edebc4256a8a3ea209254c650305e220e8d27dc08f04ddcf49ec25b510cf1249fbf5c6b5c9920155432e39c610126b1219fb965f52c655ed674b63c3d87b3155579979e8a907c6ce3aa34638dae291c7f1c9902f4c757e649a6873820f8374ee737420807a578d001b70a753fb50c0d0a8c47813e5af0f43c92c6da011bdb8bb597fa4c12e06d39e6b7c4f55b2ce08474d8842db35efd66e48f5b3a98da58a6512c1703574d22595f1be8c6d0fd49109d92581146acbbc50e047bf9f23ffede480804bf45c42d621a89b2904038d1c98a44dbdba45e38b3ace92aef8ecaa58914aa484496fa6f3c105e3b568c8e42f58ac01a4d98075240b4ac127fdbede92555f9a8a775dd3cb1e6b07bc259e1e22b19deed5468bb6b906058bd2c557a7101d645ef159d53e63405e0a17740bcce9f359eede8ef35330e840d020b6665b49a7391ff955ee82184f1b7cd9aab7a74b371dc16c027860eae407d5a691fa01af621844a9d62acacd36d78feebf459c1d5065e6c7f758e211ffb13ec2e3cec9f796cf8be16b5e9817803f10f42670513715106584f5e8e93d56f012062a2e8c24ecd9c5ff3794b5b1683b2bad97cac8ebc9220f802139a1d6dbc8a72a7d1c704047b1852e7beb90dad359f5b98dda7031bfa7f850965a0d8e6afa5f71257e158b8a6cbb7221d179c87c85df26b2805504ba32dd4b0ce8e81ba6861b64f68240fe35bf4e62cb3ec6b007de653f29ed53dfe870c5ea9343237f947bbd0d0730ec170c43863c1846a503a8ddd790dcd1ef21939c845c0be12445f63aa336a1ab934b6c1e5a2ff3b0e6f76f7a537f390eae3c803ebf09dc7fdd733ad0f3e0ea28c459ffb66e1953b05192888f3482948e08169ecfb7a228f62f0801ddd247f510c4827a1b899e5697d06852fec87d1c6ea48e66b935c3efe9ec52a1a36b3b823bbdea8e4f7c708758bfa50928affa2090d53f16f46acc98b3fb31c22e6d30517f6fa97d6fb6634c9194ed86ba0e6e6bfc1699dcb2bd40f7c25d2e8d02b835219a10fa4c54850d3d027123fe99b6fa7483b3a35275ecfb52750bca6db9427f3abd6d4bf108476bacab3c18759cd3b775330588e070bfee028c64b580d553a20c8f2a1a0723646b2503c7c3fb07d1e358e6121daf7fb4eb3cbebecd6a87b1423d6ff2b210cef530cc05c76be4d7e87e2bfdb2565aa2fef48523e7aaed5e9a9c0b22cc17815a593d4be04785988c77c43a0960974f292ca52729c388c179ff44841a7afe66d37b2022a787cc7ce228ccf8a1036d6e238aadba585dda5ab01c855aaec7b6b0d571ff9e660bd779239c2dcbcb3b03dba4b53625f5b9201cc52291e3992c13360be7e4774da0179c75573c9f992aca3d44c0f8b1804c3811c45e15266c9411d1df02b35aff613540e69854c932f5abf33b037a4d42e2eb0b16192af1a788c35dc0e041eeb2b7a8978267cd5c6751540a6de1229dad62625b23fa48a12c85dc845be3f7b30ae0281ead1dbb74a36da7a7942c3b7a3c1a6f95707fa0a1d6250ecb64a45aa3479839526e8b5fd47a09bb3be6f73bb97a8eea05d1b4741c48556273d2ca60524cdcc69e61f5386331c8e83fe4a809915965d86c5de4b6cdee1cb0f53eada28506646bf3106eb445bc496798657de747a825670db3f8f7038dd1b6b4e8cf13133c5009c1963b590656d5cccce617f6e3e7a9b297ccc15f8a94f0fb41ea5cf01b6c209b61f4142b6e479d4ec638284f1ae9275a04b99ed9d693a53e3d20f0cd7bd3c5d9b1aa3e40a8e29f2395ddb4103b571f4388b4bd87e04bbf9ba39300cfb76405bf4920f2b851d8827b7fedf0760920259d78ec057c27a14b95068648addf06cdaddc0fdb7b01dc30ebfad81bea9a2c02c5a0e651d849705e009e6003fdde4688e36ae09732a254467a547b8e31abc923c443bf58a1fc712bd9bb6f0c22c5a80d931b1f1d3a7dc5624079299f3a33b99e623d3af069023e94adfb7dbf81be8066298da327368e9e09ac04585f1cb59d1cf8df997175278016b86424c06a08a437aa8ed61b6d14f819c16b7b6c1ff6128e9a53514f7c90be4bbcdcbe527dc24b8d4b2a3ef1e5c0e70842ecbea6c074383699d9ce776b6c54c3ecea85cd8f4750c3c7b7fdcd3a12285caa9a08847445d4bf85bf6773d6134f8fc7240002aa227cf19d09e41520e94d05bf496f41043ce09ae332212318ce066f402792048a440a94775873f9dd67dc5ef687f27515606432456740d7c67ea522b72da886adca2977ea9fb437077066f7bda2873a166682adfb0321efe21d1653f746b8f9b78a91ca9ffa89a75c96dfabd6ff31c923e820ea946bac81e404c631e3b8efed8c34c41d55c30dcdf682dff03d549ad586d3243fab6db5a1d86953afbbf5f7e8d7ca6f6b7c5e04bc923c7e00f5655573552fb12f5f9ba9104902b8c8e37e4b8c5b76d84756c4d2c2b265d82b5776def9cfb97dc6741f962f9fed631ec69a2827a277e03b29bfbe61edf89bb49fe4a5be7ebd6c4d8e8f7542968241031c6bab44d8106f01c0b0167639d64a9571001e6f91c0b2fbadf47caf0ef8c0b762f5ca1b711c589148905827ecc34c9e579374da93c19066738ebd72bf5c68b2189a4fc76eef8f2c8180b0c5f86b83bb31a6f7225106dd8a72959375d7bf816b0a733478be965e1c8f6ab16a9c11d089723c067b30d1f7d834dc01591f987030fbc5d30679126ab5f059e900f14652038b13bc5cbc63a88eeab029fc23cfa579175909cc3edead5209bef7681eea67472185869c8b02ab520a7bb1d2aa5421ff5c3ba6a49ac9478626074c2c88484fe9742a8d9cfae82e454dc7ee1b5d54e9c8305db625204bb057d7f4386ab94e3beaf539516907a9cd121bc1b70915fbec2ee14418df325bdda39d08277210fbe3cc7f23778d69fc978f1ba16ed84ee7e6e53f45aedb3e731917ad2dc015ed8aea7eb224d6297b8501556c0cb700ecd12f565c8cbd17e44244a8d16cc4e075e9345edaf871a8c8a6d261b2a10d440a25e5f0beff2e2ed3a10a43b2a05b3d2c963ecddc706f1ea32268ce0163ad5479d1dc855778596930d92559a42d5b035c68bad14ea417968125ab996d76408de6e6de06c9d0fe6f9289be8e34dbc743e40076c62d45305402af695ffebf0a925094314e4fa6cc36ec19154a424b4c404f6dd98554438aece5dae118043b4ef2b77bfb8228e21c094f92f926289a67d48f577802c93b1e3b8649dabf589c0cc59ea8b01d0dbcef8fec3cddee4e9d4d5725cc6492ae3853770c5ea7196e2e4efcf76aef361dabf727e07a6a617ded4f2c1e91552a8503e2e2903e3af9e45ea3bcec261ba372b08da6dd984c4dd236a4f7375edc9bc1a4fc2f3b352d59e2a174371066d08b7725d5d4ef2493ead762900557afe6c1ff99d0f0a27f30fee4bca8d2c59b739718d0125d5c4da2e0ccc0b5cf1f90e20b6b8eb5beb627e9f9e10270fb1a3e749acf58230a2cc169d0f19a817bd1338b0e4b9d125fa5496ae7c3870c0b272ee3bf3ea56a579a64b76718ed439c83ef77f34866f6dcc407784caadf7022f4efbe902c7778c5f2d399b7ffd9955e09f36bb533f9354f1c5cde8a24d4f3b261996a76abd77324cb548eb0d90bd7b7b50fafe88ddbd1b033184aab191a58a05ba669076bc9bffe2ce9dd9f9112bd4b0b81fe3fd2d51a3215e420bb716ea9fc891097ae58a5640f5d06dffb5c8e3b0a962535d7337c6793d400a5e87a852b7cabc67d6bf2496e08169c53496e217e2f411cbe0f7610a400ddc6da6f2a2d3ea969a24e7ab187cd5cf7edf34486ccfb46a3a306174a345b4560330047e5ced9f433680b8a3fa3cf094d0c39192c200345185f06e5be97418fd9616fdba0e3bde7f9a9079d02a32767534f77c2e90e5322d31689c7146a6855bae07312b0616b4b43683bc38ebbb43a9ca462e362f44cf6cfa1dd4c11fe266265e453b5f3889d9daac5d2212a45e6a19010d51c8519a0d9c629708b32570a885117d4c7efcc034e82a01064522d3f6a95ada31021fcb42befc9ec0988b527ef84c8fd80f5ec4ce2064ebfb713ea892408cc1586d406271f907add55cd5a4e8262ef7a4f13026899adfb0d7954933ffb2e6fb4a81e055156fbb8fcb29984295b4dbf5e445ba53b703a6237b7883afe38ed8ebe852eb7c7c8cae3c72a6e3aaef333dc4261c6d94c856be6443e2f11913b146388f71399a5940b9b3104e8f16891cc4c0d39bc5c76b6fbe1993cb5e1410ec2b1a0fb3652815b8ed7fb8c6bae75af3582818443c43e513fbc2638f089610d23782cb79f26e499bfc9a952b5edb596077738dccaa6418aef620b3b8920b2e3eee0837b7c6667a16c125b9a3ca8ac06fb65242691dbd01fa6beee8dd61fe435c9ef120b92a3e92594b47fb8036cfb4c0f1ee85e13c88482169455cd06e06c3d7999578e8746ea553a4c30cedc50d68c0423a864d10f59d7f0f1a1ca1d1a7c1055a8d6dcc05948e3206dd72ce42b1bf1965a78f8ba57da25a68665c8f1f85602d1e20db24ad4496c563c8b2e960014de75224d5e72b1a2a60aa8623f2c380208e21fa1d6a9d35c4a1df2cf2191103f2f4edab9bd0ce6a65021aa62033cbbb69a942192ec5071a3fffcf0c2868aabaaf9b3b17f3c4402c69cabada986e818bdafe79a2654d5c3b881eb9dce1ba81d6df5cf4baa80e2e8bceafff092b06fa80bc9a09ad88ca18772b40677a9d624917aa594b95854ee2e08cf26b4e1a6627b9a7529672fc1e3af51de2702be5d0963d227cb49a7de6bc56f40fa37a26cedbcac172b15bb4072000c203cdc3cbe88bcc3659635888c53e87c4e5d003c511d5559ef39c231500c0584515736790f7f2837e38d273ccd975949b896de1e88d17f35bc697e79e19796a908bf1cea624d02f7a6ef635c53f6e76a3d7b5657563a0aa5d23fdf20f8bf447562780de9f18121008029d3ffd3e4d8f8cb9927f87319b103d9377ceafad2cfa78e98462e2e5fd97aa3f3aa22020086b74815484b882d5aa0dfd0e1eb6eaee16816ecd2676157a9ae88dce916a8542344867bb2c354d7033512a45081653202ce7cb8fe1a0c46a2e35408ef508afbef2fc443c9fea73f816c2d7316fa53a9864973c2e0cbf12cd1f9adcbba8988ad8d27c3056d266e07cc83bad078dbb63805e22e931a8e21b4658e3bc1e35fee6359703960046a640846fb4df60765f73c3e9bdbf07718c60976095906ea57d75e7e340015b00d9a503b5e80066b330988977e8d24b1b4edd7ad7c51be8495b8d6342740c0c6dbdca73184c51c43ca6c695d2df13b20376d9ed5dd4d619465cf864d8c75d0b80f30bb696d770410015ef8cb361299181497c597f7415ab19e9accf64d990430d7dabc859323fec18d805e9cdaf65dea60658d798d728acd0da128af364caab341aaad6bad9ab7c994878cc48932048dc294cf19747bdcc78d45603b118371ff89627f1a8a83249d320b4ce387cd06f0e2c25e471ea0b3946a98862b27ad6fe47d09b3b1b85e0d9f5fc4286a063a710b2e7ad161f58e2706c34da59a4e1b1e3f7f3ce51d35af880572f65065e7c65dbc5a2a543e922d13738e7d7089f7ffc95229dc474eaf0689cb5d6fb79071a54982c514df05b78c0f538fefe0660326f7a184494cf9fe64e9564218e060056a92b364ea904e345d7d1aabb55594a9764d3a696f55c9f09fc1188a5d971610749532e791662ce6f4e1b796d5e2eec3ffefacecc5ccb39c1571c3ed0cfa22fd75a924af59667aa1b3aa9a6f47d3d3ff1298f929dddbcbf6b814df59c504b37952c7df6b2f66f4f6c9b87de7f5793b53af4020b27ff34a8db12e74e69ca2f60ceca706184e6187f0ed2b5f61a83b3f4005eef64cd873480855b62a6e926b09ddbec0268a3107dcdb31cad8ec174b30ce6d7910445bfd6254765d3c7fec1092b5ec4752e34907348bc4230cb8f3a0859c938a9ce73b7f5fd46f7b0d97a47d0fc18dbc80ad4778f7242d87b8a27a50a362372b617276f6b669652b2a7193a55daf51e55c3281912a46b71413f093773ed55b7b56f29073d32f4307bab65de5d4031b02a733d7114811622c7f492f8fa5a5cc5a0bb806f0eef32e8bbce19b0b1cd8e231059c6b4e76c20eede11814eed85d6bb70f2927adef78e273a5e153f5b974dd451a8d093ced3da97cb01c5bc34fe8b6e3cd0e6fa97af357c64cca95df624b233b5d40628e3eb11fd5c3c1dc79dc2395633beff0044cb17d043ec7d519edb8344baf51acd6e72d6c1c1da4022673f67b4a7d29c34ca7e2f44bd790e820ea64e8c84be7b6d45c6b7eefdfc7627673b5ad73bb736eafdfed6f462ec297fab34cf956a5ff3716b148a201616e69d718a219729175ec38683c01eef38d29835b31078e20ee3049f7459cd9a04da3414e273a35e79aeb3ac919e0119c9d8d71c07d85b8175c1ab3be8629f6c1ab06d7df0267400d1f0db779333ff66fa6ba96c8618681c6472bb99cb0f144b618ed28d3544899eccdc64ebd3b967f217a5fff9497f515ca6439332fdb74b4804cd63ba476d2f3526eb73153f97c4e9e24149f24941150388cc7141a2d26da751ed256e734fa6718b8aca14ef3f70e26af7ce0b2c0408e6fed0b39f6519e8a95646d065383ff55d89cb72926efdbbe0a8303118d16a3247377a1bbe36678d03b4c1bd0de03dc2523867b21dcb6e30b48f7298c21c25410761e65e52702035a88d1269049af085217992ee8814d8d5b1468690c5dfed9710de883e11656b7806e1357a9cc9ba5d2daa211fa1ba77d8af5688a249516f3402dcde6e309dbda2b1864975f973234be700bb4a6928ea959a0727173b81ef9e008f8d3441c8830e4a15ec13931d7f79006c3ff68b31c5109db5d0704f14c912f37ef25e4691927c7355f17ecabae69697037b4bdc2cbfb43a8eca5f621dbb859e450b0482da374107687caadc06d9f48853da4867f57a5d0bca9648e79cfa74e08130317df0ab24b8a05b6d62a3b10f0f863e40bc345f05b978ebcfa6d291a9767cc5feee33d06e3d20eddae1db1ff3d66241ffda75e9451246c88cfe00d3c3212748a032d91ed7dbed9f5473b614b4d3e3330fc9698c129ec5049779ef155b868524d221bcdc860f9b8750ddd865174daef6b48acd30ef07c5536032f90b5492231deb1bee9a2cccbe6ac447a6df8e101b45e08a8002d7aa72cbac23a931c5d0cc66854e38ed31973bafead9e2db80cc008b93b3b572b0e76d74a83770f7bda2b6d0233afb998b9a49c2af56e40f2afd7b965da6a5bc2821590e7713d05c61b4cc90cfbb3eb50fb48dff8397ecfcd18539290374ec25e63b50d7e43955ed52724e984919eec19d2b0d86e2d451ba075ab5e5d458c80ccbf02ec44df43b8846bf765d2246367ee1c9a1b4df8a23af7909754ae26d2cc07f63574dd9e9f505d20bcaf0cf8a41af4cf81074c8e66e39b1755ac207e22ed96b2b37cc224ea2dc9739493b32eb6b12f766cd61cb488ce865182cb02a0438d070a1104b81d8071b9ff4c81fb31b5ec6f3029d5b99d08f5211de44f527435da59bac9f681a7f277e150b85aae09af3b62fbed998e785b4ec5f739a6c7b8a5be21464d0055e95ee084f80c72606b81fda539382a11cda3ea9bfd0c2ccb5c40a21d761b3cb64fef09b0241ec10acf3cc756b5c6324ca7ba2e8ce2a2babe11b92731d920ab20ab5d8a7ff1d8dfd139281946d58887d2cce33680ed656fcf1fd524c8896afe01b3d90504b67987ab1b0f3f1b1075453251fc0c7d875b15889ac98368f87bba61af968df6c10a6fecf228a6be0db76e0555fb4a9b99db75aa62f2e8613b0e75ac630ac3d1f48dbda414c3f0a4592af39636752c5c12d43b5e766c2036216fb76229f59831fa4382f492ae0f924e7fbce70007bb669b3c8db0c5d4426a1f9c981fd398703afffe0270e279c4bbcc15a2a5220152859b14e9e6700fd528bc390ba7a314a09b214bfd9de1504be0bce3def16df6606560fe548702bc5cefe826ea312ef471950595185e6e98727e786f8ed025c2a06ff67277d83d7c32b84888cc5d0c8c2f5a540f8496606aed2c127214a0a7567f471e1097ec6f42aaa22e0ad3a5cff0f3b82e070684d474fb621c94eb98370e005517048bf5ce3a6d883e0fc69f4827dcb2620dbb31e047c90a326dd27145839d48cb577c5699b85e7dccfd633217c0dbe8b259d9dbdd9ee967442bcd4f50f4e52262fe7f93886b5d8f66ac29839620b1f1d57aa39f09b5027326b93ab4610672a412c691d4c4eec76cb450e7df6b30d1d9a16fb0f5d379e1c8094e6eb4a158fffc9fb2c7431363269f9254b37b6aabb8aa103cae8ed2d1ef36ae11b1a4f631f3a7d387e56c9d218622c91b647dcc3673923e7fa71f8fe6cd80f75783e755a215b179b5efd6e8d96145bff3377f54b3c7d2d5f69dd84af6a96c607b35fd6bbcb4984cd1fc5716b477b21295c2e0756036da1109d0ebcfd50d7413b0a3fbde77860336ab55a3626a16a281316101843522296fb4504a91ecfeb2dc58c3bed67b1c45875bd98299d4a58f2138544a1a3ed5ec3e822d9debedd068dacc6fa2e1bb201db7c5b879642193e6efeb02d7df5be891ad9eac9735295f464c41c41a2921b4c3f38aa96d23f0424aeedfd9acf2852a1e09c471db4c468436691cd39f6752b5a40d986cb6afaa1838a06f063db54ce458e3ec9e44f6643f1a25442f4c1d12e46f6e377d4e2917a80a17552a932cec76ab79f30fb0c8bd9d809b0ce2e3cc3b616f96b4e5a919f0eab5651be710ab530220f52b5ee7d6be8b977469a5a0a64b19d8ff3da99dbb5a7830690b857a64a7b0b086e62cff008382b38a41b3372fef8addcccf351d506d331fdec2d4a8dc1d0d2ab80341fe6e53690555bb21c277072b42506cf77fb3c34be99a70ed913c9f528e30a20d9b575666d506a85bef2d4231cb8c8c67358862b2ffe3505a07f2255864b0dd9b8edc678729eb1adb0b42d944db986b48a70ef972b8100465ff8dba7f014dbc97b02d0fbddc698e56623676045ea0a77c374d9113e19489acff35d83a8788f1b8502b7112083b2817b2995a594bf10f223f9fb50c96aef51b5360d3c3899b1bdf2a20f6e865d12c69328aa8654f2b00f4f6447aa2c8db5f0885e53a2327d01cebd1accf250d09ae73c4bcdc16eb2791b05a713398d9c008968658a606622341db69cc4f79ebc77e13c620bd5c150ce24be7f184708d85ae10a7f89dc07c91c803a486734c67a92bd4c44129eda79262568cd2b26bcefbf80fa9ca1e4df5a1361c7a3e0a659574dd4755ed25886b6b1a9df7afd7ab4b5139fca6bbdc88d677e5f60f4eed9f15a9bf7243647448b6e4a5dfdf34b63d157a5b80bad119c2e296280f59e4111d3e8c088b9cccf33ffd86076ecb0163e13bb4aaf0b22dec3266b55f038f89e6d9317e871f934bd9de3b4ee7a37b11a3481846f913385f22d851f1f6f9b07fdae18521c66f1e81dc38cf1880016af567d37e218b7442f3a399ad9a949ba9e3be7f8052f48b28d48f6aa36ada28a49b8a5c89bf6f6c8bc912436b4f9664789c22c4e2359d4f481825255eb3b21aa59d4b47ba32c8e5abb294f60a11a432f99f915fdeec932c74414e66219b1c4ca87ce320779f1d142b9cc1ee4c539de3df919690975deeca704482fa2800fea53111ed32ad08a4b6fc246b81a07cd8a44e19b14747ae213062771360277eb1df9683a5ca564a6bb2579f1d66be794f2c613c813534f0708265d3cfa7685f067b0b40d07c0cfd1e035b396bf03bf7b4c89db4a5209098bc137128d483429073afe13be1aed58d9b1eadd64b505d268217ea8bd720bc0833edaeb086229adfb28e85e2079314dc04600e423fe5dbb27404effee5287258f39b2b0ca4dd04d6eb96c01dff1d71e9be4a57bc8bb45638df2ba9a8e52525a6a3ae33e3c0ce5ad5c6be18942a44c0de85890b65053f50a4479ab7e3af913441347bc1416c1f8ee4d312e0e1fc1576715e9d072c5c96ee18de682e52537b983e6908d6d733d81629e4c10b9a4fc6bcf2cdc93fe329cdd667474c41d1084cbba8b253fdeeede17202afa343ba21440d3d374955bfea77f1c91df45225568e03cc0e8bf4e1dbfff0cfc583811796636ddd8e84757a04a3d8737321ce72156d138a34f95e7da9637cbd28a9bf5a0d30f763e5cc14819f339b2af05c24b7d17f345713853198564acca3f2268f98b112642999b1b5a5c00cc9402380e46ceec952fc6891d60e8fd5d3f0e0638aed907319f19f0f72714388e0073640c7d13b5417f6ca87345385d747215fec7290b94bee61b4270d5f95ee5bf8727bfe7eaacc034d192e6d012331af7b5f970d83fa80272d74ce24082fa83dc5855afbf7e0bda34a40ef70649823feee6e3ee9624a88d82cdcaca9d44bcb82dcaf91b7957602c807e0fef8265ce2adcf1b98c7f3d6c60f44567341c4a2a12aea1273e6db9d3e431fa6f209bde58e873652f615ee04ad8bf2fb7104ace381c18fdeb2ac73ed4eb8c1fbf172406e8f92c51e30e72cd10e3e14deb3c567bc90187d19b63d25eae9ac49b23fc0924d7757ab1bf7b18955be1ca4c6dae765004f58d4a71f18f7ad7225bdec607077bce52e19672da1cda166661e049ebc75e7f0deca0dc8ebfe3f6d2166195dc178786b07d8066ab576f8091d5226c1e9661b8d0a1f1ec3d4c70703e93c61b2c9139af25e150feadb39db166f16ee0b53ad0ca5ebf9c348679c23b75238b5bf5f3f33e6bfcc130c300b7beaaa859cdc644f6f7e490b5d1bd157f978b42855022e040a8f63e0998d29033fb4d8b7d1803e374bf6f19bceb29925f28b60fcf07c514b6ee1ee10725980e28b7062d57035966e905899e1b93bdc41c41da2ef8ef7b48b107603a5074d67837c4501995cafc837eaff9836a092425f70cddfa8f49e3255b27a24890948b0505c9c692a43ea89f3a31dc2e11263607ac471498fb342443fe353ccf3a5fba3cfcbf4540db554c8c34116d9e5c1477fc157a625f0e93675972bce55a080e0acb6a528af5110c8f8392ccc3731483e809e902f8f9640540839c77528879ddcaa4e53a47f9820daa75236221931d9cec109af2eca3080c88eb6261cc530142a809c2c5dd596a572c0e5fe35f7d76d79faf9d61447274c00ff1d1792375f265458d9191abbdcd5dccb9f2360ad411a698f66033ae42c4f94be26580f2cd65204d13d214c0c59a788407ea8a8ff9fd531cd7ed9547ed6efc4a08df6f12844639f8f86fcbc4f5020f283dabf3e426d0ea56afd11ed49ffb19fa4ec40ef199fc3d24eb49ae1494a0b860d1f76eeea2a1fe619528bc77bacb5aaed721bc8c49262170a327e881a958be012770aaeb854cd916db785b7640da2ba9c16208f9594d47f774acf7f0b109136d78cd08cab33f91a3c315b66d6858b2c5b852b05976326cd42d03778562cbe9c497c2a6cd92a005791cd20d179bedc936cf9029060061d2b4f6aea15bcc412825736991698560e4f33381a9f33e3202483a2db568b9e185b2dd6e6869b606c0130000a052ad07163167685ffbfc3f494a1e6e192668d457f4b96b2f049180858ed166acf61b7768e6c3a305fda4ecf547f813b6678ad3818e95baf8a9d10c45711295cadf5f0c6e3ef86e9e2f344fd649adf208dac98b206e7ee500978219c8eaa22141ff63df0e4657f1ed55099da6655bfc1c78de8dd8bdff2389e6339250d5986720bb8bd4f55ce24a64819cdae4142a1fa5ee221eedb08b621b9d3788231b2a6291be5d59f059efa21887444073654c9bf367c54b153b1a6d5056d30e6e6468fbe125a87287ddb9a5972f06fffd18dd69e56802c17bdbd94f75c029d2b9f4dddde595d4598b41340ebb04505459297939979c1e8b511b33421270beb6ff06523a32275761be84b8885f5f72a86f539b7776a92ffc460fc1fce20217dd29d24e18f78e883b687e8afac6a3514f50c45d09c92853c8d3e5d20ea34bb005a8139bbdd7c379b560720653b5036b04f01b21ee96dab281b5a3da2307df07e48da23a64518e5cf91a4c5f18ce0f013dd54e81d75ed1922619f52003a15b671d5b97b2da1f1a45453334e48281c7a7e7fecff01a351ba067926dc94f3d9441a9b1df6a9220a4a671ca5e2eb86e894f445c74a408feaffdfe230a57a4d4150b30e124c6c6387d6a4f3f230ea34543bd6ee8c36d494341eb69adc1201c94faf84e3644a24e4f6dcb2306a44c8fdbc51b7ff31151ebedc51b61ded07b778fdfd0c95fda3f71d116d291236b8592abee57ac6241144a5b855021ba12062553bbc13c7bc223065c6f342897e284b05c1f261dd1cda81cecdeb882c01a606efc54545a9946fa4952ba794d70c8755b3d75909af1ee1ce7b158e77461f9dccb77c7d2a09bed55d938c21d89f4487596320f4b328bcfa5b0381424a29d12c358480d9f9941827521f197fb54b9a1eca431e8ab0192113dcf844de08024879839ebb10ff4ee199340dfa09cc6b48b134ae7e338b7ee19ff33546c45deb7cc891ad973020753f4e2dd6bda791051cca0f6cec334e94b3b7d17714125acb80ffb871c7a72d1f9ec6257cfc754bd48be87bd51a072c32e12d556f14242adb75229eff7299df4247e365ac38602d4b2cfdf8272853b34d8432691dd77bf7da5f64cd666db417bb9cd29a40bbb5208770600e3d643cac4fc1ba65786241b69891df716714d67e4a088ce83556025b3725fffd2956c69706dc497b3c7f802e6dd89f64b8c2c1a6d52be675c818dd6bef65902e39a7b3bf0b05794dc41892b7797ee16b7f4b7a4ede2dbcbd6b4e183cb32f293a80e9d8ea1a52346f5533090e622d70ed10442648d1001430e5c04b38e6bf46b811a8da47fc8ddeeff261c927f55d9e842eb3c80212f268c1e18212463f8115ab465e78d66b6209f05216968781e81d50df1db3456fccc8059f83edcc9308b96ea8d23bdca8e5e5008193c4dee708d4750c8e3a77883f76fb5eb5dfd80a1f94a5971dfcbcf753c3b205239ece1fb57774b0440ecfad8d6be629d2b82211295606c877d492807ed6a83a18d8e1221c8916aea03be4ff42038709f77b7ababebfccc332c5aa93d9b323ca08d41956d6e9eabafa48797ec0b100fb28d56a32589123f64665c6fa3fcd19a3b3264123c69ed9de4253505b70c6d6d96b277ee5881fb533eb651d06b74e28d9416372b62bdf8a29a90fdc2910b727ab60f66eae5995480eff77ddb50d755548a0f4dceb07f92268ac8041a55b5ab0aefcc6eb2b94450c75b083cac209179e644704c3926ca28d1ddac52a69fac6aa291ea3a182b9b5906870728cd32061724ab4af7dde65d80c5d41f7276f0494a4a98124fd57a93972cb385c6d72ee22c1fbd14d5dcb6e3fcb53a27288cd995e8367209acb00d756158b4ed80124c81dc7ff56c4ae6a73cfb67480277dd3a4843ff30f09979ada2fad0238a88c70af5567af9b6f96fed49eb74a49fabed77312309b387189288a769f26c252f819f727cd10e8201ed81f7afb5aaa96d9cbb72d221952f16b89bc2a678434b6697de72ab62be0a5d628fe0201643fe62b1c55e472170e7cfddd570b5c3ae9d315edaec6ac8b6dc05b0ab460d55febe3d2d5729d736b52243d0b22687b3cbbbeacb978bd923bf135f675fbfe7540056c6e1d4a91d73dffd6bcb1295ffa9e963d8d408739da3ec3d2ac5097befd26b27815a3c55d17cb4cb0fcf7cf91b15f1b54f1daab35e68025c58decad1e89b78bcd1a3210d62b5c8e6e697458dbc3edb0a7868fe31ec829a0dabb4440a6acbbc59b24d4bcef0cc993c1abfcc2e0f4c3f61cec71714179aaffee0020d27cf26eb8ffafe5f685bb0a5dac63c614fa2bf156451e491e2f2d99c7fa8ba80235745bc2b27451ba580f193965852e9f543644a5e96a841d61a6b8bb97d2bff549ec4146a46fc0a204938a6bcd6c6df2388772c0bcede81c5292fac2562e681da1873b7ac1ecbaf6a26dceaf3971266671ecedac13e451387d808f74b674077431e9df33efdc61f4f8ddcd173329fc0afcdf39548228979e6ef2126f4a1814d7b329ef7b50f18d013e64ab26cd4ea0e23f1de310375cc4884e6481b3d386513ea5b608ecd59058a36714d4c3d7a7afd759481d31cea19f3d27a81ede760f195be678313965099f7dea09633c992756b20f15a5fed2c912aaa0da6f8bbac836eba731fdab7709446d001ef02c4683c025bcb2e53530c9e68402b1ed8eb0f988ecbedba7f57c73969f931cdf5eb86cfccef1436516f2f68874b87baf3529d2c2fadf109b0b7abf9a95d55e6fab9d53c0e4aca9ab01a3f23ac7934e9f30d10a126f44d2138b3a5d3c644c3a1eff5eec515deefd1cd802ad056e93430d7f3380f73a20bbb7cdbe8b818542528e9e3ec0a18b44e0e2e6baabfc2f1cd29e6b85142955fdb08427e691104ff01fb7c3aa6b1aace5da5d125900d00379d5a6494f8ac54e5e9d7d2604ce7e6351ed128ca1177788b1c9e8347013f2ca3af21848c91cb6a440608ef919dc5657e5cc6f08787b6e91eac051013a11c9247d6a6b93dea6d5d537f203ab9d9f86b97e1c5c25dc5263712853df8305a478cfff172294db29eaa0c3133a1a7a6e145fcc3f8667c6d9b4e742b94db560afec6e265be830f38cedb4357addc917d42e096ad1f01386d4dd93559cdbaa05c4f7783daa7a2a7f686a2486ba2dc34a387a0601227eaae6642de5751c5181e85c4c124553bdbf2f81cc89865f213f4e398f33425d612a0e31c0f7f99fc22bd02b41bb1da423d9dd95a8764531cfb9b6b293b606852a01a140c0b5f9c2ebc3abd6ae079cc65bfb16db649fe8f8c5303bc379e93c8e275f78073fe493ebb359933569169c45142a0bfd3a09904241cfdefe60408c9bba7ec9f94203df99e1a048f8f3a2924885dd3196941701a47def121e772caa84d314e1ab186d4d19b2977fb965e3a57a0f3c57878677f49fa47d8653a79c8eaea1f44bf255e266dd98e6e30825148cace93e912dd09b051bb436f906d14e3bd23b89b9cfedb5d6c7b26d2c6ba171f461155fb02c0ecb68f6705c272f703a66bef19993813d2d21e159d37ec830f50c9969a1469b9f84795dbd1f170b44062ed2baa5790318b9d391c95664443d3fb93393233632ae313bb2f84bc49c671eda93e49c5d12aad5bf65b07ef76c7b03625da0123e16122a7b43bf6d98aa929dfcbaf9eee83f874e9a16038a58677aadfdc61d529efb99b17c35b4fdf4b33e7a962cf3ff811a4f6efdf41e214de99f54f405fe0d797b560286d5f0d9deae0ed702c6d2fbef0323fdb5ab85917b654cbc8924f7543c6ac9c2cf4b4868584bd6ea906f85ad03a9ca7effe2420f31e9e6be51318bb65eab8a5628bad87a0f25f42dccecf10269e3345ebcd06d6ee5226f61e8fcbf4ea61d9a87de42da93e30efd3b3d112be2a7650be5bc035ceba7704cb252f3d44fbf659f971f5b3db3742002725d8f7bc0f1da5bc03e521f4798749dbe432648f4a0f7abc9301f4344785a89ec25a1c03d0ca8ee10c3d7d93b5df33a2a02060fd3cc7a8e7c6c35d3a2958a2e73aad6cd6d748856bcafa2cbdbbfdb3b024ce6e31d220909d059b4cb626aea3cd823cb9ce3a84081e7b90e80114ccaa313c990aedc26b3fffd995d6cc2e3a9613c1e87691060f1ff152a6e5ba52159ecf8c1a89b93ca7d7a2bb33c3fc3d3d92c9944a4a1aa2bb4af28b7260773aede5aba750d9fba25ffcbb72bd1ea2267b1fce5dfb356ede592d16e5f98faf13e20c359492b1c8e565f7d2635ffe40278eec7ddc514b905d042a67919990f7743ac71918b6a6055c2de5d64ee57329d101ecf4b5f69a071a3e9ec31c24bc94433ca36e41b44a253f6200b8aeba372b3ac6b59511a6996d975031995de472fe4d6940858d2005dc9c8564c46d25c1e05cea5b46d8b3e625d46d1467e245ae27212cec8205908499961bdf19d26469b40a4db8aa5289a349148695bc2ab01c593809a27656d02d55bc3cadc1f07ed6bc1217464246747dac916fd949a899d494293377fb11378df8575a8e1160f5d9e1385c192441d7fe7f3163386c85140ec755dc9a4d8934aee316e3c551fb45a40a106343ff2ca9551e6e45ba2950600d2a241f489b7dd50eb70cd772cd2b46e1e7934bf3ef9e24b0d776dd20e74d2fecc4c8933f38b506071a4cc9f6a888bb6db52bcd2c90fe0134a765838749b49a6508af4eb8853f6012ffa931a014654b721c7121ed6e4d24a1fe359469da1ad105c7c86a34f222e065a99c5db554ab1b4ad38271e8efd86799050fc5aaac23ad6a1de88b9c4eaee0759ff3a05e7576b636425c1f3178c80c6a86c2569ccb2f9dde80e7c74289af4a5a3c98dce0d676c1adc9fda2e3d20d4a7de8d6d56fbb21f17adb3600242f73bfe2e06355cd410f32e9901a2952f4efdc3b9bdd8a91105aaa98a029ce3856abb00fa9aec2488b0aef06908736ae1ca80a824399a0095611cccc72dd84f2fdc06b5c6b1ab5228c414f49921b13caac602f10a2da87b20804dc5bd523d3873061058032c0a4374dd999259e112c18f6ed0046d68d2d476beb281c9b5bf24739dee30f036044953ad38bc59b7eae95999e79cdc6a81e8c215d58a630d2e3fde675632eaea3bf75d5f45d8c7c501236e11d16c1f7e77be2d5a20ef8d5e15596221b0ec255fea848913fd0d5e61c9b89e0042bb5e32c377fb3bf385fee17b064a9eeb30805da16965e0d46c2c46f286bf83e1c9a75c17a184d9e6da8d2ebe8f9ef9db87bc5caa7a6468424d75d57682c7b1f7350da9e683f74f4e22b648b182691384bad9cddc8b9baf6ec6572f17389ce3b9a85dc56d98cf87bee137fb4f07c808517f8556dd9e4ad3a7932f28b2e3bd6192a5feb0890a0a0590111aa80bd7d8676e96330da8be54578c1b2d9ce62322163d578df5fc0fd6663bdba8a90d85ef212530c9159afff8fb4304c25dae5a3cde9dc2f0c7672923ce8fc260d44cbc42904905badebbd76648e6045d25503067926c5b540da6514ab0c714daf82e953712dc49f5a648e079ac225635678705a959d15fd3484cc8fc670a43f8055e2b649d724434f0c75620a6aa85a9cdbc101fcd3e620ac3271a00319330103c52c90f719c1daf29964d8585d5ab20cb75cc44eee6a9a30a6543c84d7d9152721520dedfc0a05d58224ec29e9fcef40f46c13e95af9c056ea61770ae468043b7b03286c40a5e8b7020369b63a6394990868a3d0df7b742bfbdfef25c0e3ca88fe4fbd52734619f1b438132060bc22ef0388cd929c0e075460ef0280b4aeecda4f6c07bf88e23af831181d929027da22b7e8c7f1f3054c83af6609c5a720c35bc474b15b6c5eb03a8d4da98a9e59bce52dd01a3d047bc77da5228fb643eb63d11dfa7697f7d033b164af89c2b3c4aebc915793aacd0ccd3b2a31d92ab02001586fca6f5936abd845aac7c6a27900ef1809b5172d0c81a8a7054dbde5c7c106733638b42071964406980f4792535796ed76c98a3e9d8accb3e3002cfde87f53d4b7d1e693f919b7225d4455425955236f934f9df08e70410a764c900e40fb4ed7cc766aadd4730cd81721b88c42e5bf9880b7b99a74a632211c9a90f667d553fbe8e61527e93fc598d2d4af3967326966599f2b3a1c8ba56b8dfa2390334da3ec175b5bf2513ff0543ff30b0f6e81b6802878d64c25f68d717d25a34dd4d1aa8e35459af9aee30418e46f446b9cb10fc30281812247d0675e5f5bfc4ddd2bb1c92affd310ee7296c4ba515ed9287b4e88403d3ec42940c83f35043bae8dda009c40573c2ea84c36ded1f6b25c5f9c2115b788ee72df2619a21519dbfdddfcf8ff70220fdc8b26c82d7068f02fa8f68ad3d6b17dc0576022f2152bcfc1d35ffdc15908f5b0eee62344dfb2619f30f25fa86e96600b0872019091ef77a8020d36c123114497e282d1eca9d04ec8e206c399bdf67f4f14c186118f9b687edafdbc0b829bb45bcdbd25ff8c52101fefda20c9b083a0b2c98f4e5797d6bdaef613be0af1920387db2ef7324660675c20d56a688bfe720de116e3621d78b53e2c2f34b08cb5b1237652567ac561777bce79124c9181fd43649857fc7cada721e038c331c4b4193a2d4e4e616473aae927692ef365b0bfa5b0c78775bdbde28cafb9fe0b0f74acccd3287866b6b405c9f0e903295fcc07496b19121d0e04627e5ce827898bb217a685b353d14179bfd3ff0bc6bf3d0bfbe742bcc840aacd6c68be6806846c6e1d8b4f1fa618324fbf8601cfbb9ecc1309cac74b2ac9d4d61284d498c7cd67b8fa749f1c5c849424d264af553f7948bc00eb38a1ca6b7b4e5c1e2bef30851e60a0ed7945739a771fda8ef0f9cfcae65a372019e656468f78742db655a6773c090ce65ebcb1862bd3f0e9dd9906338835b31180463a45e2177fbb844361d50433bb655d43754423a29e40201ec47912e47290c0bb7a8e76a11de5b495f4e85dbb2b0fd6b86a3d01625f2b07d8779e65bcec552f4d60f4b0efa5eaf54145cc27865e2b0bc7380e5c847c3f70869ca54f46b6d8f886bb591df2f8132c43b352698022fd301d811ed9792a8338112ade252e2b5d4ae8b99ea5fdf1d0979f01c0c34e52e03fe71468f8feca6b53d6b94879114617017d2a78f1dbaa95392abc1d6f428fa62c44f847c956ec92acc564fac296e30439b05f2e513de1fbd3e838524f8dd5c31baa7afc69ef0afdd5146e6ae9a5d010f77f98ad73a135f73670d9073e32f19828b4389ced8f8be057a97e86ea933781addc7ab14d29a2110f3041d4d0c6d94bbcda2dfe17087989231f3faed22245fd2278da1c5c4be4e297592a0893b3c45fedcebc8929d97517f4b6f2db66fcc47bed703507e4ce9e585e38df08fe399151a7da75f27949e67d9393c01aff1d5d3763e15772bc4527def9b7cbd2f9871feb8c4f7bfa7e0493e91da263e62e4cdddeb25a64c11951b076f25164df31397fb26c9eaf3f4180e39e65512d0e984124319dc733b164a66bf16fb990bd64e0df907dcd0f04cdbadb1e0138c27da947981df0164ffe596b6a9faea6c9d28f2c89511b828af1e2fb210f1c1389e87f4474ecf7185563daab09ee24c4c81a39ee8cd06e63fdbb6c85cf464e745c2428dd3860a2523ce1d8d9ff9831106ef85196f96f532a1d2f30d20b1172fe1c6d1bf226e206e1051960c1222f5070179d357701d5debee267b78c298e8f5dc9863cc2075d62ce3ad71be9218f30ecf08e88767903783bcdf83e840f1f1d2ab4574003afb1fb7b621764727d909d68a3c04a76f695670e786be076da305e6471bdb97075ada90d176a5c0b938927c2e401c9b30265e6f359466c0d4544ee4d9fad0b7cd2b0856a5ef992712f9438a1d6dfdaa1ddc3a1cdbe09246ed3e24b46c4da9bb96cd24e6eb4a40c2aa7a794f558609d2b7bf3fdbda7a24c22c1540344aeb7afdd13f49218e17dbe26ee96a1e6f9dc40aa882449c64df05c0668afbe78931deb424805b6abf4ac0273dd854e48a1ec42836e3fef1b43fab410574ca807fede777acfab34548233a865926dc2d53300e3b8d33a8615c326ec6f810f7ab7a3d5e94431f4fca26fa14f554ebfa63339aa1456e9c41ebea9eba2c8e7cb39adea6c526808d00b0519031ded17c3c33bb149e18de5c433a1349f6fe9934bee690f6efc75750332ea6cc7cd0be7e3bc2ec529b53fc4d989ae7844dffe3ea1fb8519b5313707874651e1a759f8b2236ff6a5d84092e1d85fd0ce6e860ca1ff10f95b59e64fd1178653972fe08816aabd7063d9da84bd05d57857cb96f2003d54a6069a2000d09a15a95736a99d08f2bd0d6a01c89b3b89c0ae3a766b6cbd59da56410bb11f839c2bfa8d3da3ba2ed9e6d07c099b4e8e114f0ebec50a4f41944a26772dbfe97d0cf07cf0d131d90dc536527ab7f1c746e5f90bd1d7720e7bb548e8c882d92933ae95afd1224284c2cc5ed77cc7f9b4bde1638d1da888666be81a5e7c43683b2318f57bf8f1ceea5159b630aff5e25a852191d7a76e67591c0b5a2e4373025fd991b4896c41e791450b9590fff20d0adcd502e93f188e383a86034d508331a092eb06539131b6c82d9adb616ec96b59b0be65179d1fe309a506146aa4807fba7cbb41e7e23f385182e174392dd3a20b2ccb6483ea85f5c4ca67ee63c5ee217b3820bec4d2b35edbc7533eaa84532aa4e4bc892ea35f6fe6c3793adeb673b65a195821230ccf14722e31f049823f21b3bd956ef32a966bc8ce49a9aa32782b46cdbd942c81338c303923a340d84a8343dca24b7c5f4ecdcbb4aafe7560707bfd6f6c27d9b1a96f860ffdc26032165172ad446a59e6a90463445c4b8fa7099f1fc4e6294d64d6db632cd7e4680ca699d1de9d0c632155d9c335d36117783c12eefa4c953a7823a0750a96fec89eef9f35b22eb2bd9338fc96806135af96dabd9f7a42a6f450253f5a8a48ead9b23412e6d218e9664fe2eef8dc3d783dc634e117ea0cd3a51affeca403655a15e45957d6abd26cc481da99a3435ae3868aadb81a6bf4980f91d38d157499c32a326df35ed4e9d7f436394b79400183a054f1ecf104ff74931bdaa6ab8efa809d2bb787ecd2c40671e05ea590ba672e3b790a669e45cb4d75d688b26a0accdc871f6c3dcf4cdca0021854e38a49cd7054235235744933d6b386ac21a7bda2761a87fd84e2194e3016fbfdfebcc6480072e887462e2823b84c1f8e02826c3d242c637bc8095bf9a75d4cadff39ea73d726e67fc8e294ce936e21ba089fa10f442462ec6a676e57569035c8fb75ee2c706a04f3460f88fa1c10ddaa91902500bd396b528cbf502c982882ff6d2c7f4504464b9ad0c6fc559e0efd07049636f5f6769443999f9bbc93fca2975309efc46d3a689bfdedae9ed795d1950e2222ac3c4006542cd133046785b6ad4e09df734859b094cefe15edeabc85bb724b8f2c985f92fe1d9cffcf84b1ab9145598834b85b293ee4d119f83e15d842df845361fe44d9a1bd1a3570605e5c60f32cd476e2537c3ffb74e2c99e40cac788ad7dd4c64d3db3830078e68557ca10b069c40b6dd5c38174b93c81ede7336d52e2f4c565d47e6dc4d7a9bb8ce5522f4df8e5ae2716f62a929043e28d38880e9a535fe0e9dc6c42d0e4dd9d7ced6062c6c5f4b50ada70e4faa0c3f4cb600a2f40b97cfb9198edec9bbd891a5f6f05c0cec03aa3cf30d8e4d19ac635574e8d049eb020f67aaa4ce5fe3ac5dd6921f752abd65416ae4bc557b97faf4653edc3599d5022188f475f20fbfe7f67c70847e720d88ab94022514525c4900eb1b36ec31d09d4a22d7d41f096e355f9b3686858fea5c9ff2abab5cb56968152048f6f818edc4fe191cc13e3ded7466c5691e68e428d15d95c2b9cbe761967fcd3d116d8e49ad5734eae2bcfcc99076795f2120cf2fc369a8ee2907b11de06bc5d36fd2224175c73135acf3292d1bba9502a7073ea42dbacc8be0a66c4164949dc2a83f4835cbff11805cdc30ae279574e40a95d4238b0044817bfc8870f03b72637a9a6ed2fd23c745bedc5dfb94f0a3fc647ca845142ca693de0ae174e11bc83c60199c47a13cfe518ba4823a0c9c9e7ba51bec5d15607613bda1bfb374f25be39a90a0c69a1b09b3194360ad2671428617d58faec58a48b52d88aab45fc13ec0ae3712bdc7abcd84e1b89838ceef77db65f4b3ae55540f2777a6156a69ea758f300c74c3645f3e32a364981e36102efd8bdbd478a824b0a379aa6fe59546cee8166b37e31601e4948b8001a222a02019721a05ad87641384bfa8194015d7343452437f747ace0638b518ec13928f413db7fe6b4720f69fbe7703dab68914034663783a99fabbbde21d68e86cd5403a325e973507405ddb1a1da3c10b9e25a1e335cf9d4dfff55b646daaa46e95c62a362c3fbac64865293c648844c5408cf8aa23776f5db37f6871139a6bc00ef493fe217e91ba3f1dfe30b4840a74fa9bb4c3c4f2bd08f5af9c119578b532c7db4646c3eb8d7cc4cfe0c258f023088f00138152cdc1dc79a5b103cf2315349e7136e8b819ec4e34d5a174b4518d8d1369893869fab2d9a75663686d7f1948634d54daf5719457ffadedd03fbd513e4dd148640aceb764c91d35788246786d55bf67c1e08fce98cd1369c64bd494358892240d987b8344f139df63d7f3abcfc7ce15f509c0b1496eacff767c9ad6eb7fe8a85ff1ee40d46b786e7f56a71660f219fa2d16a5fe8486180321af2084a69ff10773b53bd056db02136409707e27dc5660679eacc222c461ed78a1dcac2e9ab2aa1a131ba594427731e058b1e52a9c9782e32aaa0a76db1579cdf38b3bf368dc2d0583cccba6bbdc4ee20fda3d400768d0083a92b2795c2e1b76ab8313bc8c75449825be153090c4219235654680fd8b6dde09b752b8663ac8e303ece8160fa3361c50d99c9fed6791b12b8c39fb5a08aefe26f9db72df58238a7525f0cacce853d36f225c075853a70c2ba139c799c1ee6d60182fcb90b7fb0795e25c862e9c73ca23f51a72187d5168ed230b24621b56c8b3f8b54751cdb332d1a68bf125d465e9e4ece56ee24913616581ea5849b007cf59efab1324c160bd0d9e3d8ca5d257fb4d4db1b23288de71b20764c544a3f61b68ace91ab63eb13551263f14f2a52857621071a43317223abb2980e22e01df24fb4838478c51130bf9438d4864410394942bc5b36cdb398f805d8695b1c9521ffeffe1e9fc4c162e0309795de7dca9d4c07c3d89a0512c2de8c30a0be31b4b6b5629dbc25da63383ffdd26f9018fa67e387c92f3389c6e4da665afc53da1dc2dddd18d36f772cfa37dbfb5bacae5448860bec0d1efd8c13024622d6e8418c2cddd6a917673a74919fa26bb45a1419f8d81fa36584757aab74909fe0428495b6358b13da8d5943711c381d446443be2428ebfff555404ea67135364ad4b6271ae6f6fe68924ce04127599e4514f0efb6e26ee4eec45eabe22d8c66274e09f5c07466270c2a94f777e2f771f965b73cfa55810313e17b6cf963fa79e6aca6411e66d6b8c463c8e48189fc435249b52b31ed285838df07954ea9b19d16cf17321379482ac1671b23561e922eaac42b2793de42a6604c1927def0dd8961cae4f7892ca3b3e258db8897f0aef017cc83a02d4a42cf7311d12c44abf43b6f9a63982204857f84d9a7086f64a01663a11a4ca9f26af5ebc22ff0e9ff1c7af2e0b433930c71724cea1cc63ad0a186a5e5ef29bd8fe37d6893cd1d32a53587a0f1e42c2274f4b2b3010bfde69d7704e9fd86a524c52123734b606a82c1d8e9ff44d3163f82d3aadfd749f814f55d77f80994d775544769595be4f625ec9e9985954497b02e9b4e30bfaaba5e488901e799279e4b40faec5cff987242fb829734746511a7669427bfef4186e2ab94620253bd0b974888bddc3ded3ee81bb3c45ee49cece2feb669dbe35e2e4c518b48a5d2c06ab52bc288a7bf5331aa17e0d93186ae652ca51aff98e0fc56850aff086197df0e92dd24e13c6a7721b4cddb750070321963be8e8d359d7080e7f30832f25fc3e8924511c25d2ab1f1dc2ecda173025a5462ce44f0cc80a923ede97e7a728b3adef1c2c41a2d6bbe409083825e116e9b62bc942d16ae3fcd1c1f5dded15b5ac25612e696f096ce9d6cdb1f992615e35e9180532eebca41dc82a99a77698c5fc8c7722a67ab0515063afd74cfce73e767be9d4fa921e19d97c53cde0b9d4ad40244a99678c3ac7c2e41b16bf9c37e7f1fbdb3d9699e993949dd88f890589ade27dad4ee7a5f2611df94f2284b0481d2fe86c477e8e7f0dd0a0467e21c9ff9ba618b534ae8f9292dd3a7de0376f34e8655d23290106088b4074e765928eb5a60dbf653910136c72c3ac2187b635ee66c7abe19455fec754936ed78918a3b3e9dd6bd20e3800e31de9230283164c9457ce2e015e1224ce432e03f2f2e1fdc181b395ead8e561cc0f3f16f8ec05a0108ca7a3602e8de7767bd820a4cb11e0bba44c40c6c8f540cd7ac8231b3eb316cc29674b4dbd07bba9f90a2aba98606711379df00210b530522f12cdfd6e5c091b4ac04105b23e3e09095b6b8a15a36f242a3b8837b837defec14131da1091f92196ca1b41f7ee72c56433fe6bf5d439fefc2dfbd537d2a310519e8d46e165f3bbb31372595a1bdea104d84a7793aa36eda02800336594cb42d9b8e5a4dd60e64293ba7df4041dd3b29c16af575fcb9f59db25c9e74bc8c15a46f86ddf89bf253d2cd029dd858dbe74166c95015c6acce17e9e9723b3faccaa7c988c777a2c7a1f5ed0f31d42afa576788f0320ca91d8f8df22eceed06992a05cf8db144cc025f0bd292cb8e37eeedae1854ae6abbe73975c1113a2e58addb42de49c4abb35e5ddad2c5a1193569c9716708144a3951f14eb897d035dcad8e035b364d242550c2982139b840f49081048b25a24efb7ae4432583cb66d3ad13363db8823b2ad0439499632f6891463e41b056e01e79a86513e5492a30c7126088fa5f3c25339b31f674230b9702474ef0b2dcedab5e7029c8d0d45dc1ee52a714f2e1841dda855ec1b19aefe6ae66d1c88dbc3850b1d20cf9ee6d22f01def5bbfc4f7fd7c25d2df836addf6837563bf01a9fe0a7b4a457b9c0d408a944353727fb823d36f2daa53f80f8d50139333f5172b5fc9465a4a94a3c02ab574c4b62878adf440f1a603eb1f86aced30ca74b50e60b5f021c2cfa977b8e35fc7c0c35424017c39f62c780309f741d942d61ac208be075ba90e877b8baa0c50ad577ead391078bebccd7be543c42a9ab6dc2622066a4a9aa662e48823a03ca5d7c2ccee04a0bc879756d512a3ea54dbd7815b9b5c88e799068a71de5c50bbf8e51fc9ac372f318eb7dda29cd5ba5fae6c2e201b7b7d64b3143b0b0725c80e93db53ff5019795060f36a2cd688585705bf385eaa3b5a03d9b2d8857993d982c348f4863436638f174ef5b650861c988f4bbfb618c798659cf3c16778dcee85dcc9e101220db3976d471c23b55f6809da7205d2f35ed14e6e6a6946f7316fd9d30cf0d9ddc02634e0e44e10c8a1cf0c28776e67ebecc3061a7b0afb0c487dc9db6a76d2d2c947cd57e67e3a073f329ca8b415537b44abb7ace203b7e5548af92c294acd782757d896c6edfccbf60d19dc17510034917b257c3992051c61fcb6362e3163bbd3ea14baa5aeba499814356dac2bfe81dad0fc45aa2021453f05acc3a0f740ecbba7ab6ea5003f9f2604022ca82c2b147e3ddaf275eafd984b174850027c0de7494113b5903050b28776c81a41a6c4042a347f90893f44f091a3205d703b50b49dd5a60d3ddab9d2d86a6ce009cc31d133743daa640e445784fb3c6285ed3d22d365f54f5e83184c48f7b8db2e17d5dd876c49461d77a4f8cbb0d6191ecea7730b91a5fa84df6f958a089e830c1bf059276ef49687f733db42110e0ef84abd973fb8f5d3984c5f95495971d3e0838f27f11a971cabc6e1e27b01866be9725ff0f5149e443c5895045796db993e676d3d05ef9b91c89e2a2b585e6c529e436015e0526634c807616daeb6f37a61f8f36cf8a5b2b289041916e60b41a71e4af70e3ed9c21ab62052ddbbeafb007e281fd737e242e32005adc48582f92547c969ae96b8d31f6733d3241301221937f7986ae446d34892ba0b1effd3e2aadec59474e928fa01fd60fa4b8c2e99009a79f7122f3237df2c535d828db9398997b7b91432c0bfa336134cfd0fe1fbe6d576afddad7afa09d50f9be651289987984b5de32ddd9fcf72d7069521c4099ba67a856bbc33362974f9051d561de5b5e75cbe982c33fa10149c998a56d90def20c68aaea926f8d2d6e9736581a1fc43a3f9da67fa2832919132848b34c36c0a5c75a2431c9a4fd9dc433648d910001f7f04acf79b95a7359b8cb9eed3250d1558807a7ebe12fc4d8347bdf232cd2c1eb9da4ce720aacb656fd1615579bebbf135b719f992f03d88621727313842e79b2bad750bbcc0894552e92f9b0dc0c135f31bc6c6e5c0795d2fc1e036e888538f701adcc5c7acf2e2c727a8ac8e1bd7c853b50470c9d1a85513ef080b62e874be791699b131753f91b7433ff4f43b6d5386881613473d066afb1dcfb5cbec89ddd22cd0045db2a900ffad8f0ad8d115ae2f53fc065e957beb945d947bc42e92f8a9dc98c40fe184771bb23048a3b01df40dd4e5c84fb435d79401b5a076373ef6eb6313c9dcfe52417c1785e61618c275ebe1b4db4083ae34b4a4b5a4b524fb2cd1ac64ce4d01e823201fe4659bf8f6435143acdb946125f7378e05e2b9813e8a85d20f1679dba1aee70ffc7cdbbf2b6d63efff2a43a19c0584a0533ce104e30ca1834d5e4fc6972ecbf9143e3f9300b25c815a705f3489c796e8971ed203aad6e1bda59e44ee74dd510089915dec5d625417b6bcb27a18896e0118a485fb9796d22367c48b05a6ae978e79bfedb76111d8b81900806a9540787611d78822b9b368d843ffabc379101e04fcecbd3af5f6fa09b6a723396758fa5c63e296914dc088b6fbc584326c59713ef49591a64d0a360ad934ee9155eeb1b9b897a1bf534639310f2772fa906278ec86935ceb22fcbaa368caca68d7142b902ea70a5cd6aaf1691539706c5a458901a9bb545ca47e86e465d607b0cdaad3fc1098696e55fc97b8fde598f3ed50c27e1371f590b6f8b3550ed641acd18ad5227d2c3f58b0eb0425cf2fe0def94a5383a5ae33237d788a9f65dc18440a205c51beeb8dd4e6404a373bc8dac1916d22507b52d2e97177fbad582c6cb5d37a8139e2cc4697f99cc3e97b2ec82d0e6c02be6784caa2747bb8d121a2a2fcb309800c4eb3525fd7cf612b1fa84de59d810b213451ef7d4dc6504e88569659136d1b07bd9eea359c33acad38eccf1c4f05c65bb366a314f0b2d5ca9f603c1a1f7a0be6df0b53974486febccfdcca30087bb1c2fda18db6cbce40e0e19fd9644c69479f7cd70d5fadceb0932573f4d0491dfee1e9a000792d6e72ac9dc06ef3769379378ff39dffa7fed2f231be6a8b9a716972bbb41df808a1a78980b93199f5154a1bfec4d272e527be03cb6ef6561524c6a0206b1635294282f06faa439bf52a45f8dbff6ca6a1a19a852e38b416edd65cd1a626239eb482be7010df17dd3fb9a4f7443cef01381451c20d69988ee57c3fe8764997994c025d6d480688855370c4333cf77c3cbc4e8f022aefc0d49b236514722a97d78ea47721e9e4973ca8e0aec69cd5ccd1c204ccdcb5b61e91e39b777b76fffdc1cc2de4d46833d831b4b978af8ad4ed621f0436a4f2fa29f97479c7c142673f43b15a36b201e3bcb4c45c078fcbbd5efbfcd7614d031e955af8f90aaed5ecf2672fce8d9f4042ba4d2b186f5d4ccc8cb8127539f46b750624a14fc5238d1d80ae5bdb8bb26e2e325ca98b47208c320e5d3bef8265ed0fc245c9d94fa36f6613ecab3011cc05eeeb8a0698a58e92865c69ecb29d22dce693941019c7ab296869322c793a152b6a761043a7121a05c001b1851331514793de91de4686b52556d921ee0cd8e57b07b38f2d38f5a29a4ce2cc2a3bc35d2e7dc4d3df70d59af289fa6ae36204b77a1d95d37b009822ffed46f306d24f9e514274b2c78f41706ba847d99a19c3cabe50c1dde76662ead1875caa306e56bac7f3dd4832c16bc40018c1abfd5bf037f4063396b89eabf20dc97365af753f7b4279975bb607e4a7c2650c038a9b2a5ee3a21b514bf273ffb216d26739d880f3520c8c893e8668dcfad3535c8e1c3be63676c109174dcbc3b023e95c9d3fb792a8df006b2e43f601f23c117a124afe629c024053064a583bdfc14f64a2a3b2454c586c27f142f526d2c6c2683c0b62d7dbbf8ae66107553ac8558ff1780203a6c35231be5664b5408469189dfa3501648b1563fa3cffdb587371905f83eb817b91a0456f9c5088932d9ffcf097b88ceefe56d79a401c8e1eef67ed2adea4607a40cacd7c0e62438d629a837a5226737ee88a16493e332c04e3ab3ca9f257e2a3b0e0545e2e613045842c7c7613a883795d216d161aace3952f7ace9ff7ba97152ac097b749e63b146881b1ea63682de5012c51fe8ef5b6df976f75e5f9928e7aea5ef5e5e2f072a579685bd6a2c13281d087e645ee75191994195c7b5b347d83d05b5ee17771c3180b62c7ddd73d8ccc39098895832eeb841538f0dda3b2d7f1bf3cc4a3b25f16431badd871e67d10a9496fbe51d0555776fb465ded81e6e2a82f53499da53cfba209402981d5ace18f4dc520f3937e0bd608672df5759140608646d2342477cba37a21b8d18a403e1f830f73a01468fc02b551e6e723e9ddb90f9f1e5ce97293fe3cc5a0a5ca0bcd0000504d810fc9bbe0088d7494a83ec31b642b04f730f7f75a9bcaf59fc70b11358b1812179265cab4ac331a2e76e38c88364f7d5f455002ddd3ec25cb7ba6733b49abe39fbcdecb4d4f8cf2ee30965beba8d317bd411f90feb6885d5910eeb8b2b382ab8271c994c49c1259d2efb11cc81475f77b84db6d2de3c6aad6ca3aa9496ff4aa0c89934269c6d49a45a35d91eadf7a95663dfe2706264bbfdc18189dbab99dedeb8a9cd0d570104b4b44579ad8412a24bb0f54537aea8ba9c2f5fdcf0c2c69a35be64398dfbaa1eaa76793f402de7e8d608df58f576feb69228fa78dbe18f7b8350b81cd339b06d0497fb09538477345fa0ac68bd0d93fe07478304db398ad2b93f6aebdb0d22aac4fa45aae09c45ec0b3c649895237ad2d3b7aa49b277aeb1590449f65cdf151c976ce07b3e26c36adbab3579dc1f86905d0901cbaaddc2f9a95e422bc59dd21820013bd2113ea817f3a7f126e87c4c7dfb1f94d35bd042534ec55768e253be7d9c6b8a2d8c25c1536e8df0b3b9a1cfae0c7e8b41e460532722ad4c7a62393c3b1d30b7a48a133aed1f4b4b311b4a79aaafa649f161115ed36c017c32ddcc84f1d8c5c55b766744f0a97adb3c7bac6627d5fc25c2602be239b09b5674cf5b42428a686b0ccd7d393723f66f8a8ae36eed28fee4ae0360f49bcd58932f3ee2a8feceeef7611c1cacc96e7a26f871e128bd6740a9a430f196f8e54aaa32388a3100cb1164f64e47f18f98381da2eab50c53d5a88b97a3b81d7902b39872fc23cc5d7b40e198bf3f9d517566e889cf06d8b45ea76afdc848736d1d13e640ea563c00df691e92dd3df479e01c96f34f46267d4613a2bf181429abb011fe05a41d6b6836e91d75b50d4f67076914889baa259dff3ae55557c19fed75559878f3134a4c3abdbb792b1ea72f7bf367685dc36c26bae79b6c90b7b8f811798d4257956cc608eb62ef6f2adadddcf8de064907de724300d6327e8e493f237efc37ebdb89d630f86f1efe77a030206461e0e804583e87fc86f4581bdb5d56b777cb3ea85ff8186b3c273df3b4e2b5eac55b74a31b6908f9f55a5a7d007944c0791d66d0c61edba1e310067d5caf4586794feffebcdfcdc6ec622d8f9902bf2ce0c8f1dc2650fa13b19329f84384cfe9443a161850e2580316b0c25d44a722f9155bf96a71575cc725074f415a7394e0bacdc6ed267f33142d2e16aa21f826309ca62bd0c9b8ff1b1bfaafb049246e605f0d54cdc35a070874d82b8c1a6c6b3ff6e03f43dd1d22343495e6502b8fb5419d6cf35f97437d861bc389a7d098950f40a09496e84e53648c474365b7c14aaa28430bbca1be9a72d8f8a378dc47b94d8712599805eb8b2488b5e3bebfbd22e43727064c32afe21fa2fdebcb44c731770ecc02688d3be539d494388bf93ed6094e4394b66cfa29aac1ea5f57261ee3ba46dc4caa8cb7c55e8c36efecd38f55c7806f2efa22425add36f66872fa231d01eafba0d68d5e073956219e721ee30220d8a6c4a56ad6b45beca822ffc39617924259a1e2b23f63adfcc245c8f19654f2ec6e109727de92f1b6a53e5cc34cf0d87f2ad8900b781da65d92a03f0ec570852f1bce72147dddccfcf7786c11c26efba5b2f7ffe1ffdd1495d595e1bf8516fac6a33b36f745f07ed5cecbf9dceac8b72868c4c9cc541d255e74238b2cd90a7fbcbb544c532da6b76f10dcd9a5b12eed1026fde2a3204f7d87d16d6b5e5417379c0ff2407f52afcb6edddf4751c542a72ca332be796cbaebf3370258fadf01ac85a2587d9e952d997482eaf4acbb227bb963f0ef8bb048da830345a749fa41e33fff764767697409384b5f802e2bce5b20d5f0830ddb4e27b87ea62e1b5549b32aff58ece15b74730bc38bf579b4c53f33551804edc9d25f87fa2b67f304d4029238786c2d0be4f0f489466b9460a9470afb5481bf9baf14a0798d98adbd4a0b583dbc44a226557cfcdc80593b4e8ebe51cef3c474a8a3e81b42e56c12db5ff292cb2976642c48c52dbf39a050fa6ee9789107ade402d9762d1fea7658021accf1b27f70eb09b14e8a740aaeecc7e21583541c6f8f0b21e535e52a9260cdac3f4e41e5ddb18f73ddc0f2badf87907f9dd96431df3cb5717d20462bdd8ebe861531c102b691f9a36cb741f5c48023b06ce4cf482abae16dd98c6cdf26063a35bb26106a824ea8e621505ef56116554aa0d09ee847151398b2c81103a4697a3329e16459b3d2dd2fb8f0ade4aa3dbdfaa2db658b30f87ec24c97d7e102e9b658a114ef9f77baa7896f085084b8dbee65eb426aa466c553721245347009a4490aba2b2a89666153a5fba25e19e393b5ab7997deef4c9ddcb2990f968e00f0d70c93dfb2012bd8a56fb54d6bce207eff61ef3d10705f7c74d98df5e92b96af43814cd02bbed981b36240c7bdc8fedd5be6a8c3718e0d376c916d34c209babc937330faaf690cbb45cc45162a31499c503c77dca16550c6f6cbb3b30f9ae442e398462382d602338bea099a12c18f8c0c43328b207577f6aaf967cc620f7e164c9d30a8ea8cb298da76cab8ea52407d9e7096d55a7dc96cbeaf92f8512a413238bcc45af1cc2596b2d643bac4a47d573d38c1238efdc8a47869e3d99cefa68447a330ef128c44d137871e704dbe9cff5daeddfb551d1f8508b983d2487b0d332b26840e92e49022c3e3542a530b3bab78f26f4332a34ca87dd834a65199671fd60ac4b542731fdd5d6111cd469f223e5aee14cbf850d7e78d147aaa6635c1f9ce331fe365d4e3cb212061738f9d63208414fa4a90bb5054c8eedcbaf5a215637644cc4a972f96f1e4095c0f265ffd72f40c17db0ca24945941960665e9a4978d0bf7798079e667916fb556346ae67b1c17836af355bccc406ce3abaa283c31470bb7cbf87ee0765dc4b71bf18eb52b439f9ef4258e1d9fc8da37eb648ba7c987e202c61e5f4404375dfdbebe89a7c2e64a8b112b2dcad8256ecf6d6ed23311340c2044a3dba1ae599f836fa05db306dabf4573fa1dca727a8d6cdab1fa31d3cf7c8e8e166edb4d7d1454e2bdea4051c487fae49654df876e8984b3b1eebab1fb9794a6c5be9c43bd260d3501bbc1815a468218b92611b46e3ead324a23f672d5c299d733bc899bb156b043a0d13b322d353f8dfbade9ec4375f871144c70281e45f1b7ffafcb5f3e36c29456d94986ea563ceffa13b02e6d0f4706177aa6f825074958f04dce3349fbf4648ac5fe24aabad8e2883ba2db362dde42749e788be28eb818df9d4b9027ef7887af8bac27c7df682659f9a5f1f66bcc8befebdc1990f25d0a795a53b35aa849671d5034bf2a9f6e79e102e1fb06f6a8dd42d08e7a6684fcde73b8c59e65f7ff71b92a6768a5b89366c6dc15e1634ef2526bbb4d812757b9c58f0ce48684da8e491952c08797230b9a19f0df2ce386255842a4e5961a249a57409e56e16d50580e8bd9b3cec7c481ae1554a8307d4403cd36abaf33ee4955dd11d1105ec65e9ae948a838f26b5ad1b3bac12ac57e6c61aa120de1eec79b3e896d204dade8f2423fd2ff72fc8e3cfaa407f8a71c4acf1d4ffc59f23bc46b95058fe8f6d4dd79629c8deb6e8a16dc1f999bb77bda342b103389910c431c8147910545d15c0d4d87f50c7d7acae2513a0da5ea7dee1c47bc32a8530b9faa4ded288be8cf2a3149b755889fc847dce249b01864966b7f85d3d2a196e114a79e438c3fb609a3a68d6057f5f06ee8fc865ad24a1d47a0c249b97de738ae423a65e1b875f8e34fe9df2575711ec9c94327969507db7bde37c327e2d60742efab40ab4ff2207c293698ddc28fee65583e0883813b2b597d2e7f6df5f61544303a4fabc68c009e65996e4423db280b28668bdaf849a13d33e6bac5a97c475099e7df7c695925988ec1e6e86f76771521229210829c197d93b2c5fbcf17d4b269d7688bb1eea871a5b94786699c2031e428d2af9b0438a69e010736ec1aa4d231da05139946db4d1149952cbad814c2df1aecdfcb8b57166215670ffb13ee3eb95b5c5e07255c20b1447d69e39aabf1a4b335d8459827af176e6868fe8f7e8f3a532576a37a7d38647170e4f8ca7f9a590b1ab9faed2808f295c2940b03d3bfbf898f9c78f34123c4259353252a4ee71b594d6175875b77b54d63ddfc554627a372b0537dbb698643f575b77d95cd60da156847c1205e31ad3aa8b13d70f2f14fdf7d7327f58f23998f9586ec3867a8740897e5fc393bb948278b58c84487585d0f79112b0041b6addbcefbba33e0707549e800139ab55dad6ff6177c85e6d662c65f1c56492cffa4f7bd167902b3bbb55b4980c8afccac3f135f95b234f3894ad7bd1d7a37ed3f72ccfc997d60f49823b253c35bb92a23e06d87eaa3f37a85d328cc38cdb8f74a41ba6e58159bd8802d44273345117e6f12c5e19c9c3a1165c91fa5320d85606f476c745b9a8ce9f6fab6e95f2fe006a88925f41a5a59e7b1383b55d768b1a42b577b28cd949f02eb8db9e65533e1554720ae431722f636e3b225b04fbf1f7e06c28e872e80b05707238ebfa88482b07328c550db26d524a2db9fbdbe7ca19aaf092d34be004848751f08a4b767f2991074ec86a47591dad891ef12a0b7202aa2b6cf3c621b86a18910ce2d2c6f4b4e36d8590a414abf3cde412a089e166ecfe9f0aa437a64738f1c836bf498aad4859373e2a11a595f72626a390ca6bc9a3b36536b8ccb3c045c2b58f7c7a324b2a45985fade68a6fcc34b3549fe69b7a5e65f7d8f6a586f9f804f4ca8271e503b3788a1315215c48492e679dd0cff3e54bbba3a66d47e5b367a34715af00dd2276687aebbe70f28e6887e23de1157bd51232d0fbb4db200fc8e3b02f7ad260ca2c0bc27e24dd3c5d5a7332b7241989478f9b6b353ca71b74fd56d2056bfa582bc6345a61d7732cf50c281cc7ee790f8fcb0bad67dcba10f4a397d2321c03bbf72c6b0b59741335c7bfb875a94033e847d5e6aeaed535cb6d494479009725396a73d924fc3e8a633096a4c1aca0e3abcc8c1015221117d1961b752360c6503736446aceb08a6b754c398fe377b622c2848d7243668d086bfaf635a6a465d67cefafccbb9e65a68b1658c4e748571f9aefbed7f3286b9c7b252588e6e1009ccf2b9ca3a0a2f55adaea19a0dd9f194cf682e4837b1d3d09e834317f0f557a34ae487330004adcb42edab5974e19dd46b33ccc33b33c632c90a291154c696bd4f2afe54d25f68f9da271c220fdfc9af75fe7a8f97c56e899dadbb03d22e6c82834a98d614eb80967f2d090872e9cd4ade31472e81ff63805783406217cb0099eeb77b7b4e4d6cecb4fd61a701f2694dc1df01853411510f635b4d04b3cdb94b726fbb721a3c6613f3f84879bd47399a42d7f1209b84694ad62e9961ac84681776672e7800ee86ee13786b606677364ded7bb9134511b547c8b5a531ed3cd74b908348c1a725e9e5bc42e1c48015f3cb77c8107767be3728773db8db602f3f8b2b71fbabe7a6499d01033a6ebdcf5c29a796a28a7b932f44f0c78c7203a1024591e965bd3d4d481f7f049aab9f53a36512a7fa2a41620a8692792bfaf7429b5f779f84088c7f1f3f197d799945855b38d0159de20d53e862fec59d603cae316ce8cd8180c5758bc4eced95d0a33c75d10046b2a3fea7a6d8d73c9cf65e1eeab452311b8fff5112d0fb043d39e9f56b0913ce87308a6a4c09879dc05f545dd1e9e39b2e578386280fa782f5164c909dbc24b7930c6bdbc59baba7ae0b8f9178cdccc0ab5e690a2fa05f864998d4793791f6a99f7694996c7d74861d8dd25aa0322c89862e8abc8aa27733f9ec9410ddf3979ae7b1a13ed4bb50d3cea0461d7edeaf750679e96efcbfad7e13f11f8ac44c0a49fe8349b217a0ec85a19597b05da491190ac073483a976818528c05bf7750a59243540bcb71cc7998df6b3ba84bed505e969ed0bb037ee934c88f61be0ee18da312a0a85157c6a07b145e7cc15ec68a1d46c2019d5c330816b35ce3d48a9be355a0f2923c8385563b1407902decbebb8ab7d3f63ccc4167260eb0651003a5ce8c16450bfc48bea9988568105a727cba166a12572cce39b0b2e56d63a4683fd258b7f2b0baa42049165dead09af862df05fc7aae2b360a8a7d9fb562a1aab5ef81ac818902e1d74b115299d40eedf4af432f3060f64d486decd91233ae392a681dafd21a6e0afad3edfe252b31764aa258cba2a8d8ebcb2f6e4bc2d3b0576acc32078de418e97fdf4ee58fbbe5de1b1faf1aa74cda1af690afc9ecefec404e42de9a19b3bbcb8958204a97de37fe7cb3732915762c9adbdd3debc972ffbe37b2627d002a8b7c94c6833261787acefde43dc778ddea2c9e9e1a2d8286ccd685fb2b07bca820ec723fc559f3b81166490598ba794f7c158c9da78aef1acb54a066d3c473a8e3cf30da4373593f4f1f2021235acda7e55e268e4768df1d7ddaf2bf320b659f406e8fdab9c095f66015f4229dcac8d37ca0e6c1905193f99b86cb324cfd8a438b6f92ab817794845bfa635bacd6d31e3591faa1cebf02b5b92b13752fd45d6f919235b81c67f220f0b4c5ab78ba95c3fbef921ac1dd42ecfa03d6219292797d14a7d2806ea078989c4e12265c55cd171961d9fe39ca171b974a6f9c76f9b16813bb7cf4b0a738b7291249c64a3f2eafd970dbf32100c0f8782546169a33bd4d4e1a06ee2533025aabeefdb1d74e3681c5e3accd0398d358a2ed4c6e95eba7ac9c27a84a034426877c4e7d404e8f2f139e82b2b4d30bbdc34780ff82c5a69170bacf8f4f971878c50388b255ae4f24bcefec007b463d99508d1c67228dc3d3f3e7914e6514faeb3073765fd924c420e470856ed90e45931d4f0708943bd757ba9123a8e571fe8b36686cb280e4ae87866eee7f1fe30a6247b9d6d3a9c9bcb958d7f0f09fbcbb21844a615749d7853fc6ad9dc6e1d8e62e05bd0738d437e9f025537b1e8669a2cc178933c260f44941c7853fc374c2c4e38f6e9a1fbec7b193bbdcb5a0de0fe43c3bdfb48349ac54fb19237139f544ebdb3c2337a397f674bfd0d3048b3c7e960b2328d1ace2fea44c0f9b4c320a45e75fac954ea498cca559d55cf4f811be39f57b1b0d4fd2d547d0801999e4890ba706594ec8b852563a32b0c76b7e9f9a916581eafd8e733925cb58b615723e9046f9b7bf4c560a1fe463758c6ed9f5d0d184d93a4b7fd2cbd820c9c6ac75ba5a6428c9d902e2d7ba2ee67d05ffd44af76194133e51b097d839571e7709a7e8ee36ad43f2e3f1a00d1aa52c4a9dfb63077016ff55f4ee50024172d209260822da30c430097de274d87ce2dee03cb7e35ecc7c183241ddcc50a34323a0a3fc8d357a76d34a9966194d4e9040ab9f629761d9b9257ebef681f92106c770538719d5e6e408775d40bce8d946a111e41693ce06eca7db4b7327042b0afe5361a8fb31eec14991be6106e59de0aa4e8e36f155bcd2e1f9b4ff75278bbb8e2d9efad93afa4cd366ef64f1d43f7aa1503ad79091bda4a58356d762cc3980ff3b4df6fadfffc66994cca6167151e87aa5a77a1f8da61217ffdb376fc226fbf232f82e5139416aab5baa4299417477dafc04107f934961bbcb11c867b7e51b2f0685da94cdb95c5c4596fc00e5b51750a3d87d7f46f0329e5a61db4d1dd5b09f7637383def7b3ea6fd82be35ff81a71d431d4745a2003ebf7a9935c18a04f27cf7283b44f4b29c4a290aa3490df978cd16549ccbe87b8f1dd6fedbf7959ab62730aa34fa17fda320aefde6d5fe576c71f1b473cba1dc6797401409755d388335f1fbc4df4756b372f5dd6e56f5b7bfd84c6a48b9988b6ba5ed9f84ef82fa52cc88696d4499f6b51328639ad94c95e27e7dae50bc6099109e251d4ca367211e3eb1faac722042e116f690f89b9a112d7b35e2f31c183e6b402cf10bbed0e3ca28ab9bbcc12e1dad3840a77d2643522ad5c863c2329f20345d62f1131caba0583f1d93d715bfe4cda5352b95418ab0c2de839cc71bc00b3e5fb35456c2a2e4a0195cf73d6eb7d366a7cedb0509d862e515cf7f3fbac952438b8cbecaa040187184675663536e217ecb49ecb94c6e0bb597da27065c05eae65aaf8508f9102af7981cd07c6f01245aa9a9a1398c2207c65a780e8a75f7ab2efa02dcbe2c1ee14d18b76af4f3ceeec489f8c5491aa058697215c6633da15a155998f2be410b7426ae9e4eb5455815ae0a54dfa6b198acf7a56c89404061c8b6a1ae190fc4d77a3e6700ed1adccf56a419006ea4f58085783994b85ec7f739eb6a927b0bf347742f986007d57fe7f9acc344d7b862c34a291ae8a6c9198aa713b1ac5d75f37324dca41196af8b2e671978f6693b31729be4a9f8cdd932a84bf13a6b167a05dedcc01b3bf90b637121a7fd4d6fa6b783acb912a480df190e94fd8c9c8ce77b6b1a8313991aad4336ba555e7817877bbd6c515bfdcdb84143a39b67177e2f9dcfa2d37f5d1d2dadca35b5f38c9b232fdf7a18423107483b945b1879f10fe191150a9a9dee1da4db5daa848ccf5ab454a0bb58c43998f7667b855e5c4115393fd85b0a93f34b460775a4b6e831ef3a4a70c8b440b564ca3b5e1f0b5e5f9f294caefea01f2a0b353f0d7ca968891fccfad4a2de026f99c42e4c2e146312b152c88015146bff5b3453a6619028c9e13e47884755ea749d4687b04d3a38b07dd90201533d1a9af623674c4fcadfb2b371dcd5e49a7b98b8efd324dfc815f2d1009b3f2571074aaa3311805d055d4d2877696d63f9ad253fe22818d9b0dbcecc8e4c74e0413ff95b70cad9f0249776193a18b72c8802c5f50c6a256ed3597944869ccd9237d48057021b96acebffcd53efddfe4997321b0a332f4d89c1b9238e97ce74db06e707ffbf51e9c7642e89f42a664c6b052506454deaad99e07e4f46981e19a1bb83670a7806f534396d27266dff4463dcc040dad8efd47259b54cae8a9bd121316062d471be17a8e91a89fca5aeb5dcb635194b2948a838adb5ef402e72f552eef296d11944a981f67ebc1d0a5150721e7817f773ca2cd00705fa4cd3be385b98185a6e97d42efe8264df38b1f5353a0c1ff24472aefd5b21d2c7f64a8eb6f6f7acb8b4db94f5ab13ea0ea6c2e132d2fdc3c45a7eb527c02a1afc4fc66bb56646fb095272beebc7b8b683bf886c4e1795ffca626de3da7091b307672f6d76d27ed0d082edd251219b1280c017e4875e6ee234e8d1e916170661e1d09e30eb86d2f19f8454fb0895fb51891859b96ebdbf042ac4634c6ebc3910a303e4fbcea29df8545297279ac0e5a226e60de996a655b0a6bba8d3c1bf0c3ed6d59803571b0e776ca853ab95955d14529adc6f25dae5940e69f58628c62a696913c1c5684653fb1c0183d0cb8962d5606411351dc40b6bc1e3b82a6f758cbc9bfd02fbe589e46fd40489673a9cf4b429eb2e9b3df672be974660606b7f38a8d3564759d65e4b7fb059fe15bddcaaefaad9f7436cb9934729374764542974c16e02cf93980c0617f05676d8df4f694720ea4d0c5bb14fd9aeaff6f6eb2bcacc67ee7c28ea9adb6cf8fd2810e37cfd9ddc3912cc2794a51af4d25fb07f1bf3b7334f96095e0b3231e8b4e84686666541ce04c3e310e95a7c522d1b6b6c9bb6b19757373a39d9092b9ee76c6c26666eec9dd73d50d48db94a90f67f9d6e922e79ac29dea73b3c4a5fed9aba0f6dbedaef83ff217887d73e9de13af111959e1b8f35f6ab5fd2fc64f09ea8e3344ef02172d00519d9f6381bd2e1c59b36ee74eaa1f318e449f436292f32ec2206bcd255cf5ea7ea97915736053d6a72a4141e24633ce3bddc5fbb00db4cfc6db7b26734e66a97ab9a99ea6a1504684bc76a83ef26c8fd0da3b4b8642d6abda6fa572710e4202594b8aad21ecaca064ad0a76caf207d39e29733ba872bae3f3e64d5152279a0f038d52d267849e4d5c3ae2e3c4b4fa918cbc31bc9e1c4d28bcb92668e876a9935c0646a5666aaaf881e23206d202a9cc9642c968ac4d22a1da709e0c3cff61fb3dc7bb6d411b76790d00af7e4992f9f64507030326a65dd3ea5e812f04608de07bfddb26b815f6e3a1ea66c7cce6de6b389f6ac38408d15203d0db863b100debb4a4e066a9fc79e7ac74704ee1c89ec20c869588458a5199e7a5887bb0f9476fd429288afd01ad6001ddbfd07b8c962197dad900aa1f799dabd7285e58cfa82ffc0b57e2af2757ef1027b1c7d52d72a1def55e39d1724c56f287bd5c4d2f13c0966f1c6c28f911a1d424a7137d642ee60ff038beac543577d135b8e4907f82d47b3ad46ea1341082a4f70ac9136478304be4e7b3d1a0e4e95e4e191a0c703f2ec2af42ca6b0b2a90d9574e301ea005290840c4b5a9d80e0e1fb5f472441572add5ea8350aff343957bca4e6e22957f5199a2d9b699538797aaa2b4c6eaf07eba79cee418d3c521f4d0857dcc4dee03b020f3b4f5358f9eb12a57e93d7fc4a10f7ae314a12034c71d434923b10fa3812df9f2b2e8af2620f465f135224bb1edcf084d7f7a3939c1cf97f8849fc78168fccb267c386f5061b7ec5b52c04c69d98acbfd61ebf858d3cfb2f5e43b85093c9851bb7be8f67a72216018ae858a8c152783d43797d795196069596c6c4ab224a8df6c95003394f5d0a354c8d9ffa27d396948c08903d73149836f1730176d424b95ab5cb6b00d98ed53c3b94884ee579dade316a33f78f5ee2e158a53f20c3c5ae6ce5b5444a3c23b9eceb83f0af1f68951ce5bb5f1a32b436db01bbb2c59930ce7895474b3edb5a0066b6bb38aa6527a6c9bed4a8b4a4a26f57dc61a451e7be0a80cf8d39a9442e54b8571f200e84f4df7d9ad3bd3f7df188bb85897737c7527c508dc682397fa2d9cbee197f49e967c8385241a34a971565b77cf4f727fbbd42a7bc82342bd556a96749eed08c86eb327f5ab9389be6ca5c67cdd83f532886f9e75413d26b1ccb77518e0cbf62be70a79efe3e2abd8d4db88ef61fb3cf66fefca223b4586f7cb25306090110eee333fb5cb81b4d590e7898e753ec2e57b1f459fa5af02e77664f220bc812cd53efae586d3e6580f07dbca1df994c5b68672b9bfd7ae8c49afec779ba6474cceffe2838dbee6ab1e12aed05e8b2b25dbd4d9426479c3b28f12f2d14fcffcb18e74a5121472a443a935d1d330f80e4f3ce3eac710b335badf89d716bd2a56958aea92b036fe6d79e86d170e045144e9eac7c6c845cc438649f4090532a35acaf3bfd8628a42402cb6320a552be8ad9b746b2582a106892fdf9704c7f6db42e4719303f0833f058fad9e38e4e8108f81e6cbf495b352fbb5020e1aea8a82f777d491e64d35efde045617f081e327cea5b30f1952f02720cfdb8367e3f64b01211c6f8e5cb58e7f8b45acbf8e704dcb3fb55963ab2bb8ffc7c230ea4a855dc644db32e43ed5e2e46dd542e119b8de328d472070caaed188697d0c66badabdf6f136c73734d8076aded7759cf37052911a33347e1f7d5d3c6089ad994c928d8035e69703c1cec1591b1691a93da91973e61dec44fc1dcd2a01f1d779746c175020c1d1b2dafa1b703c365dcc6bd3fc19dd2032500dec7ba60bfdc376f95b5f3fd17b095974af65788262b997700efdd5f5f16194c763fe3ef199b3342b7865a3c041f4160ea813919cd2cc4036893550b3d0db6ca7030bac0f5a8cbf2a0db5265fc914c5f10d5329637cd6522c5d7a509290b85bd53cf6f9327b50bd4127dac6657956aa722d95a0fa7961faf6278d418ff05f33fa8b0ebc21a436a4bb4be49fd6d64a4b4843cbea6bf1853ba25670e110d0f70e0a94aafb22d99ab8eef0efd035708eebc5d243c92ce70211a694f7575b7c53c2e314a8e10a90524408b0b57ae09c7e99d02c5b7d68dae08befb94237ff90c25506f547d861a1a234df1dc9cc5dd6e9c69df438c9e44777a6e67731431dca59bb97c1aeebf74631c200a293f97fe7d1637a460bfae0c341361acf18df1b734abcd474d8ef76c366d7c1afaeaf35c6802934203cd6a21ce90c780ca9999a0cb0e21ee2c182748b79e29b37c4ffb61f5435af9d8b48b81561cb52f210e13c31dfc64e6deda38715df2783c16806ea97d5b85fd0cc94298d7c0e026ba98cf323492c384f4367b2d1226da22f38e795974c2ac2a854f8356f5de77786a45ceefdddb6c7c8b33189badb77ab96a8b1fff1f5c5756879874fc56d9f8c6f8e067a07ddd3737087a07d64dc0e18e5cced8ed941ebddbc55229f5b304d3679f45c8ae3ff4733989927137de9be79b5554d0ab144dbc4d7b9ba989212c787f2243952a4178e92c97a4e5a1cb0aed2fb24ebe9bdf18f79c27bc788ec093775330e1b6082af56f8f5b2ddc23b8b720a2aae62b9b9575d6b02706dc5dc10db1d1b9965940451bffe27cd0f067a35f76e577b771400ab2dfc03a258ef86545bed41a74d7dbf4325a072568bba055ddbd90bea66515d4b8480bffdd6c2163a2d615a182f77f98a7262270fc94dc736e774a388e29b0c91a6ce1f62b71679d5c791c2dc9f17081aa15fbdb2216c17d4a6510047b13304d22d5cb930a6c52f9df44006e1a9bc11ab2007550ae0985a988a40f4ac797a1f11ed550fba3413396d9403383703572143e2f37526e33899bc326d84fc7f58d3ef5e3e83bb64230db3a6198ebb691d18027da59eb60ad62e5b92e58fa1c336b31942c22f05498c587cb75a76d1e1c740a0f218f8c0fb1885f863a0fa78d5831254a60687c61bdbaa195de713993c3fdf83c9ee78a90a97b9867b67c16195386ee7fb6eea04da3e3f72bb27a3350cfdfb0ee8a6251194589083d4cde3e9508eb61e4f3099f374aaeadc48260f5bb60302059df573b1433ce34fc9e9c87a273c12ff90255f128747aeabe90a86ec3cefbb677192b12280e91e62af07cc0ea77ff1769206bd1d2629ccc72f63ee3d29b6eaefe28dd9a6db8af1079244deeb1c05eda6c27dddd9f57b5ae5a6be6546160cc59e4f4796741199b72422199620098dd7ff3d5bbfcc3a02af671d8b5f05a90b3f895314a9bb5c6f7eff896a72e93239d17e511272ad279c9cc5110f14d51b0bfd9a404d455a4af3dd8c487a77fc2fdff10327011a484e2dcfa05f2fd9bc9e7145f63ff6e7d35b3782eb8280416415fa2d93b08a13726c2e3b79935eb7ed0bd0c091b290ffd3484eb4374b83479adb381f2d9fee8915801f56b28194b4ad25bade989fb1fb3e10c36570f53c1a877350f6c9f4997fe172eeefa80f1999597273b25b067838460bb35d667922a1448425be2914b119c8da67a31d53ecb7423d882636e7824e8386fffe022f53bbbe7fbac41c1fc88e4ba07808e6d48262b35648a5b758f083515c9d36af472370737db46c4b87ac870d8ddd3855f4cb87fd791660ddeaa7223a1388853b4da688614e53ad4b1e63a1145b8dd30e3e85b593b7dd076ced37e0421581397426b1faee658809361d50105ba1c6663ca881376cf25fadb418080d0ce914576952e8b5196e768b58c199db283c0fa3ea734e654d4100895fc5bae8b20dfdfbc3615c0be5e7c99eda165e9bd5695459a90e09d4e11c22e53a1ddb7c4268376b64722355902b1a5d483e09afa17354a93ca28effc6dcd3de19111526defd78208789b36f96f68830c913d4b2b74d9930778456466ae67f6685059243720d7e3dcbfb43787d53c87f4e38db680babd1ad104179fc4350413c3b390c40a96ab93196829d2b0f0b8270de530bae59c0dd60517f8c25c0a3a994ba4825b67aa8c93ab90ee36ee6f2a7fe75657e3da3baeb6859c638c0ac0ee47764c27bce3e2de4d691819cd569eed71da8aa35c2841d76467decdd92058a5f89727c36fbfba15cef324d6fd7531cd6fd96be3dcdc4ed185c095a4030b08f22d7228554ece9f344c823d114db78ec8a3f6aa9f1f9e2b201265b5957766bb0dd97ed1e3e7f52a47686d6f52acc0de12d365aeeae643374ff66a8b2b703b5d162cbdf37f2d249fbdf3fba284d894b8f2d29f90dccc4eda12acb04f0bab4b308b4371e2e35ef61d198b89509bd8570dee100fb36430552d66ef6ebf8c0520b993f99609f1cb15034031776a5783812e157932c16771a1ea4855d3b538b1a2ed4b7927ef54d6144f45d76d2e4b1251ee1c10c5911d46e1e97dd1d8dc516d5c241c60c42b2561c1c1ba52b8ce721cd24e902b39aeb82604c54d949b0bff54b9040aa059899b64e912d3a459388ac14d22efb1659cdbbc10d58ff16f01e2ac6d6d5727594df89daef0de295b41a5c6ab1250cf7aa21e898d28ae4db26754539d6dde151a6bb32e3d83c96ed56ab5e30082d94d319f019b158c44d598d93feb9bc69b230f5d4d8598d1e18ec18123f839acb25503ec3d2354a038e1d19bd73e010393f91b63b9bd633ccce77f391eedd167e5f399506d977d0ac4ff18228ab8f0cfbaaee0896cc5089e396eca3ec9a973abf82947d17660c8ea7f17e9ef1aa7a84009bbe44cd1ac3502bc4ca8bec306b6c2201a540a4f5a556cf91ff25588c6e14634c08c7a8003483cd9e1759b1d2c7a7a18f5a7b9afd76b5369163c4e60fab16442e59ef6f0f1aa0800b4d8b81bd437cb2c1a202098e1b25286006dd68d26d11a9f4e0b0ba944b9c4f2fe6ecf610a5f977d9dbfd34b375cf50247491070799765f37eddd98434cb2228605976e990cefe23d322cd0788afb5aaefd921e23adcd38c69c1eb3c456c0e4072b02d1334ac82bc8d53f47b681791fd48e09ef0eb7a89e8f67dfacf34298c0e74e693d8089d4b1632e38e2a0440333342c7f5e28f20f4bb4d57e8d878b537fac3dab8661de7ac6c0a373b9881d306a5e53622178f8777cea310c33c15d5ffab1227b0ffb028f69185158939dbfa3e5b73367cd7452bae41e1298776a97d9a188b51c6b315f73c59464a0390558f5cea6df42e1e3c02f161681c216da32855a1c54bcb12cff95dc74d4694ce6c47b61b59712bf7af19a389bb903f482ea7f073c04945c5d9cc7c0fe85886a6c758a0e8222a6d50f3cdbb338db14dd497effbbc53fff15d55a523149343f381007214318bf3a5b0c602078518f4dd98f35d72fc9e0e7fa4dbeb74fb8357a97151782e656bb44326b058774f7cfbfe334e948ed6727c0a14100a2f974c5b2357b90b7d3d5116b0951ff4a63a880547cf47de988596148abdd581017e34bd8fa20215298f46c0e58f35a90184abefaa6f4a729ebc1ad6a8e36ae01f91d50073f6f4b5eb32edfce4bbcf6df53aaca893649ad2673dc19ec921be72bdb012157e7bd2663f72369b56efd8b69af8c110ec00b9ad9a4aa6a0ac9811096f0ff438aa4ef8a92070c7ebe758bd97827fbea807894ccf40c41ec829bf0f5ae7b3bbfd51329f47c68e7558ddb7b82d320d462fb77213d957e890f55ebdbdc64300fd3f5b9c2297c8c30808ad18366838e24521471a26a9654510db9a39a6d52fef133eac235e03782e6cf43d0d42c6140941b648e7eaf811c73369fc8d82f282489f764bdfd0a57725eec11b1e3130af7498d80ea7bb731dea29bd3740f2307802e5cf5a5b814513eec83010cbe3184bbce0eea54002a4c651d55cedf04d1f2fd25fa1869c89f389c5c332976f60d337eddf8e4fa8d473fef6253a1627c4c1d56f1a80085199bd8bba405589b831374e11b98db3cdb9ea648c4e3f163e44b9a17a218a6918f1434b03290d6c9c5e50d9e2374127fd2bb45588ea1550ba8098aa385644b5791a89d9aac5a1b403cd50461b95c80bd9f2c16143efade2462405d3eb103e270659a07b4ab9cd2402d4fac2e80aa71c645e2c22b17297702fec2f1a3cb1c79b253f5afb5bfedbf595a2f1b9dc2ea195c2cbcf25e2f7807aa76832265134ef5bb4387a141db4a604c11f871576574e29a2cdbf0e11ccf52178c794b98b01ccaef3e189b3ca8a564cebce5fabf1c212eaaa8145f44e28463ae0163d6600ea2fda5509ab78cf945f231b68566c0b494e31ad6d1a54fdec8672ea0e764b512b53ac5958f6fa46a75ea6d0720b3f7be700b13f1a6321b79ec9f7bddf382ac9afceed10733faa5ee94039722c067eb303250b1064ae1464ecf972447445c9bd640c1913e77366a8b6bd09053b632b8a431162b5eb4494e136c239837e13467ff76c522a1f1f3ab7b77e9a0446212cba005e1acfbb7daa31b24271ef9e9b485f99484fa9620411934f90543f04f932ba53531db16f0fe23d51226755bdf9c81460ee75c27c8053dab0a4a9ff40bd41fe78232e71ec4783d995d665d0a76ad2453380c4dcb1bbe0d9382e0b78f9a2ee9bf8d2c88bec14f92de1908aee568757a3693a3b16d0fa3c87856459c6fd136c5338ddb19fd0ec34e0994d967e427c88d2a18ba4f009d39a23f27b8b9f6355e73464b90cdab02d9f1f423878a9a498fa2c3f778b56f67db604f922b1bb73639d72afe00a1ecd80708f759051660c4007ee3603f87878a21babd4804d4941049d2b3a8252e93e1b1361ecf287f9b7128bdf9177734cecb7a9bf9063e80da792401ec0da1469783c1caefe9221f9a892a09c866b3d6960f88bba971a874006c96c292ecee985fb5ebe070aba165b963fb6806eb50ee971a6f3f9eeadc11338eff9f2626eeb203bbea057875fb0bb9b9ece27ca28fff32de81bf62ee71edebe5394a46d8819292bd9d07e673f0b7b88dd19645b1c213f8232f98932ea2c1a16e36960ab48fbb9970042bd99e1ab151fce8d6b523c41dc9b4bbf4cfb5e0292d69322e7a41a702254b859ab4b458fbfac054eb3be979ab198fd1c3f044dc962d0958ce0adc59361be4332494d44a68954b53beab17af80b6ac8e504359506a7b9dc1d03c1a2c8f4a544771abf65bac31bd4ade2003dcfddde21f8c7011b4770528277be27054fee406af483a7f4ea3f6aff101b9c49a9c9fa2ed9d947b16019fe344cdd95132983e4f4e8b22a898de5a4e928330de8ceb6d0dd44949387b32236711a1192c210b101380ce94fa089e0fb9a759c4785dd8aa8696aef5448040124836797cb2f6c1cb983951b4c2aef159d93b1518f8bacb63f0eff1a7c5040fd43473394b0824c55ceaf5fbcba6e26554ecf4d5e0f376742df2a7b4b71ce4cda22e856a84591ec7437f0b280ba2ac5c58df5a8c0027c1d0cf30dc10b4faba2ea66826febc19227cd286ab1bf4d9a8ae6c2c993d5966464b3d8357a4ef02e161cc55e7e35c485600320522ec1e40d2c82786023ecb273337c256e8dc121b770d6a4df38abb4e44c56eead0c4e1b56922a0ebeb18d6558d7f5180902f4e1e420054947746b78b31f0c9f1ab38df8e9b624f0f6c9709bf9ef211cb916881605605f526dc9d18d228652590178e4dd8005db1b577bfb47bb7deef6171ffa40a3ce1ec7ca1ca3c1d07f47d0a78bcc6d62aeefc685fa35886c1837b17b74ef2990fd67b24faf075019e15be46277ed4eb3231933ac5564dd21171e886bf41572ca2b5d8e81e2f592eb1d121ab05e2fa80900d8a1fdae0df44e1973532c9d77cc0f02a614885baae286eedca8a2254f839b79368fa9dd1d57cdd387fb588181dde1f0ca522ed133de87ff0c3c94bc064c9897c82027b3af5eb0e9cd9e8407888d98f9d573fd9ab6c1d1307126c3a0d3659de3a67e2be2edbfbca4169d9c896c69c2bbe2a61a3916ec3dd8b2d49c35b656bd469fde8a5e17adb9683df7586d1edacd0c147abf8bea73c4c632336c06739b7ee36b46ccc45b6c654e93bf7ad27f8c55329645215ca5ba6af2c2f0cb934249b7db4e9becd8fae458e8be915fadba6a5dae76f05cba0a9541280c7df6006daac508326b295e9c82550ef70013c7780a44c323557986b3ffe8dc0d2b75ce2a10beb9b877fb387aeaac1540280ace24f7835954bf33942c7f8319356d67fabff32a6398d546000ba06ecd160008c2192a3e2d4a5a01263da974baa249168fcf6b584ca7b9d5c69593a2a77afdb91086b91ce3b83ccde7a68482ebb7809f8c10e53a2261e407f660cf8917912d87648b6967285c5441af91285891292a8b15e7e6a82e94e65031a7c66f6144a7309ff12be6653f67999fd6a7f489953299a672037dfee574599cef2ff9d1620febfb1e21ecfa1f23ccc6e5c1285505c4b266e472523d92e74a4fc365583d3fd3544c3ae87a83f0e485767e2e54ee5d87082a3790511374141aaa43b72845ef309240a6b261813d9daeaa9ec86e467c82b5c632f6c9815323a0327db9a76d25f5631bc348c3ce1856b79d82971eed39c163dc794e7b82b6f8521b028b97987454a753d27b5b82af11916a8fbcbd0a715092a7a714ef9f607db2a855cdd73b6d5fce2dd561d0c83b3570de779ae990bcea1d036069fabacf6750b52e0b84da611414f5c57c7c61920a21666aa7709ee4531ee562bda51c307327ea8f7681c968ce17427ebcfad3589091aa20d50f82c3e90dd6fa60baef948dbe09a220da906fea226c0715f6d011c2560faf4f6b30ecfd72dd99b6acbb115564462d941360b355b0a13c222885409bceb877aab1ef7e5ed4110f1e7f36e7bd3b3462b2cf7a676b66abdacf405e627e336e4ff4f91ad79cd20fa2c7756cd0a9589826118ded7e4918b426184a1f309750096d13097c8681b806f8e80674603ffb4664f6f432308ae15d3476dfd573ca5bf424be5efe32690fb8beefcc044fbfcae57ba0f3c64eab4b031fdc696e201373f5cff6f077eb2eefe04731c0c3b78149a577524277b7188db759a32b91e14ede952c2c00131dfe17dda9f87b7a751bf2b95d67d981fb094f39f0081620faff19fa22c9c98316f3838768bcd4bc89d81276f47994d127d5825f641c8109b8f18ff6e894c15e85deb6e26d4f9ce0d05b296082edd0fa6bee1cd0110707a395b97f4ec69e488eca0e19d779cccc96e2e8d0f2918a2699ae9dddfe379db1bdbc82810281bfcc53d1fca4e9c3b54ece820532d765be77bceccc9ddf49d0aa8ee8bea169b90a6a61fefa344dcc04838992fd6e2aef350c62dfca6118e5dedbf53a75d5d6cd406aff5b4c2a8d7818579ff53ff599070c9974706f64d1db0c775341c97be529442fa4cc9169e4cea3783bbbf70bd2ea4557daef34c8a65d2297d89a54c5b705c639cd7a886ed655c88a5237653d58d18726e16ce943466087923019b20a02cb310d9ae57ce52b64467b733ddc25861640c52f19ce0ede997d4c01bcf0df5aeafe222d67a6590b9bf7c255e8ea751f9d846ca6911cd4f41b95ff6120b08d09ba23e98fc479fa3fcb3ba5aedea161b4867deaa1c2cf2d296b06bbb2966d34f293656c72616eaeece1377082c8c3000d7efcb9e62cc19a5fa4ca1c430ddbfd26d93a54381123d62e69a3482b1a754a11568cd0b3071a60069f7a6c4dc7218e8d991cd63f53f9b2812d98d76290b08e146f5bd4e124874f3a08bbe9f2ee18325f99d879ac01468635157948241d2aedd6565433a6f35b8b544a6df1ba86c7dc17f75bcc63e32e8ca951e6b698ef647677cdaf3b5f5f955ffe7454fbcc872545266704e7fda9b53e1b63a9f0e8110b21f6fd6fa59ec660292b75758662e0d1ddad350d23f25b1c3dac596be1967d8bcf539b73b3e8963395c6b9695b44b49cae5c95305b1b7ea6f29cdd317231a193ee1e80a30de543843d246ef9892f64d669dd3ff6ca5722ee634f126b8b13dad82a1efa8f9929c52c8ab0365e0e2dc216ddcfd56fa66115cbb1eacf5f442de2c2683d1d75ddb6767efe5313793a7d8513b6a796ab4386ac63e4289ba75bf77a83ee336b766273c9bf7501795b976bbca6028c3e3775927ed30e92538ba99345a87a35caa3c8720535d0f19bf9d621cfee39384a543ed8695dc8c612ba03e19b2e36e7e9821c2ac26ab94761b1d083413bcf06aa06c297671655eb08d077003663c57ec936d988da8ffd5b9608e5736afd2b598167a6bab715e0cce256438768102bf9d8c58469506ced1a223f12ae27f2b37c0328ff917090db471c52c12f074b78035a08297422d3c0cc91f6bab5b514b066369397b63fddbc25669ceb862483d4f9947afbb1e53d6c83fc8fac83d76303192769218f8983620cdfd12b7bb1d8c6027c4b75970ed91ebc4abe16d8d51d475e97f5d4333fe748225d753f30bc055aad6dc4880acd6d7ce918648e583594a0587fbcee9db57fb2462eee7940787d66b219ceedb1b3da01e411874a4e67d554a85dc0b7f8454aa120b659ce10b1e4d7a7b74bd2cfea8023f0daeef931594c9d47b05662de4a0a1eedf84c3d23a606888dc567d582346061ca0a35b6df53c42ec288b170fe6b9700b317a5d8344e00ec2d724d6e87a04de94c88ccd84c183219f27437033da79489f7b87390d9786e24672d773f16b8bdd03c2ab8eb2a138b7bc2eedeea97c076e1b4d6156cfb3fb509e904487e70044f4ee63b359aa121985eb482f525e6728e8900b9a02564f08e701f6eed5cf58ce48ba8c92ae18cde4e9393b5124e2680819c1a4b12fe6c127652490bc38f48e315a05ec0e16c1445dfc25a652a2c1c41a579b2474909338d8578421f0e11c9aff0734992f23c5709d55eb39b72b3d02b5430fee882889baa4870fb605a7419a8defcf6c23a14933fd41c6057a1f903439462743fb0915e658e84533b1592be5e7b9469cd56137a65ab6eae6c009acc785711ecb5c84a5c98ad35b7e94ea5f3354e19debd5288cbba2d25368f34d5ea8569c094a4dd9d774a2d28feb47e9f4168b2fd13aa5f2f73cb640bb60a6636496e5dedf3f0e6a6ad0639d750dbd7e369b985ce68a2413d04f3eee5a6d44f1dd593cc7afb6c7afbb6664485824f72fa016eaa596a24cf45b121c0cd60d073aecca763fd3f23afbd1eeb1da2b1cb85f7431a03fb16bc4ad432d2d9358eaabd730c69002dc5b8c3ab216b33327a49703c0d0da06a2494f76f1b63b622bff934d5133af03d3f29094c0d5f1b36ffc6f3ae09d2a406d7e3be8fdcf675deca1f0552e0e0687bfebcd12609eeb7b5c7356fa344dcaac9aa370352b318ec70f7d58f2a0cbed1d2bd328162457172841a9a76ee8fd0c06dbb889daf2c1d773cc4aa23a390a5443fe6511c31bccc956496be760d1fae1a9f507e4ead1ffdd0477aeaa3075efd30b3b6c40fe232a87b2afd75f8a8f8a5a7ea7b1df942b126604d73a7cfc9cef5c86428fa4f6a4823a22f29c5417b60da56a777d76bd1586dd74106379e1ac6993ee9ff4fc5b2256e5e2c60d4af884da4383b2eac1673fe310e57a03f81ed72716ad596ffdbdae480d22e7bf2ef1cc017191eed0d55c0389b03bf0cb1d91f6bddd11c7a5d965511cfbb99097f8fe8772d5207d53aa1e3ae92f77930ad06cbfd69b2d8565e625ea9a103b4294055ab2d90431d1c8b2b7e7edde491484975d5413c0a04ac1aadd3db5004086aa1367cf8bb0f3cea0fb04d8345e8b66a19ca807d4bbadf93c808932724960d1fead7317027ad743a82fb6d0856116cd5a1d49cd32975cb5cb8eee5d9acc5265d2338caffea83f46abbf8fb6f63e3aed7dc9994e549062f34fb27e8cbd13542b0f375fdf8905da4e30e303d85243d8ebe577490c737e6e3b9402f40eabec43b05d9f4c59d3b343c6b5abe8569baf6e7c5177a81b9ba73e1e5f75af7b091b81feb1b271adf716fd1ab0b1ce796a60eaceec4bcd680340f88c8c6a87d1353d5b31eab0d05c52243c14ca9b9b2e9b7a69c338d6b36050cce8afdf7be2cf5f808431b329cbd16916ad18aea8f94091491d09af78d79704bf80acbd39eee15764781dc37c44b6187942f238f3bdb17beb9b05d656ef705893c931ed519ecf997320c0e073fa50df48f98706ab0bfc2ffccfe6b309ac98c5595fa684578b8a67eb79f3a353185ff990896761242242d5ccccbda08275550ba0701773d2e3c83da7bbb7b006a268d1ad531603b16ef35bedf3bbd50b16ba422d1914785a319831a824d72937e3bb9ed8edbe0fdc08e43952ec238fddac4ee9c8a4752866546de39af508c80e3cc8053ffa395562502e6de8ec52fe1029df3ff787db47d806a3bb7d76df1b12bed5b83915ca7e4e38f74d418d5dd78f2961f034274a7b0bfb428732a4a03b4890caf29f066c3ee4d6040251d429eda92cebbdda75738f4ed5b0320aabbec83d93bee61d24f26787840fb3f95afa7f9dc0c9837aeb8ecec78efafcc1c642164e06828ca472210281d5a927438bc01ac6af11f33488e89e83bed3784be997ba61cb317f161ef8a32e2e59050142d411f6980dcb8c50d58d05681e880bd0774abea49a4d265071b49fd05cce1309cdf4f131735acfa1b4bb8ddf34318818665f189e6a091b8012ef15cfcbaeb8e3bc24a5a8827b5a15556fe29e5d35c48c8f60d73d829130587aca0a28c2f8c17475ef36645aca91c748aa02d9a2ece185376f67c06e27a146734cf3bbba84e2287b91465c3ea0dd4de0176b13a6b3ed1a5ac3ba707e568289752950f9702ee5673a4afa8c86f4ad2fbd1e98865efa5ef915e73b70a0957d5734f5fd8def6e9b25e6ad269845dd49feace5658ceb4e56083a1dd3b1e8d1105f44d382e232877e2e3c3811cbb75edcf557446f2a2249c375d4a592d94997dfb09670191dd8b46b52d4e1175495eaddba8c94b0e220463748d368d2a0cc2e168e92b90a6ee1f8e33bf630f405f598271b33130496aec4e57e5841bdf111e9283e0c9605ed8f37e8573ef9c3120dcd025839f50f8501fb0936106d4ba20c67cd92f4cad934f82a96af54ee649f42614f5f7c9e697f8297274c5b1b3e356fe0f7791eef5edc752f7d79c35827d9a8ad57612ed4a3cb0e4fe768c98d3ff5bda4cd40728c9272018eee65427b0eac5fb6e25a4b122645a7b28b586e49667d6cb82324dcb969f57323e4315e872748c9df29dadc6bc21b83a3f7572c3eb6445f2e830f32f02fafdd1cda8b2e5ce1d6ee3b9ab98c2c9a62b9535739aaa85bbc0925c72ea95a691ed39398156cd0c6dda0218b1cb8502defabf721658d379737fee319871050b2b6960390429fae165f54d48480f88f4016c0feae0564b2be9c9899575312a2da40d8c6f3c33574698742071a37be5108ecd1220ce96fe49ec460628a3f4d51d78e768af30f529af9dd09ae07e080f3e03d1700464fdfb097a024c0be19cf8a2c9d33d6ff158c8599cc3a5637b34a56a3aae9e57924af2e7446d551040e9914f33295133f6c4585840e59fe41a8f46d9289025d70fb6ed03a8112932772ca927254c64b4699cae4cc2143b5bec1b50a042c510c071e7e9be59b19b03307a09ccd0b37b717d323e8995b5cf6d5b7eb7e6523d55146902b4d42b0b8aeaa5f99641a4aa14f06486fd9bf10585b084047b1c45e35f6c0b8aa768e41516a49d01b25b22861318edf1f2d96a361216bb56e4a7b8a3474e1a802d36ce25a2f3929c8611a8978a9d59b5a566d99abc19548cb0abe60e68df6f7c328e49a37fe12381477deae7d1fef48bf0f8bd501eb96e3326f2ef68536d3476c09c39b06629b6e7f87c91bd98e8f560255eaa4ca0f40f6680160c2b30bedd150cbccbf8912e1050635b2b8ae16dcf17c0ff96b4564777eda63af3922a5a28fcf2e4c5aa9f01953229b28e337a6010f200b8bd303f541de39f0c8a7416189a72d70e9a8f5e38eaa3f19e030ac58ec3675e699542ac88e66226a9af21310b2d86c00d548875be9a163c662d06a0a84d03480c6ee9de8857bdf8a493116e44d1985536d53be1f00052eb11974590ba1da1d14c231de69ded2d8332d7ba2819df023de03c2fe858e49af321497483a4bff8eebef88b2d650e0f9e3b39ca8faeda637cbf0d889529514e73ebe35fc7f4235aaa4ec9f3906ce10e8737cb6dffe07902e3fbb1e3835cf5284aede3b31247826a652293b1879475f616c1f80b15e1267f0c1731ce6d3ba07aacb39f6bf95755603547652e1a4d20ff4a25c264d1d97ca5b1679042a0e6e047fd9dfe120aad71160fc51da7f8158b6179d41b280d9eeb5eb48c77be71338e2bc1657e0350779c5399b4d13e82970f9e251fc84afe76bef681ee597c677571e676309344f74b699f13790fc42416235a56081e4965bb0cf6a7a90b36efbfb70062458fdd572429ffb8a7dccb121d169bafefcd4730290034a75c630346af1829a26ba3477e9859e571494c862d1e06b52b671cea595604cce1871ae87282e0980ed4b504030fc74368fce3fe1c61e536c3a761b5e7262fc81d123c7bba8808ab7fbb051e1d0f20e146f93f815f674479a7e11f3b9494d2a0fdbd76d67e51808eb8f29d24a60602d784a96eb8f2c5965dea398652b9d3fe64032a7f9c5075c2642becc45a54c4065ede8682d8c601595abcb5071f105c12b801469a2e1ccd10a900bbbce5bfa1771adb62d1ed8b5564b0dc9c1253362d72e1bc8bfa2ce2e0f570b0c17ad6fc4f06b8d9ba3dd7d3abdbccdbecb227b1b80c720fbc273e82e72db81e6c5cea3fbacb1f71c2d67674f78f42a624529688e08f964d4d6d0863c2a6867b2d8c0e41f58a1e13e653b94d64d570dd509ff9595c7d7eb6b429d067a5091045edbd76a2425cc9637612e20f0fc30ab37d0b20f724cd999dde9f9c575152f3d1e0040292b06a44457c5085f2ff5b179e036c487ed7d8c8d50630b4ff886731f3392dcf35eb24fbb6a0c5d2cd781be73b5556990e88ed945dc89bccf1bd945fde9160dba8a043c63737030dbc63ea6250b8efeb2b66835431526311059da6317ff1298aea458f59603c6bdff28f9c365ae5b30ef9f5cc9adc50210e72372c1b141dd0bf548d5ba7b36e1c806cc8ee654247c651d5832973d7b5f0585adb8747cf08e8c0b6fe17519eb73af881d85cc528f9a83edcd3d26001f39292b99a6124bd10007ed80893b718e01c5f64aeb7406462603cb4e49e6da8befce355d458292a76c7461bc3208784567e7ae520b37c98c19271838fcc3c69b82d6baa1c3582633084db4f71f9727f1c09c1b6f52947735c24208cba766d9b68357f17cb9cb3ad6269a10b1e3b073fc7781b7b270c2f2923315a4b08910dffa9c167c5f6b5aeb46059927dc3152d289dd0984adc1c2e673a797e92fa9e40f890671f5339f7aa841a9bfc478bddcbf8befa6a01d7675eba26b2b18aca529eb808d07fdc0163225581f40a3aaa31104fa567633c61600f22e627a3f1cd96577e25481e60ba6c7b8989ff02734f7ba88370364646d4a702c57991d1e0a7639642a79e73bb95af9fa6d4b9620ed83a3e55518a525094c7be4568995d487f1b4e6642f65d614bf232b50e53528d619461cee4c23a881386111dcf14862bfd52fa5fc11deb4cd283fff5fd19d54a96bb55a9a7c19a8ce4060b870fd9b8ef36ce30b7029560d1605545ea481c9b303ff6fd3bfdf746d0f106870e90475405e6474f82bf499c9e6a173dbe6284c36ef364f5d9f738d83b4f33c1eebdbae6d5e62a6b6cfbb1da6c1d03ca95cac41e7cb9b8319acde33044d3a7291b717f265764b7636351fa959f76f673cdfde8d6c40d8425976326d255a945d2a31b1177483771d16202ca03c3e69dc63ed3378533ce0014c868103cb0441f3f810ef5d600ac468ff9c8744a2c072e1eeb6af03479dddd95a584080631206f1ced40317e470af340ea3b0b92c97f0f7e0faa156b3a6910c67ecfe774adaf11386c774ca789033f30f114ee13169254dad819f84db255c1c62ed6966f343a3b426fc3d1067a36dd1f2890a6aa6da2001d4ef4d3bc45ed5d7cbf4053abbb98ed42cb7d8dc018e3618a68a545bf21617aeefa0b4256f4720c57d4ed88d014b19e252e67c6f78f77cd7501740227eb637ac639e85d1a6324c326cdc578a2fdc881e8d616ad1eabebe6815e71a974be8adbed97db345514fc07686db538ba4d18703b8e38d1299d983120c5e0b837bad130235bf368a7f31f1e04bec1767be24ff7b8aa84e7b4645769b39a8c964d4c4b94c103b246ee13b3829d069c19c1e628d31b8cb2c7e1f2162071542dc175262d6415b9e7ee9213a5aee4f3bcd454348676a06afabc758164ea72c58525024a15d61699c1321522bae6e028b24dc11443e29fe7dcdf9398e464b8ca1a264e862a1223321e7e9030358f7d88ac8128ccd53bb8558f49d05debe296ba33d23530b619f652f120b4cf19eb7b17a0bf167acdf05f4e4dbbfb12abeef2926acef130342772741cc3b707df5ce452b1a0275d6d96604b58fdf63d2cdcb8f3301c9e7b93957ca07e914a66f8697162f0aee9c92a3e1c4eab4314907c6dbb74bb55d09370f7c0fc9b8859fbcd018de1961327eaec4f6d8781076973c22b668304b10be574bbd83b78a9eb711ba068b461af78ff257cc4d66f4b5b651f6958510fdea3056249c646155fa3a8c325f7002a95764788b3774d87e0bc21aa442beb6f76d7ef47880ada616493d7056514cac95e84f546c7dc73666f8b976e74375711a8dd036b9712d1da8a87cd9e7e34f8b00ca90bde66cb222e91a8f1a8fd242278ce480ebf8c2686c18e7720178d14c125b7877fdee13b640be52529bd0156dcad9357fdf9bc21516503bfbe80677e358b6136df99fcdb2fe3ade2a848f1f94f54133bd96b22f3f26d9722a2f12eb28c889b106069e9bc32adbea28dc4c002883229f9251ef1f8c03ef83815e77ffaff3b1686d54378ffe1ea7f7c9e5c415c01b20b9029f89e443cd9b31262f29e718aa13d278869136d67ba9e132d731eeebd619f46b9b2de3ec479ff57aaaa486db91fcc0336c6608b82eba497085dae1ef520639726d9a361c851d8a77a508c7b294646ec1ad785db49d6111623b4a691d687a329713eb0e51ec3fa9f4314eaa87a88f982d9481bf94f5b600ee36a191273592d5b6d3d9e73a3ea8b1d38a18ace2f97a4b0f010685e150c57242f772242a0ab0278de099040f42bc21dc3758d6d38d0654fe9ba7678ac58fa8db987cc9664f277d5039fb9fe150f06d83af2cac0d48bb6b65964e7a70d0fa456dbb346a02f1a9f11c4d2fe7cff70c47ae2510ccd4bd586cc47d5dc649fc5b07d07a79357795848914aefb6c242dbc2f8db71ce4fc4138422b2d447ce76b6c75d1c252fe850c0356abd226d6746f0c1c1f10a2698fc61c2839d716081297f932cb1b512b13f5d6155a7d347a19beb5233d4068239e93a7f8f69ce8045820c5874ebec45227a46dfca491caae484f08bece1e9962ceaedd58f244b48d61af8ef834d2dc8e9a852b44b3b8d9c9d7fd1c19160a6bdefe9335e78066896492ee38cfb0fefbf0efe33b6c4d7fa83a42a0f72d27ce8294a19c18d7c3a92d2daff5a225297be7040addffe09a637f54670c681d4efe5fd10d2daada74081758eefc677469504fd896efc281db23114212f9eeeb7b2427023aafa716f91e206a4b8bdf3070201fb9c13cdb887daa74b5a00b6fd7f7f9f07ca9cb481e0112a6f2e1202b76c294876a9dbfbb1f0ecedcf92337b5043c48ab4baa7001f0447f80c99f01b4b69a605bae7398c447aeab818123e8319fa3f059df13e87157679ddc84f1c29a73f953862af36a96e5c179500ce23d3116d06fd96917ccfb0ded6d1f9bc0fc1dce1e5c151b89b73ebc8bfe973903b58dec42afe1af4000fb0eb3c465cb5093ecb9c058d9c21e211ebbfbc9db4222dcb8d28bad4487cb9ee5e9064f3f506541095d5b97a4d473a74d523d55d612a389faca8c30181af43cf80a742c71c508e6f1f0132cf2206eebc42cf9e7b613646cae7d23c89018897d52c51cdfcef81efc244de8aaab6d7c3ff2e96b918daf112ac964c39697c2e23ac3e59f39822c9f991415f5dfffe2dd1c3c9257456fdabe4976f1af585786cb079ae935a41607ce5139a20a76db27a87cfce0ff2bea0d75d32f667ad386464b26bf7fd86b68d4d917c1c82bbb7df6f6cde963a34861c684d19be17599fc66a1227c0d119f30d46a87ecd32b4002ba41ebd009bf06b4d7e0c6291a7373fae1c973e224f84cae3b5a7b5c96d8d6e3f5aa10e535b088b672d70820cce7a09fa4563b9e1b8d1b61de6d7b181b90d911c648492169e1caf3124e2c9387bdb864afed3c746d1727214bdedb08d100804d34d7080cb078805e57269749100791bd401f3d07e8511d3acc75d8d021bf8bf87e36bf1c747ae23baf1d53e4bcbc1c461604262996fa69d784dfb45b8af6f5dc263a50728b55dfc111234e6d89650ab867e37d69a827c2b0540725ff7f41d01884a11c241e06e514b27d6c9fe5e63c752ff0b0680d8ae5e06ef2bdcfd4e03345a86c56b9e096b126231c4601ddfd6b82bd3d1ea6178f087a5e00d8b2cfdd1dc18578d5da105f8988cdc35d73dcf7de10964fb964b3522016e3ad13390d56d0e16723540f48782a8522c9f0ac2780558a21ec56bd508584bb95de9033f4373eb9438abe267f0d042590222d00e046b6c5e2a3b86431112032956a9989f8bb65e34f6e085fdbeab9c200f279bec24baf7976238633a9e650239d73f2cee14ad26d53d1d2b3bb1b09206d0be6b5db4db9dff470df524b716c67dc0bb85fa6dce83c5fe34a5ea86790d11218fe9e3b57ba0f981a186af1f1d0698720a62cd16859a422d97580082da8ac7522605c9ad1e35a254cd8060f8644847036425d3e13fee63869ebb07cebf326b9df36311cf526c380660ae6cdf549c3b9b0c97a1ffa1491e86660df4db08b29d114f4082807618df697c699ac4c53b70c50c6ccf86c3a27319c34d4d56f8763f1778c68e33075611af959a4bb10a2dc6f4df938f8e67d588c63a18fa2d6e1c4082d9a62a8c338a8114d1e837a5871aa15d010c2f748eb9f87c922d54c58a94f27d0099b9534c47e171b4b2746d2d5ed16c501a44ac87b6d6b6dbe467f7a130682a461d8165f5734be3799d94b62eee48e77f91144f74e7817dfad6cb090110bad567efe81f42369695f2ae55613e6a06cc8bde30e6ca8a66165087e99707e2302373f7df397f2fcc37b76d65d980314d2646b022571f2bc0b6161ddcb9d5979517239841dd71303a6c627529ffee21d7f8591113ee685d5bf2640714b4ad1d2f87d47cfd6b2cd514f2a10795429f16bfe852c5c46de21b75e9099e9a08274b3ec6a4a1095e79cb497a2a1ee07f76f858298642c94a876c2c0e7b627cb1628dfd0d750a91f6169b202881d5172da61b33e215cedbd0da271553092d847e29e38f812837ddbd42bb3faa5485092bc1ea32906cd5407c213276f348f41e03953ada3cf4e8610d4b407ed67be50536c7eb647cc23b0c7103d7e5cd99ba289923889fe3b992746b39bad79653d316910dca0ede5062121b77fe69585ec8505210b2ec849a7190ea17facc9f26a6b2d49a97b0374b6f14f211a0a264f327bbafb9cbf2571551cec14ea20e82fa2d88c771adfa8de8e81dffef9c6f81dbdb4687af213b24ae0dd1b03306df028d3df60b3befde8cf7a3cd717e49371e15782e765c875e73d62de401017b9e43760f2ff18690f1d47c4c857bbb292126adf05d399839837514e38a0631ffc4fe7e664b0468decf2d0e49a0745a03f8f291a8cb90272fb3e7c7b11d3cfd6bbc39a31e69b50a6bda5797770f66b8b9ab984d844b505c57a599a493f21d533c21ea6488f9cbbdfc2fea84a5f6bbd051ceb48e0638218193ab3e9a798492998a5604d32860256f5e20c34bc2052ef81c015ea4f6021e4c78e2690c1140257ae6703b98ddc97e5a22651fb1478b1b78d281a993b014d3e74a88bfb3cc30f1716a7b23602641b246159b619e374ed81d62cc9223bc811a0e675d83739cbdaec64c99f41b06bfe8142afa3b9b42ed4dbc29613c19b23ddb267c0483a97d63f5c1a540ab9621b7834833edfce6c53c0d5fe1b0bfcbe2b78b3514d97de55feecfcf00cceb0a6c4806c03bd28d4ccb223321a0280e4d85cbfca17ef3ba8289332c577d2c814e1d1ff823519c8b844020376fbece31ecb6cf5daa260ab57de07fe5dd7c76b7896962e8223533e357b9a4192bb44f96deefeb5314546c90e131faaeadcf4e0238a774923c350620e8b9d8026f13de9e36584dd33dbb6a1fa783775e59a7e267f8e801c19f87fd9aa400dd38e8c502e7d8e47c7876fd1628464a662b947285fff2155d421f2a7529473241e8f686cdffdae815d0301066095bd0b35a0975acdc399452c3853253cb985046870df60fa6cf9d037d3e9bfdb07b3deabb6f0de0909e6d44b51b86a5fa90b76991ca20c7b58e3919e5a51e2b901844536e4613402f20dd9e99e0832e90636594d7bfcf6693488ce4df339c32ba346c2ac96fed8e45c38fca89a5d5903a73797b78ab3ce184db66893bc06b18de56641b27aa5d8cc9c417d230bffc0c6f02f1feea78381e86179df64b59b421197617a9b2cb1653210c34ca102d7cb0ade0ac347d8f5160fc421178546271f73a9632626045e08682a143058466a428f91a25030affb84fb0525dce41fa541f51d1b71152ef48d0faf509a65a2b9437bc4b408baf7523077f7da7451a0513adc7f7fce79c6fbe775f3bd7012fe99149dfde438a23b9b0098594fc7029d9400ef8432f10d460c7ba809b8877b7b611836e72f5cf7d0323635215e7105a9f4494c2a102ef7e25697f1ea0ae1db8be59fe1cf62d2cff2389cbdbdd71d9c7115f6a51f160d588bbe748044eb7260d6933ab9f4cc9ccfae01df2a58e3f5c6a6809cf02b926af91d6048ebea725f40f148d6b61f6f4d33bf0ad91a15b7f79d91afdf7b44952c0ad462c7cc12f9bbd59b98460f10bc71e642c717bf7c4818e35612691a5538838ee54a19f7ba94f5d68f0dc252e1b40bb6935420b32a0f51551842f7b4cef8344f7f532317621b87c71e23b775e6a94a33ec2546e7bbfc20630861cae066877746b74dff706ede282d0e3e84c263a3d385d145535c245bc08bfb4a88804c06c6b8ab1ff359495151b0bb3727bb7c1e046854ca3c13fcefdcc868978c17f4c571251d50c34c88aa30372da2f9dc87747e632aa7972ddf6ae69f4524de1ab07d18ceb467aab403e899385ab438c70797f22f0ec4805ef9d1baf14738be5349fe0b14bea55e75c57ca18d8bac6d77a9b0975bdf51d73ccd9567ff0f494f9c8f826c65d6ef57165ef2f9c95cd4d9990255821852783fdbdf0d1783edaaf61d69ef6763364fb3ffc0c47c8dbd67fcf708f42b674ecd0bacdc3af99496d84a0134903529178eae76ae56f7dfe205dc202ce911b260dccda24f4e87e4eb269ee0dd34fd0fb2ea7bec9f0fa6cdc34428e91fa11c105ac2a632ced2723119affb0d2047cdb54f3ee219f8f63b9e090948f50e3e29a7e75347720e1e3f8a05ac2d0f1669faa0b14bee01a1e59232c36c2843d552849ab6ed8bd2d55d9dfe8462b3d68c05666c7cef02f4ea2928802e378421ce7ddd8a416828d28bfba82f764d47e80ed107739eb8ab7e915c711101007200502754c607592832dea75ab39e03e50c774286622e19c77de179a0d9da524dd6a0a7ac7d14f2f786ba04c8279a3838824af5d71f4e883899db78a4006f96294d8d6d4a0682e535721459045ce2997a2e9f75fb71591247d5affb139b14cd605f2d97799d6df33d55f8539769589b2bcccd6a758e5d044ca75a8b63beb4b4722c88201b1c26a4f02a135eba146af28e82468d8eeb9173a933840159183129d3252ffb98e9ec8bf4241e5ead46e2074065719a3bddc284dd0bd46aad6fa26853a7e354b0506cf7964e881ae756fa30b8ee59e0446df0e596c4ae65cc8b8bb93c93ce69d4a3ccbc5ac31b41bb0f48d1e1184b901c16ddd917984845b8f175cb55316b2bdca1dd0decac9a67e8c8a4d56f781d9c590adb4da6915839aab167b38a3725ec4265a5376bdd46288d8836c0d371a784a5ded5ed38952823aae90f2c6ad5bc1cb502bc68ca87ef85cff18f1f433379857194d55b0e639494701fb502c6ffa3fbf2c9d5fb55f94aa2a1e3289d8185fd500e527a5a3538dacaa9ef047ffee5f938d29fe79fa108e45683cb7ce463969351da8d154147d3d34d41ea7612be6c1e569f6a97d05d1b6954a03f1813ee875d6db4795d73ef2a65744fbd61297a01d761095343b62458ed39f167adafb438ca4efd1556a055a430c26e5263ea02beaff85f5b20c73662739275d026bbc696cc50d3743f9796b6d404f5886b22a613b83eed9033626eecc575d9dea9c7f3be10dacf894a4fac119d515a461183031d28df1aa94d3fc3012aa24e577508198ea0024bc7557e36b641e2a22f31e397332ea58651e7d1f037d217dd8638f598e9536812f49651a8e91d6b4281ddd8d1837b9416a97ea129dc54c571228102ff7dbb9f1938550acac0ddb3eaa7a62c5cb975a0c483c98e0e5032d879bfa9acdee1131b0cdd471286959fa4805e73ff5499ae2772e398089ac7f9605fdbdbac71d7c60e6b8c6bed0a60a133d6223e39326738644a633c4f08456e76a544f9f594c8d19c9b48f593d66231d873d7e5350473c2eed1028df4c2f7a8248145b722b1b985e62cf8893d60ddb91a74d509c04e156dbb99967c2d663260a3cc41d6d2a7c2ee74689b49dd1f4262979ae8886a5f6f25ecc644e3f1862c812ee5b72855bcb6bf696efa86834243ff6cda160b32658a8893005865a23fe92ef292e4945109314f43844955ab449292db225c0eed3e66f96770ffece0ab1bf28bff8dd3883ea7e556e676f19e9d04c5b3814ab1573d2d4037bbe649308745b5db3f34b0569a257393d249e9aad30e700cd6ad78d01e67c0220af0df4de9cbc7fc8da7c21b26e5baaf719c780a1fbc4eafdf689491557c5e79776fb1fc2accd8e3c8b78c2a8c79a0e02f38b62f4497b0e97e1923d55929f25135381a28980a4e2a65f32309f04e46ef0997ace686b58129f8950067abc56dd17f059c14630a8b1410e0c6d5f071abc2fd300bfecec4a48d318c66bb2e2dd853625b331319ee8da1ff16c55ebaa6bf2e52e89e82f25107d31efefdf590ae8acc3de6fd799b4db2736e1cf95abe78cb10704a541b27ad2207c6a148e3611a79ffbeb1bcc9579c712d567b05924613f79e656519259562f9d2663db73a2936b5adb90f940297f44fb6986d33e2e129aca8b24549fbf90f4f08e20c98520cc0b9abed2278d4b23c88546752168a2dca87a10df14375d10a40e94060ba64659bb2ee4dad95a6c85bf17bde43af1fe3c471c60001f038f70bb25ae0e4f31afed111c46401948106ce80a4e86d0631b0c961e6f23ff1c3967023a4c39314d9611ccf994d83d3aadf36a1c545cae84db91b0e168a518015d32c26e46bf9122c4917aa298c42e8ecb00f96d272b851cb80d6ee2eea9af8af3218fe121163cd1ec65362899c2b96b5422f7e31108ef53278a9b50f36023bae2fe160c2fa998ca465379560bcd6856c1c28572cd754791bb662f34125026639da9f458750c31ab1ef8a00c593797eb8aebfc5cdc1c2559d23e7c9b9e787c240b04703763a51b6ab533e60a0c470714ba03e981f4f7a979ef66f42471c5357e5337356e2f7e6e047063c44e0670a5aa9fd7642dbe15400cb39a461052a1579616d6b51879d5118fa0b5afe0c03d5e8f64f1f5533e35a2e7667b9b8f6b33c8c56d516e248b3bb3683a8cbd4dafd7cfde0cecb73d0edfc1bee80d0036ba772ef8a582a4e917c6eeb00c18f59f965b3f18252cabaf957c649305fd1d51be99963081bc7db27d842d1b8fff6acb562789a84b5c576a45d37717802d60aa64ab5a27584acfe96ddf273ba065442dc3433a7eacfae4402c7cc381b1712b56196f0e0179256578cde143c98c38218921ec46670d3b02d8c71c2d5ffa6f32c16915a9753207b9a2f4e84c2977f02abe931efc8d85979acb509bbf8e93bfe593fb19925657a793e807956f81a9495bb50fd957b3d17b76e9402390d6b4d9ccd688cad2acf9cc517605238acea188a6095e7afde63c8cbd04e0c203ffa8776408f2c876136808af02cfbf990fdd3fa1d3261787425424ed6cf13b04efc8280717755404bb8c0b4c840f63d7c69dec6b7c6e03b8f006c93cf31cd1dc56889930b9fd09d200cbaf4977ff87089369e9dd4e0fd05b4b58bfa00e0cc63ebb45759dd396b2c9ee21849bc1f5a68ed07e7ebb418b994012fc56711e4e15e57ffc090b5bbb11674a22d2509e0caa7e6d0f37e7dd673b6dc84180026edbc62bf419a0638ebcb33dd04fbce3744ee0b67cfb0a443b7d4ee5759ef21fe00981973e8f06c7ed5254fc1015226184198b3ab96f5b6cddcae4954e0f4f94d48962c53380c23e2c9a2f43f996a2c2e77f78f5f8081bd488627e8080920cdf65c168a54f302af218386b03823dbba468da2545107e3d2bdc5bd8471dcedaa2df741e06469b1a4138349dc6652f1067acccf68f0493be9b5de6634b5459d7497b43963940b66c3693b7b49a5b881552c59c9d9ce405a094b16875dbd678f0f46c312da6a41d0827b5e8b59286dc554fb0e67a53b1bd21f4c922fbd0836389eca6f51589337d66e003bd457b8a4df2d2222329a79bf49ab85d56dca220c6e0ee1e03338d9891abffe531b84215bfff6a4ff9e1944dcd1859b079c064c52b810d724cb6f166be96bc6c85c1a2611365f19b95e77ffa3391faeb1f0e2629473578509943918ff93c7563ffa51a37fe5e1d5d1448fe40802fb75420dd3ddce3dfa4afad9821080b74c313b2451f5d2f96718c99fc2b54f3bb2e70a7425e7b8cff31fbd71f1f374d03d56a646be3d3dcafa8845da7533cb33ceaa853de018647b268ca96a8d13a0fc96d3aaee32d8ce9ff0d948a90a8784d42002eb3f4dd568bdf5c05c8a74dcc1c97d202dd26ca2791e8f455d642923459e4cfca2a66cae3d2399df42c6a6aec7c58848248f7e4c5c9b099fc0aba747de94d4fe69656793daeb435512bcc3b1befc1b1253687a3ac320043e7573a324c9863f8a59d7dadb133349a500f5a16b3844965bace3f1aff13fb92cceb902b922cb644d4b763117ccded433565a6cf1a8c1470aa67b7936bb529919f921181719f49d22ac95890603435d17dfd98fd47356ea8f7cc16f5851a0ab960b59c216b96a8ad739fbde61973a2aae5f8bef917516badad722e0148222545815324abca56fe0f27526e415f4c9506a007726d4d38f3dde892ace1245ea8cbd3344f6580a13dbf69daeff96eef897478ceec8c1af315a3f079b9a7b833b1ad143904e8a91b8138b00c02d25537a23aaa0a0258672fca810d9302dc73184bffcce456dbc27868956f590cf441990a16a9c0fb113ad1377f7946c039ff31a8e1bc486f6f15141793e67c07dcf88ae4af05d4e15503aed0738c3d2bf4e630845cee9ebe85f61e0f73c26f754207b5d06e5dcc3f694ea20505e6b27029892d3c91e71eac3ef723c6be1e2c1213e5383517d62c922352f679e5cc242ec3fa37222024b2424975ba647e3b608d79aa19c664560c40a10428ef67597c78c9b71908cf15f040433732f4d955a1d5875aad0719b44fe18dc85edb92fa024796369b46bf0f59749764d5a4d37db9ecc9719ff0a01edaae2e5de0bddfcb4cdc447fe7d56f3583cf85f1a25b0d6415c319f52875fda19e2d988a232fdf9570467054df9b68b4d39d3fa371d955d4d2b3e6c269b6ef758d6c46d5436c72052ef98a1e3b332b24e6d4df800c483f4ec03c35120fe0ce2e410e9d7cb95f265b80b7ea2dc91e5035fd6bd9b5146009aec0aef8d6f0d8139a5d40cc55d4730e4a1c65923c3a45a797a025d52532c34846d74bba43ba5a093168bd070f74826497a5913ad548b3b3a035c9e4fca1c3bbb84d0aab414cbb7083417c1838de763da10abf21100c757ef6260479a230bfdc69955afd1c1a0c21ec63025f5dd89c60ab4f49448d0259871c3169af1f5d58d6975776dd2c401fa6c0e55e5a4b21404dabac88517f0bea084bfd7b9b1b802e848b1346aaab6663ab83a9d9e19bfb1f9420dc47fd8852563bf2ea215df65836cb1a722d40bd0772794536936d8ac5e34137a2c5df94f97a0754b39ead237bd313cc7eace2b7005f402ed72a33668220e0a042358f9929d7d65a252ea5554ddb0c7ca4e273a822a127e5267876e018dfcdb749a6aeaa656676de8f3588eb80f56c9584956340e0c7a4ac1590c9e1dbc70ee7b365b58bda200df7ba5956ad93baf19863d5eb1c54abffa078b91eb02bbedc570dec0debd96e0150f250befc05afdfd122cbc18d593831b317337c5bef3cb5fc2d1ba5bb5075045a777eac9eab75c9b89f21190a275b5323dfe8986bb94db19d76f64f1a81fdb33ac789d85ee987096bb7bc36b97edbc40e31e64e625ea61d04383812484e564a7bbdb734d14f5bdc4e547dd28a4252a9aeca9dc5d633acfc3eeafa51224673776a2665d2b55bba7eeaa877038324f174bda7348e0154fd6cece99746cff0c226eb744bb132f14a23560415e752147756a3d7dd514a00f83c8fda9c86ef0a6f051d7858d71f2253e7d1f7ee45360f6214d97d72c65d396fa235d4a11a6b3e2df23cafc63f26d83f3857823b12ab96a37ed425408d1baa5e47b938676c242f30434d3d41a2857c7d68b1dc0e9f56a714cb1dfa6249d69f1496a1a83052209420d03bcd1e5ff3c7f1818ed00f7b6551906de9b9e296ddae8daf72e5b59dda1a3879dcd6b63f039886d02899ea757b3bf94491f67d83a8f52772bdd8be214d4af00dcb78791be2d2c3702b2e780d2095b1b88c94d23811de415adfd28fe02b47f10917eded70505bdd412fd3b2de6e92c773154972b5df827165bc26f4459f3b72db5283114cadfd7ddbc362273e21602b59df1b747ccdb14b906506d78e63890c8735139789a0250e3e2774a6b85ae18c73ab367ef00e3bcfc99302f1ea6aba0c9779293dfbd47a521260908b6c5c82c8b44ca9fff01a4861fdbab2749bc8e878fb0603563bfd1e6bf2a2066f580b529d09b9369e0f7af2808d91dd147879f5d0ee23da5dbb8c41b82e5516f5706ea18dd0298cfb86825ae0f08bf837a6f468e01dda84bbfef0383b9f0435cd18dfc7f840ef96c403d44047b0c49c177562fcec5747a806cc0ec2c5f0229406594154419deb4311908b7ffad19c2aaccba60af5c1dd65ddb404ac8564c0c5c54cc8c78600d1a401ef3531257813b8510ba4fc98b32b587783c3391362b0e0ec6fa0877289e92ce1b914bb0d9c834b7e1bb3c2a3e08bd9443edec33b8a53228f3ed4f701fb02ee03826fff6733c684349bd28bdfffbb85588a5b4d624a4b0e1ff2b4ea17179746699c8f5fd3a249900ef01a06044c6e11fc2d7684cf8272ec34a3319991ccda43082bb2a55ca54338258353ad02d5c9b214638c5729b3d79c77922713aa56c21f69b29b24f241fa3b6456799da3b1ad9891ec4857b92e157936eb6a5272b6d805c88f9685908d502c1e800ca92bbb0492c5982d48c01421da8acdccf6abc9106b3674e5a1e09e2d1adc65d72e70a6a264ea5891fdf8734544914a193f98f9bd6c8a4850a96ac732b7b176b6b2e65b924465f0ab54bd0b5b3e33c93b2fe40f8c0be891c031000ff277969074fa738e753b0616ab457c09f4abaa29059f02692cce326a44968ba8e103c8ceed6808ee2ee1aae498b13fe3ccdadb18afd057fb3811cf9ee156b52bf7234e77291ecd8ff8009fa265b24b509400557b10262d285c8edcad929459d9b4c93e992ddba70c1ce52e4911460558442f4e87ba14343210660f5109f94618b4e2ab43e2cd4f08d8154d210d02f38c3cc5958b1f0e5fdca2f445ddb71747b9dae8eb17edb12c7eaebf827953a4f538df16d79bcdb84fbf6a0df90a373c7bedfe05d62914f86a47fb046746c4f0ab81d16ec7b423fd4fe129ba6f550de451731f63dd290bd06df431c75b05f052534d2161fadbd199bd69dc6c764755a65ef13a2da73bbcfbd1f982ceac2ab1f2596e19f78c0210c9eb88b951be9adf96930bbf925088ec3f52a2df5a9fd31c32fcfee7549a0fa42de53c160e72822b89908ab0eca1abcea7a076f9bb3cfbabd57f8d6470fe347959a5cb7f7b504d258cafe430a0193af72b39671f2760faceaaff61f8319fc8881a54daae284f64e0da781333c1091030bc34340bad790272e618895e4548301b85f88f3fccf17cad32ae5f04bf1b9523f15f987aee28a781d78a92fc44b318d1e4cf8ff9a14adfa895cf1292e2157cfe6de25d72f5a69c16b2f12e222d794ef8986f00a4a3cde4d1e504ede05d97c5bcf1c7dd39dcfc617c3b70695926f9f1dcadf4b71fda03b4b38bf65aa710b6fd302495cbc198f13ca747289204a62046b139786289e8b3683e3cbea0a9d50bbef9467f990e6affad61bce6f8a2ec995c61b5eb026d3120fa362e928a0829ea3c8d37392f7e88afad91e675c1074f1970ef920d8b68d5d17c6bb149ed95f3c0450a8b86003f6510778210441d303b59003f384246e3a34bce912cc4d60dc481bc9ba9de843a61ba48820a5b99f0fddaa7f37f8a7da9e0aed06628127c0bf62dfe47d4177dcce4f944813d4c00b9dfe6810f1a16738c1c60d3027c4891b9cb9776cf19f2f02e27e451d9c50c489609ddf51b4427138901bdb0474c71e2c4323f25272a6589915e371a0ffc37c93e91f84c4bc22af5ce1c3ac07d51a4e7aeb65385becd83524ec02203ef760c86bd150571c9ff3f951531926644748aab84c49005df29bf72688c44cb5e6663bce78b5684a2a0c24d52656f60a95ea520752e0ca6c357095cf1d7cf613becec3de78cc340d7b036aadc0f1b9f3be2e692f8eb57c1e0f77c9e084e8e78ff57bcf2cb1db4efa3de227279095dd4a7c2f3af73aee48d0d21cc971f5999aa50a7396919c3e29e2fcf9c46299cb646d5133006f394466f1632d3879df1d863a765719ed87fba6f5cc66ac2116b6e8dc7ed28892036a0a6f3ede4ed6d792fa0d85cadd69bd49820a90b4753f98a1bf8efbb19589caa8d6f03a490521e37758fca34ecbb55cb6ef17c2f9e4c1e997ff45686ef059df80f12d37fb7422fa1ee0b6c229735680002a35a6f5364de41cac6c28c0b430c2297a53188eaeb7217cf1b5bf031b16101b90700b53ab1b31a885f458a25f0d44124edfd1c3c2251ca930903665644ffdfb49aa68d1973acd03b9221d7b6f575b66a491d9377a8d8afe714117737d319e341716f238eddfdd7948ef3b6ee41e9c037eee97ed7c395223b6c3dd78661fd47016eadfdde38c675eb60e132a512ddc16e9dfeef66ed876b7b4cf62cf995f9c622187f51387f29372aab1dec4fcd4a09672aabb1656ff71ee723742d3253add209bc4b08fb26ca879b92f1fc68e8269440d77a4bab6dfe085c18c92fd1457fd3f3b8738f05f238faf17b236caa76d583e033dafc31443be396527b0ef0c6def2c4bb733d284002047e71b86f7c2c7fbc97ac78364838733047d89336bad3c69717769ea792ffb804488508359e474134a600e5a0a44fe6493d6dc2a0f6ec1d56c7bca8358e8179a597b4de27f7202421f54bba86f6d5eb5a79bb08166f0a0ab2f9b952dc37f1639baa94ee98f8652bbc78cb857666d242087da5ce5e80030b9f79ab911861665de87e86d65151758275499b4b65b5d7f8cfec01097c77572dd01e1394f590bbbff4a135c95fbfb58ddba193bd6a6a41e3fdc75894d40a03a88b969ea79c9aa561b4b0da3a44d8d2caab7b2e21b576f61dd7137346ae3d9c09c7e2cf55939904f5e6296dd702414e7f8b4531b78dc54dd101a905d5ab2d57acfde26104d5b94a24cfcc38b526e6b8ad9345cae7ec6bb8a2c4ee8f583c56f4f04e22588b539529c5c184d6ff64d92933af8fe47aca16073568ed64027ede8063a9faccbc5fd18047751554c76bd8af1b060a40dec17518ee39d40ef96125737c3304f1ba58aa1544accf106450d7fa59aa2b2d4f5433d0072510729a9407de6f2d3e8b5e4cef21c75d094ad6fe7af16a809216654e4661bbb2d91488ffbce5c52000b8cad7ac553d3e5b147083f11bc4569b03f591a2e7e207156234b833b4fc36a92add234e47b599fb8aa2865d7da16f632f1f19901f613ced0498f2dee6c54d9be095417f2ac658627ae3b6595baa669455c89c8bdf017a6360656bf6889c9687cd668ac6f982bd1b6fc06062101189036e730b99cf3dfa330b35ca4eb6d04d098f9b40d069e4dd150d2bcf3ef1d007ca8760d72a4a33c4bc0842ffc162794881e50f633369cc6a0fe528c8cbc637cea6d2cc95ebafc6f458979852a9fd905068e6758a675f47eaceee29bade392cfee411aed40e4e3bf7a8695ae387765e113db1f18db551f982844c4da8379dc3c42bed56f028fcfef8218eb3d0677cc0c465293e01f481c7987304142716b15240e616466c683bddf09f545387d322a4a312d3e7394438d6b36ba6a8dfb2968b2f102006fbea1672ed8c2318fb33c3a792fffc1c362321b73c90a5dee8956de62bf8f76f113cd19b302c6c96c268f7a16d69a8be66862484a2c11e697aea4a11aa2f2fbd931a2592861b4fd362746bee092c67ea204e08458413d2dc96a3d1243f6c6786a1f19bdc44ffb951562d4b5d050d9a58483a5496b56eef59ed396f017d10003e855f268bba3c06056c54eb13978da21558e51e1d370939dea2675ab0d5ea3e16c144e2310e3ce18f6bc24406120a4866339b8c310a308ebeeccce0f44ed893155c2c8f37a1990c9ed234109b5d923be3a81e5129ac1f42d5ae79ea4d1b4ee73ac20c323d27f9fd7473567fb7f6a142dc976e3502f18a902e3b61f9678c14b969f6b561f7b934914ddad7ee20dacf9dfa5e07e2792d7fac5389fd9581fb587aa3a51a68bed406b62e51494a8f4fc6331488ca156f9bbb58e600d584dcdc2451b10ff4d46ff505bf21ad70d3cbd530ab625a4b9229223bdcd7ae26171fae9e676848c7551ed43a5c9b668aaf7b9922dc19d215682f0d8beb1399fd66a89d754d70319e08b620987a40130f4c60d7090d921d3389ece63fac7df422dfbfcda973b454f0a4303ae09fc5dfced9bc3793a26f936bc5c906f5727cc9a9af5de194073bb5488de7722f5cf03b55695591afffc8900315c4aed03df66f6e2047372151f4a15953ade885cdc2c66ed261b7d8ec874dba713f1efcce167c85e3e5fd02af4ba7d0c7aaac71f3f64df7124142f3111abcc4b920d81ca388444a021f11dce6fc6785514e922b27c89b3ac08e9324481a6b3a7a195cfd74d68eff1d62f2a6890cd4a16a92cbdc9f55e171f044803545b7e7dcf420855d9ffa41f9281a516e071ff305f786dd9495e9be5c318c6fce0856f68e90be15ff68b094e2f1e17809abf55cad5a7a5ab72cc45001d7b4a228c08aa1f78bfc1d7e60868e91cd764cb334ae29cd7c7876be3e3119328eef342d6930dc1991d0ebd620ff35231fef4679abb8ba2876ef59643abb1d1f7d4a1cc36eaefe923d8a3a0a676cf496c68bad6778203e24bb277d1edd27c8bd34d4c0a6a69a4ba12be45e2ad6051c76b6accfe7a8cb8740d4d813d36c777dc96ff696eb680f4b1870b88423ca5929229ab19d1793411cf58eabde9b2059c5010e269f886bb611513121af2470c5004b761d53babaaa71f482089ff6a047c4bdab3c666f8b3ddb9dda7dc2389dfc2ebfdfb8253387a74d1dd03b8236004ecc494e5ca7c36c2dd5e1fff58bd3453f43d9ea73aa05e3dddcf17c2af6d49b08cee6fca18dcb2fac2e0c7a24c6b2f3978c99810b5282b0c39c6d56849271143a6916bdd76027b8eb6a1fb1d0745e27b31e4960deb9129694b099a9bf681df094ea77bd98d1be17d225ec74f0a7fb13bdadce08ee58da6d202df41267dd308c71713477463baba501b9fc0f5397e3d641003f2fc53c5551c8f27a020007c85179c66b3ad7e54e83d5ae7bd1fa0814a5c235c797c1898981d12925ba12235ae644a696df354ef3d78b5a45ede7645167519970938e9b8e30ac68dba9dda48480c098319b09cf4160575c513c873f8ae2e2dfdf5b5210adeb323c63e67dc0f6fe5ab70cb2e6fa145f71d21ed4762f11bdd97eab2fbbd644bd6af087e7a80fe523f7f798065d36243d96526b677158184efdd7d31efd968ef33b063d03f71236361dd813f0b9f1841cd313e92009879fa073f4ed84f9cd5615b3dc5169c345ebe7872df3b48850f19a497a21ee28182a70a3a8d9a70d906f0858c3099509ffd34a22c978282387fe36cc3b4b81c74b85dd5153d0fd3b17c83617d36be8f903958a7f8c39c59270d06dc4c915b1f213cc2ff3e6fba99abaa87ef283d7700b6c2e86faf454245d02dad19b80d3477cba2c1b411d6cb4cbf83ef30a844dbefb59f05330c65c635c6b7aa98057d273ccd51467e10cab5cf4dc8b1928de1d6241727d6178104d76877a9b1e048f25ab6c06ec2889aa876884737a3e6d258aea612271f438c4376a651921b6197b2bfcf3c415a5482a764205ed21181f4c2a5cd3e2b75c85366964b6fc4fe0e891b5948a344be674c29faea561b43f1c7a76dfcffca57a839098fcee9f4b5a90f35db0b7e7bb8c16a8272246b3ca2a9e8cc0e868fc793d51fb5a7dc8ec998fcccbb355312aa8b3a31940aab0aa7207afdda92b311a1f7aa3d5e423aef83fbc34245ff0a8e1bb66ec585b8231c5a20c509d2c420e550e4026cc0361b0fafc00d88b9ac48cfcfc8bfc9de32037de2a7cdb108f3cdff57c6960300b32ff2c7b207d18bf01b8a6cd33e5528877bd01bd17dd5921cbbede2d39c6c552e6ead8b38c8d74a2c27efb2aee87803adf8f943f9fcb328c1b8b359ce3a79226e9fdefc56a1b257f2c3ee79b414fc145604440fd0398ac1f75156b6436e482da765b109c2243dc06193871812c0f765cc4f833e8d036601f36bcc6fefc444d5d03a616b0fe3e1a6ace4eea351d3020f143fb87edbb4be354652f82af32ed676786ffe1fabc9dafe655e8dad4eb03360bad41fed552a4a04d0588294d4b49dde8696a04caa6d9f76ed726b568d9f4e4ad2c0c75b129e3a6539ae22cad3e6f93fa6251e643d3a337fb65ab97df8c65a6ee6ece62d65800961f2bbcfa40ae644c50fcc57bfa873d66d8905d5f2b2e6dfc9d9415e28589ef4f960bb7ca6d8cf80178e94ea8286fde1ace2aefc4bfb0ecda8508dc1aef8dfc5e5c817d58d6b98a6a00a3620ead56381ca8f02bed6ce885d2bc4410d489d7ae35fad13eff5f54c62dcb35384e2c67a920337b40704a6089bd5f2d2dddf5ef77208d7bfa195d3ce5222eae28f59ee47a4301778d329fe30b62ff6f5511ae4a51a70fb593d936f5435e0fdfe591fd9a81238c87b269c838ac5d21b8cae2b80c72f6576dbc72ded8017a00bba557fca51acfec4bc1b935ec30d346b6e8206603fe72dec536ce171e1ed3249794973d8ed454ebaf9838c60df88edcc15b49e6b721e7e2591b7d24773636c3b3906e3cd1eecd2abe27fdd4cd738693d4865f3557acb6484b61c6394b09d14e1f4fc2cc5112eb91fff8c0a19d31dd01b85fd36ad3d4341b9e6cd7d55798268ff6513cd448823cea43b80fe317a987136ba91cf6013591f2c0fbf5288c34d93f2687ab66a373e68c0a6ef78920f24df61d484ae67e273ea7cb1592e554a0dbb815e68496a535af00e4a3399217eb2071dc245a5103e816a053e0c65ceab3a34119190dc2639a20ed868e5dee81baf5b53113d8686ce09aa47a2b24e0ede912328cbc196770749b40c7475b5d245f62eb4175534e9fb1192e69d2ec3944ba3558ac6613a1c02f51315ef49c7566d1d0c2af02db9461e93e09d60db6c6c864d821d605230de883448c684d0a49d8bd53d189c7dea6272f60063dde92cdb5a26e98fdd02ff8ba6246fa68e7e562c2c437b7a6caad30c93f67ab85ae095ef93d72a5745b2539a4b01dc7432e90ad4631bbeef48a2fdeb0cef8ee74a2d0b308e3abf23c30f9bddc01de0ab75379dc23e2f83364127c516091e8aca8817fbf6cbc1d7cf7cd0c2de6e59e9f36b9ccdbee6170559d55efb4c8e09b87d6efceaaf1b71a6646e349cfba4cebbcaf4f23010dcd1e2d1888ccac230201d515081d65df65651e6e1aa161d1cd3f17af5311bbd23b3dfa8da2bb1b5c786bdea0f80882791ecc3af94e04d3603d05339d8d77298923bb41603cf0977832c2c1724f0aab9c73e7b4c3e8ae378be9a5f60d3f9a44dd0b964147c9b462f37e14826fe90bdcb87b2fb8922ad21788457d23e927b07a77ca82e41091f51ace472d3892a92ffe6a909ca27d8413eb6465efe07f0c072af0fc4a02076a8077037c940da73ebe34d0dcafa71d81d39892514d883ec5d16f8d3a930381677a56d6c299a923064c07e1172a66d4fdc6f86df736de364d7e07c68760fb52a4c59ebf5b88b3d78e3fdaa6df0680f77e0b5a75a96b8aefeb57ee680f91e7cc62ee0389b6a3cbd332e249d0c4ea4a545f2cc6901adaed047df7bf4e5a06837135a16b7277e548c31d86e3316be4f74791274334bea8c3c15a0d497bffcd672ececaeb26baf76aa8a76942e61d9e003f641cd9a38cf5f1c89488c9c580697220a8a69cf4b993caaecc8044f508d3c5fa1e4d04c0d81af97377a65f6537bbc26827ee826b084e8a7949ee4cb82e028d528ebb19a9d1950dcec6d18e24eb1954bac081635e84aba2dc6308e72aad637e35abb56c10898ab6fa7e2d28f1936b48d562ad4ab4cf5331becc0982abc0cc115f409951d8e23bc192fa664e69098417251c9cb09f80e05b91a5c4b2828b3e8e7dda02b2c1847db66c519c23d05f9e98befa14a0e71898d04288eb1b235eafc9856f29e89c98bd84979e5f2fe5e3790db4ae5b741fee4eb5ffa98b81512ec135fe4769f10055f9082ea21c9d78b082c11c7f3da4395f1d9b6b3b65635cc885e6bbd6a8f563efe2bc9949a0bec5ebe15d40f97ba9768be54dbcc1d90e92b2aed58c82cc809633ebb385f03d4b6f1e432d1ebbf228735c69311e99257ce8bdd2f5151a843514586bc7d0b3bba422c534ad509524b3e5fb7e96d4679c8b73f6f03be67c0d02af646c3e4c61464a675cf96f89aa6d781e126cbecff3360c9771f1e0f5f45e433f466d7bc6f4fe98bbd79a5927cfdd091bcd25cfa0441369ab163e66f57c147b62856039edd7a1897e9f9a5059d31fd9bcff834d25b896b6a9603dad3fefb0eab5afa2c94b29afe4f5d05af712057ce62f62cce3c921c5ebb9e4e85ddfd03cc2a981f3beec46da1ca238e28d06e0a8ae6bc697ac98d7f35e362f74e7d3cc09f27b412febf6d95357f0d2fbc6dcfabf84709fee31da57e3a879d41d1f48822a251a2483aeacfbf89373631ef5e02a5c42d006111e2e7b76caa745394b4194a3a29bf9010587b0c1c519b7b6df87c7cd9f8e9a8e66a8a4b1f2afd07339683702825d3b58ed0af5893933d0d299732def28d70186ca851e8ec58a8d0f985f7e05ff4e444908a97e19849f7e455322a9a421242a2ef8936d283a095b6221c6587cfd566353a40bb2fa1ab8b75ba0ff5629b1a4bb86b13b478161267b6384ec21c911885fb69ec1cd6ccfa75ffc7e5f610efbed93ce63b62ee4f0304aa4dce6736c6fb7626d1fd7384a2f0ba19884232a422373b3a84b8fafeb0912b62ad95229f8b9fa0fb1c3c147dec06d3f33236f2b33426eb8a25ea8714814cdbf3e0b28e0d738190e6b988d9ce5ca4459928d22d517664e19135939223f4e86efda0800efd2ef91ce5b6d4eb63efeb23ea1711e8d6af85f4cc85f0ca1bc33f00ab06b14c7150da416e7b4365f2c6199df7c33dbadc308bccf5c4af5df7a60968ed91e76d3b176538fc9125e80a3b1d872eaaca4474a2b13a3fb70d76f4d378fd9c4e1eb3081427bd83aef58752803e2c92480be459670de706b95d73d6b24330a18d1f678c6f89a0668a3abec8d470dd3a4042de381fe89f067d311b9cadc1119721711820a955d025485cbdf306792b7891abe1c5460372aa6bad40a780c19f54d784cbb5a0b97d2ce881a713506d78089e6f4f7d073737fbaf339f9691fc49373a0f1df36fac6e26cff6a790a009e59c11aaea863c0953a146a5248cab94eed69cdca64c876e4479ce531b852bbac8a2669d7e0c4807f3bed476cc879c26aba6f0e71ac400c516289aae1325c3a196209bff6ff4a29de8ca9db9d23788fbcefc0a0d860733415ab0abce9ee0db2e99fb8f90d1d6063924e223415e26a170d840ccd3e438dbc5352fec4d91c17de665d62e4ca947e2f849e0af26deb0f07bd72d28d67ce2cb7b6db291e5f9df809a3e0f7c4e806010f088bcf56af435afd62e095e6294346031d052b267ec9b21afc30aae5d04474075ff0329438592a9dcc71932aff5295fbf342a353ca2064739580caedb3c72494fd63982d5030f31ce01bc7c2586cf2521dc6c40e539c9007ecadfb987d2f6e9facc4ce42b515328a8b4dfd715fe9b9cdb8f792e3c16f6035ca20a0e51541fe30478708df345b194960703efbe7b737adeb91907911d237054a8698f7dc9979b2a38394168c50ff9fc8155ed744a2b00da386d8c7be29d6e978eb6ebcce3886d28d2e03bfecb77d6b2a629622b86fc3a7b50f135035263b404fefb38cf4c5b2435310dc16fa42a4f1f767391bc2cbd3e75166194e9c6bd354220dff6ce5e05af08a72de1861a061085cff6ae55c944162a3567d42cda0d23a12e0597809cf790f2ac057db6f678b01f1900eb00e326eaa05e809ba74a76443bf6bf68cf698b84ef598d7a5a3236d810842a5265228414025ed652a7a6b17e3a5f7dce4a28905889651ff9cc0f6ac29359af9a07bf0b42b953e3c5ddba38a304d771dcbb78f5463fb6da8f8f2597a05fdfe3a5b448666a2b961cd3d2d1f908a5add24fafb23952aab40bc7451d9f16b16131c41d2f25d6eab4f57617451eedd517aee6a3f7e60694a265d7a7ec431544de51ae8c0f18936d3da567599ab1b2c362abbe7fbdd72d3508456d058c5e8df2d9d6e1552250a2983b006646587769a1ae1d3a2e41511dbeaf5dbc5c5d947a989b5cca9c81502b49c987fc611b3070bf9a233bf165ef3e4a9b174c423e6cfa382336435bbcfe686ec1334631cb41c0f74e3bbf15bb2da6603f93c3d20fdefd7b05d0f20a8da8cd1d1acb4013b01ac88f7cc15b1345c80a0e17280adfb6c12cac4e40077215ddb734f1268525b7fe4a36dfa2265e849f9e0c94ea41d3f5330990c05922586ed5d0928abf8bca35f48b20a506f5d641d5c423165c5cd354fd28631059b2601f2c863a6d306ca7425abe7387684874db16e701db9632f40844678a3368793a5d295ca3e2bb5a8d1b3087afac18d8268a10a2aec517807c9731eaa8b311460a8e5d4972a10119e2874a2c7328b2f9ddd349fac0da7b02cf4356c2bd42168b020c2f70bd259c9dcd02da82e6897964f9ba8cc63d18b992d33bc348c87cb184aad457774515fb92633919b2410e0252407d7d44798fa14cad2485a1476e7c87d6a10dc4703db9e8056b38436c8ed467c8c7f3377199c0bf24227871cce1c1e5b417ccee28888d2e061911d4443fd347db08cb8fa9d269962982b49858a5d0e4878dafe73b3917d453d7cc09b5f7871a7933e9f5b40ef2b176cf89d21e4749f596e09ca8b42d33a95e44b5030a759af0fbf73923c5bfc98f3733ab7f389c2454f96d833c67bdf64cda38f389e26d027af15a2a56565cff5f07f7c75aa150733b94fa03a37aa812dc5d59bf6dc1f9429631c001d0720b996192b2f5b3ab70de5bc51d96ab8518c27d086f9693005bc988de8bf77c4042c22e9fc9c55339473ac09a5f3aa8af3354b8ebb709d1ff440a32bcba16f8ba5dfbea910c9710d93eff0d18427fe63e833ff774e271fcf6b5821456a2ff2eafe40a3b892058129288ca98c6a5e4026c6540dbf7d90b6ff43c942b83e5531abf154dbb622868b0e02e2073d539df2586aa19e5386cb48bf2800700958b208fa6defff8b4ee149a527890511b23a58449038366292ed215a6f57a7bbc0fbf98b88b8833f2918745ca9e29af7b8f0b881c6d768d9589a5b1641aa8cc60437360a5cf576226e5200fc00ab7a14b477fd16ee53432f34e599e598d9e37718afadadfac299caea43574b663a584f1e2486f86d9d0e2e2f14aa43c44ceeba1dfe296dba102e2ce353ff7fd1e89973ccdc0b57170bdb4218b7a28422ee9966ca9ee493e4959abe30f6944ed965759684d32d0eb68b122f2f0cc51371d6dea3a468c2c4790a4d62c5296873384396f2e0729e12b2974d898a82eec19a50e389c0d10396ccd08e39d613f28e9fe0b8b6e38d62b016087b0b44f2b2e609fcd255fcdf28e5fd13684883969561229ce34300d94456d3cfb480b11061fdb2b64e2f313ca3748d5d7ab1d1385cf9e7d2efe455e141287b0b75092e3ea4274fce6b362d923aa1ddd14293f308833c66a5dd08d0c54d810234c09957cf898aa2c2fb042b61dbdb797ec24f38bb3201a6e7ae0bed560bc1e37d6b44aac65b4e407ab0e44a4ab2effba2579e223f4a50bf7b50c83abfaea2769c267385a10035b46797d1bb137a14f30a9fa5bff74fe1429b144ab9eb865478ff9e1aa0f21583073988b1680dafbb818097b4e0adb0c5917801a360cd6e1c80baad2e590204e114a6eb9b2d239a2e41dad754e5cac6628cd061e1d1e2894ef4a3aff1816feba145730c131fa9cefa03b2585c9b6325fae2f085bac1139c5768f21385e3499aba4434240ba7451ef114d587e4833fb9202bfb1993e0dbc995a56471483f94f5951b46766fc1868c75eacb7fce45ff30ab05b94c299a352c43164fbc3c93799267c6ddf6b30662d12e32395b8d123c8d8ba05eb3fa2d8f082e2803228d8d7b5aedeb1c6de0eff8769385dd594dfa8b210ab5971839e00f5b60d4c25ab9c848ab66a4f7f5e8004ce4fe429b3a57b1d1ce0b25fd95fae0bec917442c11fd689851a30bd8ff05cccb0c2e42fbdeec893a967cd966f17cbb71a3df1d8776f7b32da34b6f4e8a8990840c541de1661429a1b71c4b6296ad8b6f6c612c55c69a78b91ca6ffded63254f6fb46db3244755dbc5144212d34b2e9d0c34f9f3b091c043c37f13a090ef24f170ee0e00fd06bdc6d69d2963c8e7c335b752e1a372ffe6bb528cdb4ff3724d55f765575a3d0c3c7d91663a74790392583ac456e2c80c94084949cd716b2dac4e2af056f9be0672e79673b419b47d1943e484514249813096167e429588a9d17fb7c58b799af1ef3c0b87310c4dce5b21b7c7cea8b96c76825d104006bb9e58887c51db0f734e225725e298190e2fa551f1bb5a78f7deca756ea5909951745a2bc5497fdb1f4334bfd32b352b1a6beb4b30ea2d647b920d29eedb86dfe40241c5db70593637728088c976360f6dfcdfdcec13f9ab29fc50f25e23d664fe00727982bb07940a52a3e6f3bdb60d239625924351be087b9c258ba496b275a3dc6bfcc741f27903265ecc5ade5c9045f621a78693d4a427f19046608764bbd978d50e4123f540eb5bf1384e74640a38c40533b19099921976a5fd7d46c2d37370cf0345c122abda8a36f023241ffe285f8d87181e07b1341cf49522322412b7c78d5e5765c2b44f63c3bfd2f2148c10e1ae3c6a5dc34dffdb1c52864c9bf8a13261bafef6384ef05c2bae7478026d6ce358e6ccb8ac1fde0145894f6dfa1cb36701e154e6eb8277092e28b4876d0c807fbb37961b0ee259e2ec623beb4da8b7a6eb2b80f87fd633485ec36a784a01e19e4cb98ee44f7b328ba84449f1c75b156905bb508af2e1a91ebebebb5cd2306a313d8270e5467cefb313500ce840d58e51bf23e33a8300667f54b45be9bdc5083001e43c8873ed8df436f2c49bca3d569caa85cb045743acd4221430a62b6020d6ac11466204d861c70a662e2f2d0f3972aecb3c34dd3ce7fd31f17a3d0e24b4f87d97cee8a24a3a7bef302a7d07c8c925d278893a8b6ff9cde140a940d28d5a607817976604d8517862ad783a1e49c17fd5c49c9a1c2f336e02b307c32333ab9380e86ece6d36595ee1c1452831056f5cb5e549b7f7f195b0b3c6ba7dfda0b6a65a5fcf71f984e62277a2fa6401bc8eaa48f36bbba84c062d3d6e5f51272e36c69027b9865b3be954c998c3dbcb13d6e16e26f247551196b1de63e0fe2be83d2381b8b6359d09e4cfc304f66df8cbb007ea68b42bb58b59c3961ef71f5aacd7c474ee4d0f92b9135b684312adbb8ed32efb6edce9ef187682917001f9315af925f4e49beaa79237b0c10ae3e0e413ff4fa6b709e05671ea0209cdcdbd4d9300398dad7a8fdb6e3ed6db3cc176d3cd1ea05f58ffc5e08c95c81bfc4502f7c62e942fea009d149c306f0d2042939e1ea4c35464e429926d9a43666d3f80535bcf2c9caa61a62941db5a14748c464424be047d2412aa6cb0776fcc715f264a31c8d18f68cd42725c70dad34561d99c2f140878072c2f1be32a3bf2a49e5d95e3adefc354332e6ad3bf4f197349fe3a2d2e42c5cbae6ade9194b1f2595de452016c1e7a0d1421d68debaccba4a210c57e69bcb01ae3909681e4aa45d8b032bf2502b6fe7439e8455c61d68ab2bfd4af5853d5f071fd9e58f67b543c06e69be6647bb6bae823fa244b0a7e54d923785caffdfe34bee0619359620d88e99812bb96f2b3dc4595b1aefedc13c00d4b300a537b8cb992452ca66cb6c120e3d8ae18e08ab9c0fd34d114f3b4854c8102c48f67b9dfd63692dd1ab2036c88473b520470c30ab28a7311952096b2805815b22d80fa81ca325c754fb8b03cb933bcbb1c311e6a376c16d3ebff8494efe82fb9bae5511f563052146d9d7e823a96a0a9de7ea41d9f53aab459b3d3fcc2e7f0adb64a48970a9ba235c9773f23e8c48a46fcbaf7f85b79ffa8d4c166410095bdc8158ed92610b2fde14d400e04f5c22df4d3da266538ba6d22595fc8bc2d684fbfac88d192c807244bdffb075b9e94d79e9f019c049227c9e57e578caf80d3da32118eda1b6d5063ae5d8c24a5cbd3f987df7d18af8cd89ba3198125c1c154bbfa1b6266e869535dd58d8c9969a8961ba86e6145870c38ed1b7cc10127e97b138ff0445239dceb2fdd1601daea71bc2a6428311d8224fdd37ac9a85e5e3c2eecfa19e8d8dedd1853189a016492593f006cbbc6f8ac6b7026e62e31f7dd6d28288e7c3f503a624f78fd39cc0056d76d507b6ac6a4a8016bc06dc8afee295c7bbc6f1ef1ca7b44b1c4e964aeb05304376effd0b0e2540289f8f32d1b7b26562b1f740a7552c731e079f02c7d00c6b991dd3f4db18680a785f8cf7f3613e191e8ac071f95100bd4d55a2ec7aa88acb3f689ddcb7213bcde3a1b9b63e2751aa0e3cc761285d51bdfcfe95735005f2e154d135a669a08c8fe987aae4d4e39775d8b503f8d2a198213d13fa6824544f9fbdb15276921bd633435996c2f0649f63b500bec875ca7c8bd91d5d6a2467a1c82c0ec229c49d1930716444a930a845e3cb93b7538b33822487249014e035b85066ae39fd6241958db60019c897f546fa1ee1fafb7740925e4a7e49c501ccbfe3b3cb35699a324e4bdd317896c9d9c273fc4132fa66ee95b95430bcca480b982b0f531fa596ab92b1f7b7e6c666f8c616d2a9f29c38271b51abddd8b1154ef2c9857df208c76bc8ae252d89fc7a79f45935fb10dcc52893623ee591d098d82e1fb1bf311cc7ddbbdc1062a803026e3696890df49bca1d7713561d5bf3d514742fc24a7eb6203069580f1c36fedf5fd1016ab57a30098fd9ca2e40e4bc29177e1fbf23978807308df9be2b380a345d2728376785c500fab68377f1e242baa19bf326a7d2864d4b335b3e79f0ba7446f4c113db263680dd2d0ebbfa59ec39bde03ead4bd38df36180d7f0c9605f5f7a5c9c874f1ade91cbb740a0e09fc13c59e2fe2638d180c4741f8d5a90a860ed0f268e7b7f3227590448dcfc9c3cfb1ac8abbd76b6ab7ec25209faf65e93b04fe5374b81456285a867db72079bc2cf69492bac6cfba2dd8ca63ee7ff11b39e24e461a56eacbc91af83cd6f884054b6025e356fd370eef4b4f1719991149ad66e491812c6c3c82850020abd38f4861287ca228311486a6920d91c7938135c2d80ffbf28d4c1a76e5883d04918662a8b20c18395a52e161d9e3037ce96bf8311f408a630c9b68b53492d8507ce1a8f5da208ee4141f2f4e5118155e88954a814761c876fabacbf1a113aa30879e1df0d3a2fa60f3466c5baf2ea696f843999cca59a2776d34aa59e80612880ef67c201f39f27be35557e0678fed12194def287a187ad5dd82ae6cb61fc7f3117c3d1856ac6c60e828cc725397dd8466179161d5cfe886288abf6ade827eb24b23c50a697bfd8e8316d1b1939d9b5942f90b49dcfcb713ccffaccc4bbc5fda06b35027ffe48f5719a574722073d4d34f4e1e0df05f25a1671de2679f22fbca653a3c0f5fa74d32e40cc69ff7e752e3936cf990334485e7e9db11e80bf158fd57abb23acd253e879cf8d393a548a34c9b7623f2161f4339ea263cd0a557177109faf4485822079c49075bd0008e7eb55ee2daf66679681d69ef2fee35b222a5c2613013d2ffdb46db7717d380eb38b524e0a8119a2065fb018c0078f53bff349d3d7cec8de3b714926721a1017166d486dbf0cab0d9eba22fc148bdb4934cb909da5e3324ed59b2eb54b3d4ee054ba51d0599cf5cf243d380e3e2e1e5849831b36594f430cdb4b516a320ef20fbdcdad8851d8cc96127ad535997d4e8b2986fda048e6b4c94a21cba3e8673462c153f431554a90fb599da2d457a7faf3e4d5c7ac484a8c3d4840782519b14a9a464f49b158cb46019d20d42dde53d86534bac78c7a2572eda26740e5fd386dd532ec27cfe0361bd0cef867d0e6f830226a846cc861c43bb8a34e2af77b7b9967beedf93ae1e52e015875272e19131c49e0bdf25b26bc7cc9439abe552bebb2e790830eb0eee6208abbe11986281ce1508826f3a19d91ae5605a95ff8ca80836b05ae75f479d4b3519dd622aa0d527085a9378952e6c512a7b30a847c8a3aa054cdbf87a467ec567e84f0256df015d1f7b49d71523096a7e0abaf85df0fad184a0e99144c5400214cbdd7a356bc9c69a980aaabb8aab85b23bef6990b8a1fc1e6c74e8db201082184ee0ef447b3f4a461c3a4d46285bdff9df09669783b305b65fcc2412fe49380bc647c1d00a8a5b97155f2572de15a90dad119778dede9c9339e44049b653e0ca5a6578c8de6c47cc32763e2244dffdedc2ac70b36a3e7769a7c8a8be046085494a2f5a00dc495c650f09158bc1b9305159b48088ceb90ef74ca3e5275e3894bda1cf246deb16b404a165e8dfe9e08a2bb302d2d7ee39d6d45df5298e20795fcabea2298df4b9755e33faaf8cc86854dfbb6c5e9746b7c6b40c3b506189f4fccc6ddf72828e5eeef234e68cf1881c2b3218b7294b008052d4790d547dced9de7d1fed5e76cbbf0ebbae44dfc55bd5b5abf18faed25ceec40ed448d504210b0c37b83df1ff30812caf8a394527f792d89e0fce829cfbf1ad52be2061a68464fa79af7f84ef9b32ac7bf1ccd6b46e1b94d4497a6f9889a23b2403bd722598b2528656fbd7d8186611efe94802c409b3fc7cab1e6433024cadd5708fe314ce71d0d24a75965974ba8ce15e2705b4bbf8a72fafe7fca3b3867e9f01d2e8d9c1c716becd6ccbcba20c4c3382315378735bd2c4f3c7000820c93e8ef525a45419e264e9ddc5d8281adfa526d5ceaf19bf8bb26baf3a6f4aac90bfe2dc398928ebed9db03ade9a87d3e2adc56c86ea4873c410b823b918ba5018132c8d5d4748998bfaf83bfea60c51073e3a73605c43e5bdeb5398cab3006d4b625e7c294b0f62cf14544d5e5d0a47d7169f90022ed613a1c695458cb937b3574b6c00ac4e681c0c11ab9bdbc45b5cb674175d837623298740239c661140c25b4986582894ee9235a7222e9a8b73e3a1740396f72495f2a9ae8543f8911a82af3a1fe635c3572814e22ce5d9cafa9b5fb29872aff458629cf1a233bbc3edc37dc2f97ec9e2cc1c76162bfff773c7fc90d3c91e4ce7e6e888311e374665b604edbaf017d8a1badff93c7815b43bc8cdf93e65a5cb4fe5ad5623ce9e10cf731d28b37fe0aee69cd76e7eb20a0671f147cd5b11c70ed9fd510414c57bdc71a86400ef5f21e2219eeaeca89f19a2668dbc945733f2552f124b3b046e765957d3fc87168d9622460a720d095bcdeb2a23b8bbf80e9a5a70be22bdc642adbb700ad560f2384abbdc23fc01a95808625b7bccac8fea5bafbf5f8fc47aceb9a20646fa25f78cf925ee475d52747e1c8762ccdc1a076c544736ad6b30b12887dbeb12da2c30cf43c413a7bbe4d5d8d1c7a53fd61aebcf7ebcd0f461c7ab3b332355b5a1da0bf133967747907f6ad5effe37e7a8ac1b53d4d1f581dabf6bbab485fa3d4628b383849f7467b483cd43fd09eedc84b9a4286bd5d531581703f4e7f07057f89756c321262c55cc20a39b450f8ca1df83128694b34c6b95bbff64d481860c6341a0cc4c7cf5285c60f9bdcbdd644bf4fd19f201a9cf885cfc43efac79efc73885f2e054daf5209f695e076075638d8bc5f568a6fba61a2404776bb953be78a6a76d2b4b9f95e2ed1b8779a73fefcceee6346e4c5d6ef421d752cb9640cd3c66f0c5e70dbb008bbd3019280ce11a0568bdc87f3f56196b7934388597e40f39e8b60ef73480f2fa79317f723388009dbed478428576c90347368f9c38af73fda3c7e966a325e7aad6de7e5713db51b5bc17f87cca80bb215f95d46e429900e6215e5388d91975a4e6298da33635a0beff902a1eeee5915b4600ab12dd6bbe293df28fdf5fff9938eb46e61d2efe275088bbfaa5def02710f3563993ae903c2c4bf56732d98d69ac7516a8ec4349066326eca7e5e8a2eec19710961d1e922a5479862a9184730fc8031d67bb64b0a2cbd41bbc714016c9e43c6f2b525aeff26757990033b5b9151c45fefe5d272f75befce86ad71d45596a5ee3a93084d3efd2e61cfd63a2e7c7c45315d8e3f8aebee11fc7625b96be958fa4c289ae47419a6a4cdacc8069cc9a68a971edfa11f2286d57da676bc68a466a96c90c68e4d1372a5d55bd8eb5c0ddcc60c70d397e417b44367fcb4c64f5e5d6bfaf00ecb0d86dd518530a491e072308f36dbbe175ced9c5a27c9f2ad06a646bf06eaa7ec8029a8e0baf55e94f48f226a2d477c3e130ff166ffae649c76957fc1d527a27284041bfe04993d2551a7793128c8bc7307f7bb6c8a08081f84878a7d566495bf2624403febdeb9ed705d2fe0e248a843614cb4b1daf1d3c26fd19252f652fa6f20ccdcbb8c30ae30295557c427b5b9ca484c9f360acd4e572771f2c7e44126812ffcc3cb72d1de9d4dfbe62b2a2905c5fafa7dbf48833bdb9f7633987744607ec76722de9b1e8bf1b076b4a3bc95895233a1b9272af16bde7fa65838c6461438fa9c0c0870f5811fc6811a89e50e7c6aad39ad7092db180af803bd274d2d940eba71bbfa01129f49752d4a806244e70c2bb9f6fd4a9dd260ed9d8e9dd172f4cc6b617c31fdf2b5014cc2915484e40a9e5d9e73d11da44cdf6834fcb3aec29d472a396357024fc0c3d2a87e5c9d4322a35ce14ac0d51c643053a7be96726dc6d475463230f7ea709f7a75477e46f6a70b1ae5f8238df9b039f2d8e47f022decc290c28ce260cef8fa46a551a6ff14524af48581d93ecc2ac83e8925f3bea4db6671c4254b02dcaebcd7d96bc489c15c8f845a0f8c1c1ca2b5ce8de346f32e462d39f9fb20df86c8c3bf1c4a0b01b79630891cf7ef8e49e64a15fb013a7ce5debefc9559b9fae64dc42e2b1c038c981597fc21fc4d3ea68ecfa1126fa47203f25a48f6db2af9e783e338198550e3da6a3f61b3c39f7aeb6514a7464a86ad2bda76a7e2955e0b99c556df8dadc8b1d9645317f36d017307cd654efe97065c5a2af5ba66f5acd76241e1bc95469aeba44098a17f1f07d7a52817679f85679e3ffde85b8d1f8e01378dc64363f5dc02801d551b68c36d5137db76cadb8256d65bbd8031a22322b2c1799b54e2d527e771f5f535d846c6853426a2f24ba32f8c9e2fd733e84b0b968247510387e1cf17160aaca391c059ba63390bf967c3131fa567fef08bea2856829cd4b444d469125d91e3d4365529d27562caea87c1ac16c723205014cf686b8cfdf56dd200e137c355e3972e129b6f3160ce7b0829ca9be58b808a7e81e2ec23506b7f04cab0d5d78e0efff73102cbdcdd8349db2093a3367e45c1590397a5583df365a7cf8bd9081c1a75b3272570d2db43a11768ef0feefd4cabc831e05ce4919f337dd38e4afdaa8c1dc59568a7e85348ad782cf7b9463dca85b2ddea20472ed953cbef3a5b5819a6dc8eec9973eb4c2ce2be29907ebf675dc0f89c1c9cf59235d3885f4059e89ed64caa8524a74f4a2107ce19a0df3906629cd312f6a3485a958af1d4c22bf4516806b6d67e9ebf22c52dc49113c5ec6cdd23940b5235ca397186e12b596c846f2a8da5afb99c0864eb869c6e6873d1159919593dff26620fb0422b81bd795faa8b943437798770f7c7e43191fc757696be76726bb078f8bfc68286cfac87e11a588f181df4297cd03bb5e5bc607464da9234aa2fe97c5cf53f75d4791f3407fba186f6b7c9d48bc8086a1173d02b6c44df3a272aaa3159d8055f7c1dc22d82da48614bc16077d8e85b112dbaaf331ed57053a580894f49144b41b2b48be5fdabac8a146e2f7d87fa8208bff359402cecb289720ce27dd37ac3df4432c2c68f00be594c42969018d68e4a688e7c22b217bda0f6c61f01594158458e47a3a94b57df539164584e7b433393f49aad03be602de80e5bf49c7494b0d15fca3b9f19f8a7ae5f1e055df7a1ab0327683ba49a78c7ce85d241e096923ccedfd86b4303eb82277416a815a68656681de929e56b4273db42bb565ff63e23491df888fa99d043ba202773b9fb77938ba2e33cdeb55aeb4959990f7760b03e88aede0b72176d5e7ccccd2ad2a126fed9203f03ac432a4fa07fdec65811c3257eb782b8690659feb753ff8ecbdb548757ca154d31cef7072f32b33f9bd72d6b046ad92d1afe50c7dbf74fc7a20d041dfc6569e68ed8cd312b3b54abc84c6b22102000d7339c96796534ea5c1bbebe15f7ce058ae9906f727f2a1e40ad1bf22c21fb6d40812687c54042ac2cf17c0ce25d8a2fef6ebc3185ea9f90fec3f74ff1ba186b287608e35a0ad4f0b3cf8959ce6bb6ba55273718eca6459e00c52935f7736f854dcf7f5c1afefd164efd13d3113f6b09dbee081987ea59b6b234f6fe17b66e31b6d3041385770f9ea1a2253aa7d666e329c51bdb6c0c2a6d2dc905fb463287983e0fd4943e52c43c48fbe8960e04bb39483099cc1605ba56c73135de52630c0714fd7635803818e8be3f2e0f21cda037aa3b0f575cab2c8a9ef845f680be3e4f78f8c30e7a63adbea51744dce81b2f1ad693ae5c9b955e3e172dd8f221fcbb62ea84d37a4c7af9361fd25a8149ff86c3de2a92eff0b42453b93e21606db088db1aa86d12a9e35bf3a228a4c0d37ac68baca4d6a0fe81b92a9c464568ba06032b51c8ea38325570370173d55a3142e0571b8d27f2f69a8dd6abb1683e0524d536ecfc5c53912eb76611392d4a71561ab661bc11f9f4eb5253c6637143b9822e6794a0aa31dd4374c76bc790945fbddec2aabd0ee78744c7d8af1af2f0dc605008602e0cdfc7bee4b95a5a6dd3aced83be8cfe40852f0dab43d675f31721242bc6afa05e0caff9c2be6135b9bd8e847c58a51f132c8672713df4ee2109cc6f4349a24f74c2f073118fdd5bf89274dd6d1d23e7d454c44cfed4798e6b47c38c7182e2d6a56d852529a05461560c7de58f87b9d99afa479e099c6ecdf6c3fc0f96d94b0efe2636eeeadaca9e704d930a9ad255f39c55545cb0364a1b7efe2e2671a9045aca069c9875ee213bc0e7d4f86599b26b67d32b4c3a3a043a7d9c7404c28f53b4a84ebda6ce74b69f90fcb7f296fc94f658560feb58e7ab32fed4463324acd686e1c318952af581feb9ed5fb2d3491af540a92216b51b3f2947c587c89f249a5cd637096976d2c1ceeddcc609ca2e7e755204e24c7c2b580e04a628c4e5735787b99750c9d5eba1cdb5d288c3bb530715674b78201a15e1aa2c26b0998b9a775bc101569d9b482d4c9532722d390bad3835d4566dc307051e0d84b38daa2bc7087eb369669d1017a26c791680a34db033f4c0f3ff4ce5b161c7a41e1f6ddf886bc3c3de2505f4507755a6fdb48ef5fe179c32e2a641e3bce7e8da7bd969d2c871ba02fcc95f05070ef660a2966a6a764011fc450a0f9eac68b9bd5037d64cbe6113789927425c9d49c7c64d130a29624970874ee25aab9b149fabb6874d67c4b09e82cb55d86cb453e2ed6bc6f1bed55714111f3f00e7a0825681224bc0ed3855649fd7b86f99ad28ded277a56d5ca6414853011c4fdf93fca3be0899d8b2476e19751922ef221445547aa86bf31af83257bfa39d1cc1e7e3a87a646166cad4f29182c4c0cbd867c4b59f35e6ee96e91e4fc1c56fccb58a4577dcf18a3ef12dda9e854c764fcfd82e42b502ddaa2d90143bc1ba02ce442b1127564e9bc315094e92ea2793f26a08cff9d53884a3b4a05405a63eada02330e98db77b979f3c9dec0d45d7c9e6c1c52d8db2e5fc451e7397cb42443548df13773ad20be2cc068779490dcffbcc618867e7f18c147655b7771df3bac5cb8df567bb5d246e24d93e4ac786f3b8b48398a990f55a788bee234009c6d407bc5e71075806de1fd8f504b5b25a209c768ed0b144b43cc8178f7bd48998a93928d5cae071ca727cbe295993ec6f867cfe3f5d6e3a5a2774ce0e73d096a15ff5de496cd5e4229be3fe3bb5204f13109befb87e4da24e5f47bb8994d00d083b69b96358a8219dbcd5bf3c249fa19e96a6e20268d906b01fd5a35db936d1aba15c7646237a0f76790d09b771dfd649975d765589b672707d5141aed9b1a0172e83b9dedeef6c5952e0011068a6ae7abca30c196e5b7c44e08ddaa13cb4261207be83f39b7e71172ea7de71518760e797687bf3936b4155062be3f7db787ca1565e1a262d6851ecca26a77eb475df391fbb0fc3d9b757e7faf55e3d9892816e377f786bc59377cabb088c4f144f9e2dd6ddc248d1db493550a2c5d1ca7db8561dd9cc8975f7a5374368c014407925d5785b7a34875a1626bdd7dedda5876e1e677143586ec17bb8e79257c9b676cb2019677cea341ea027f376db1aa39f49a1bf3140c21db774f84d8531a50e2f8ddeb70f380a362df32c3d5c2529285430254ed1af301b2fd7590187032d7b7ffbc4934b9d8caaa09cc8f86d1fc57304f36261977eaf83b26cb4cf6227169b09a27df71990705c68da622a5bbc17e0e13c77d76f89e7bfcb69bf13c52b276aea295486e1e9fbdf205be9aa85db5a675242c8a4ebaecc74e47ede24929736993b2c11eaa181c7268eb74e0114c72c6d8f186a68b4f477493aea1238ef351202624b838196cb481b16f782ab3e9f5d73fe05b8943cd87cde5f50b9490355f03b504e0ff23b4978fd83e48dd7aae66602c30a62e92a9d08558e96e8ce303ce31fe867edde807d5af52ea910ede5ccfcfde711f27c9e272f0420d5079279d39b580c9909d94061fb3cfceee55a82522090841db095affa8749c3b1bae3b013188c247ba2b3689ed0d045cf7771333c753b503fd561baad5bdf8dd176071a799a9d99c81f954708ddfec6cc18b074b727a202c55714f19e2ea1ad2a481db968f27988385c61bb13a3c97c5ac91425f7ef72e40f82a29f004ef37d77c032cdc2a7861231cfa59228deb78f391b593f538c9aaf9ed455fd2085c222364c5121622c9526e32732970ac92ba8e130aadea4982c3d1a2a7222dcd1a97c6d0e59b7459be957152b296e584b856f3a161a0061c941988a01659229cac34c866e9824fab5e4ccf4c2d9ea96ac33367bc380d39b2a48ae888c33ebcdb1b8e96251b3865bf655e12c7214e9e5f51f0329766af724fdc8b4f2d6ae033b2e29f86b1a87e52862e1b977ee68877aa38441b665aa1eb2204d769d2272f14804291b4bb2213b36b6edb2989a13a1c4c7a3000e2e6c1563e084f57ff45e475d35cd1b80a5c30dfafe25a81e7b21607a1886f4a069ca616f5d1eba24c21cbc64a2cf5d1fb9ebee474c67eb31741f5ab51b5cab69d4075a6eaddbad6f617ebe481e7a1ffcea47d7f7697d91f240b6b2dcae42e86c02adfba6b826d94f792c36309600fa9c20038f21e496fc597c1dd96277c08e5f9e6b83b3f6a32d8874fcbe288fafc6b8ba2676e26cea7314ea6672425078715f6613274ff038486e2b053d71880fbe93257692df29da8920c9000af6297b8abcd3fa366fffe7f5a4518f0bb9a3ddcc0a51fee309409ad7093fad585bebb62d854a9417aab47d33fe9a17bfb048c486c771ecaa5aa0905b33210dcae8cefe17f931521b9ee21e039eae88e54928123a38129d05c68872132eade02923c5beba1e3e70c11a898eae47924bd21e97e6a0cbf04433282215422a2e88e145cad96aa7998d86cd0f0f57890e4c56e4a55d10d90f79d6014e91deb10695d53c7131fd51576c4e3ef1071539d2ca4dad726461ca3d9613913ef6b416685b4bd0b44af524243d308b8f572b4c7f990dce90df5e8516162d7b1c97979b56bb851609d3916ea3ae94aaf5bee329ba4afcc7c171e45d29f81585dc1e7e308b6ba287c14fddb3cb5b5c241dc7ce3e870ca06dedcd82c26b30a39c0875517982f42ead1ddab06c2d7314a4bb64ee787372181a4779522b4b0920685635408feb94ea2760558a89ff6f6406701a1aca6476133b56123fdca5cf2035701bdda9c7f21fb87ea6d760df6272e21abf3a3f74cbe520b3e262d2bb680cb3b731b410d987bfbd06f38c07967d2ab7522109986c8a0ace4473a9eb9480ddac2fd1e7cade9fb36f41777c705a184a2f1ddde34ed33b0d10ae11ffba8a4772dc8788aa3c3a8e4132f7bf6fe7131d04f513a05147c15241d4d5ff2fec19ad1390baf8e97f0720d2ca950e12f424480ffb087903d9a47e6d633630c2550258f8fe728e3872fa43bde27ffd279a4f39f4e98c8c3d20323dd6813659ddf962e972cb4a5ddd50429ff922457ae14bcaf386c41197e66a8e9f7de7d96ce78c1edc113b3bbd53eb38c0187358698fb231fe5a4c4c51b7a91b2c997e803a90d06e0e0bc2ee26c89e0e5796ad1c46c496d5d0e70bb3493defc1e0639fce3e0d6bad04a51f27632c6ece28aaafe80580eabd968474a7b755b7b26542ee10bcbc17b007b74165a0593a3d78a5c36e9ae474f79410d8c44646b6116d7857a3c4fcbb8537a7df74e8778c4c324b15c72a98ba1a922418e9784b9c3c61286ed34ee8e45404062a178e0c9a8ca6bc29365cb6fa1fb6e0e24ca573b7861c2f84ba082b5a9c325340f86bc655c7435d8a4eda3cae4ff3cb19ac99b88ea07957511c36bf00f659328020646e90d3e24bc44e8b2e365499e6cfa30f1e54df46c5000b9f7a4c4d75aca7c9fadd464a4a61559326940d9189d0c16f7571c3eaa42e6cbfe68370ce55a22d004b467648738046452319959982d31aaacf658578acfd23b3b655f23dcc53855856086c24bd3d1542c0af7c87353ada04286fc2e8f5baf1ab7264e0b3f7207f963a0e54a58513bd60e272be07af70a1f1e36d9a22e9a903a6ae0f0e89191c4a576908aa4fd213287bce3c81940d825f3239c215602a8addfc63868edb18d96a9bf1444b54b2cc5141649b04972a7eea8f8a23acd1f954031208c63f06b9a1f8d1f5a39a18d512069166ac6cf33be08921282c0a70d241f360ab1285d685d2f9fff0902db8d50d068964c79db6454c5b1c12ff41f59829c8bf5ece1809adb835938b3c527f370074b0686ab9c7850c20f01f2410a659890b61dbd3b0d1c87f7df81aa4a573e52423b46f6e0273a9b3700fd0216c7cc894dda43f269f8d86c338d1b0261605a8f96b6f93b9e4fe65af1c6e64c7ba932a458416acb842cd64278d84127d5abd329effe57d7e261edec10e1873ddb07fb645a845f893f198cea8cc512ebdfd68a24a4650c4cc95caf082ac0dd8e28776c3dcdf9422277635a8f71f7467a6433bdfb434d4f5a216d4a08a012cf3dc625d215dce9be0ed558eb66fe36b1401e16f5d1d298a5162e860db90a5a67061bedf61f6c79b42ead5453d7089b33dd24b4503e5f05af537b2da38e6661d42297874d2c81fa4a4e95cebd5054ac3d10b93ebf9d0cef563dd63a897cc503f2d060bc99e0c108559fe9b7bc65648d8c9a09ae97c927349639d303da1b9152478b73864ddf3bed54f6f9a8622a3eb3f237cd3035d95e00d9ba23fbf116b280e83a1869aae18849ddfe22f127ca5f5f48635ff55c9ede23e8c09538a0fe8356d1df7ea8af95e3886cfb588b70054dc113aaadaee2ad5ab075f2e4921c15615a06f8b6fc5385b8fa0b88a79f11bbc6ae53746abe8044f173d3645a425c48b399a072d0e699f6e4b110fe4cee528c7874b2b23ddac3ae84f3dadcc2417138a70cf97428bf74e1737361c2c7b2e6b2aa23e16fef41888ebde88275d28e613090f8b780910a888cdeeddb365a19266e8b95737a206f230903dce6eeb25f951daf9dcdd53a96afce367eb235e4afc0ff8dcbbb36fa41acab096df504ee5b03e5361e9fe2876ef1168f73a497b8d1ba9a8549bdeb124b97345e02694e1fb50dadacd79e7ff035c9debab5b550054fd6b2f1f5c684f3a3578bdaac115be5bdebc492ded9377cfcc0abf281c70381f4b9f1dd9e6f963c7a9c559acc4144e976a3087eba897f715afa41f9900c6663aa99529d7671849ca689cec1958e352873477d8e213a262bc8770e6f2f85b7b51585c1e12847cc98917c50a668b68abf02da325b38fa769b42bcfd852b5376273d65e5db94d22c59c8819298be159bbc3625eec9ed014be218a89abd9b0b74bf609594051f04e315da53aa14d983463ecc9cc15f6fcba0b4d94f8c7d470de8501050ef0adf434049aa9c5eddd41d159e611e3f82a3ad9d0fb942414479f45652faa782b04ddb79ad7f501e3b7fcf88cfb6d79900c35c8af77ec557b115692ea564e8715ba658bed56ab4da8bdc52f655ada58f1a347a66c5db7f1ce817993453de0492acceac80382fbf17a7c2cec46a9bf0e9e611be75356daa0d4559647b9261dafad3f1ce35ff4a22eb92eba0ec7aeda213759bdec3fef0027ce0b623e041221b0adb3c7094a32d9cdefac96ac8c4a755705c2e7de0b6fbed4c5a17143976f76a9808f3803a48dd0e8d17c3acec6ab6e0c741a999c77977382e1f5750a89261fe59237d46e928ec9a747a8048f1d5ebf8bfbe813661b23a3a9b588940ccc457676893f4eda609e248c2c5fc89212150cdc1b8dfeb728832c23b204c20a99137c5fff463d0dafaee2f56a091164cb0f8c6b6b5d64c056268d497517eb457d1fabe1e6b5de1ebe1a2f071385ed99d13e3dc9c380de9b2721842a6e787a9e8086e2af9d45254633abdb06c0ebc7a7f9f637d83b4fe1d9c759419130b54fc85f00bf32344f67c352489ff2c09b8cf5985e3342216f82d1a34a0b980f24fd3550bf5ae4273759f6324254bfa237e6fb92cdf790fbfbd723a212af8d8329e9916a99c61648706c78b2ac5b14991d7846d1c747aa33057a983dff17dc26e78375d8118a1ecacdf0b8bf230c270e0d4bd0b3a32fafb939e6c4b1f73633743eae02f6d4d49d3b9695cafbb13780fb9fa9e9cf0d2041c7b7809516cb0500564c970af5c431df4dd4beea21b39b1fd2961af9385bd92b854de7dcdfa22edb7f180a60b96ac1be4e517aace0324699fccb0af30b749c6f5d6bbf5b309ba0464012b8a447649f950dda197a8946f381b9a39aa90664e5599597e037ed663b401dcbc7158be3e7baa674a4789c79f29f73c56c540e3e6e90d7467bcbd8de46c921fbfbe30a1e7b7541a97709b6edcd5031c8163e849570fce5080f27dea08c743a19584c83db9bdc2c14c2fe22b53c777c0af71855442ed92b710a65f731af37c7b4efad5c8f240b39d18cec9954cd16a2f3b63a015c557458e6ed19c743233757fc3e25efcf0f7865ace549d8bb3a89a2c179ae1e764eae36398cc1b9267d61b1badef13bcfcb7392ce221d3c9c81b6de9f3e31faa1cb9322faa8b6375f5f577d985d743a2f21d26624b0e251f7830e490e94b4232856d3ef386992779a77afad0b005b40815e99f96b494cf2112ba8f07a30f6175cae4a92eb879a08b3a4ee1c88c253178d76fecdc4666733a9aaf20f06dcbd42aa9ca39f7022fdaed059a01978451d45abd7c635430ceb70352232b23fac47ef6895200729c74c1b35e9c53862f79a0cfbb18ad27de8bdaa3e3d41969867784063a731d24eab5c6f98e3c1ea867754be427df4e47be8eb57f68e761cf6999208182212b4da3d2ffe50cb555fb230e20fc9c05ed50d1b349a5c4a435f0e1d8b2d250e5a11373fb64d947cfc4fd083347eaf5002a85b9fe1b7f35863489d08e21fcfb273b55a0b8f4e47f0aaa89aaf99bf7b3a67ed8b7dad791eed7bd82ce00ce1752db2e6a6693622bc779c77b47ffd03bf8837ac57ad518419f7faf8af897ff7ea88dc459cfb83af8c108312cd665579434edefac781dbb1fe25bc757ac126e755e9063251e93d8dacac006b294ac271cf401b8ec4e96269a038b33fcfa64eb69d6e8adda199c17e4f5e56ab6ac770efe60184da76612b5ff2d5ba748b8e24901dc61006de63d7c580c098c5e516a22f6efa14b010ec546ebac39ce84d03ff0e6b5a6dc9a57c1223dc45192d57d006dcd8dba00fd5def8643dd87e11d48d1b45aad97711c27042c3d28aea1eca8949b4bb08ae17e2db3b959ec72a4530be9925125b2296269cb35f0d17e1cb2c7d266c353afca7a3042ab136dd4ade7f6ffa6195fd3394d8c58619211088323da73b171e798a24120e2515c4a87cd1cbc1b3c53627dcb6382b1b6dd03b2811f492788d22261a841725b380d624cd64a89f6d7bc901c793a8f3770626a2b5ebcb8a6b8dc604da99f3bf8525b5274d2518dd1106728409dd88004edfa8d0fe3dcaee052b58875824e3b0b7beafd17f7ff82862e01caccee8ed230f8e488cac70f6b0d349d78d2815209dc4126150d37fef59d4d232cb277e77e89eec1b282dc5ffef4e287cd010ff347a5d16acbc79f09db901ac41f41f64f3c14dd644a710bbaa9853198ffad3b75c65c18cc2afc14a83fc329f25a6dc3877a015e9b55efdb47658f8da07430de124ee37c1ef6a1e1da90029e15720be2ae9f962c48bacbd24ae15171139fe8c039189f4c427ad26b843cd3fd70af460a1e697bd70d9d0816c9a319d9442b6505b16b5ab223073f884f82285c9b81c68ece82377db4fe1f2bdaed85aea9bc8333f3c45c5e9ef05361fd2531949a553d0defbcded0ab7cc34b656232a38d80124c8f87922f0c0f281c9521e9ddf0fbf731f6cbab2f86754f0aa6e4151d672ad3cefd3d865d8a875a25f3911f0fb2ffc477f77f46320a1b5d5d4c139221adbb1fc963e2e9be77a03225d959a26c9842f895da20428c9ed3d52d59a2477f2392d69c5fd6b4124bc0b9fc784c30f80ea3af4f227fb6dec746fdd21d967f7c72402618ac5c76fc45835d7a04f3d1b0dd3ecb2f89ea8a0bb358fe252e961efad89996ec4e78ae29f983e076d1f21765b78b1405c9b8275b311809912320ab16f0e205650436fe51a32de4a3d6f7399c320a27b75ba00843ef6f7a1a93bf57f67f3f7bc9e94e101e72f9a994b3fffea52eacbe55cfc60625a200f2e78ac76682da38be3d1239ec7ba9755fa619c9d293c3230653ba5df4cdd77b689e6a541e8ceb4fe3dc744adc51a7ba80cf0741e10b4ff41576172a5cc36ee5655bc1c2dae74596608ca0ab0e6ba34ed1e5a70bb8679240e08ab4898966e313f831f972d9698ea22bd93e654562e23d582f21df78cf0cef9c3c42e863726b0ada50059a8b78890ff05eda4481820f628719c453e8b9076507c6814a35e187c3d566f54b98132c14296adb222a843cce3f4d7005bf2f10a905c22a3d2131b57447075ad81ab81ead6d3c51c923ff39644d4c8e029b0c08909036fe74fb0e49bcd233fab2e1c27bffdd71e3eed7b9d845606a7caf576ab7f99004b195ef0badea9e4cfc8c435624300875bc7cd608fff309561edd65aa1cb008644a69ebbfff1a76341e618b2c92dc208f8909ab0c91f6e6999625056036b3d99d925fdf130b19bc64ad921bf9a16da94cb17beb7fcde557b79973c448b5cf5682931912078c565257686495e1ccd6bbf56f3bd05a1728e7523781c59dcf69691df633469e1a44fad491dfe81c3991af6da0c87a2ec2483fd3e233c1af065b2b153d524b243cc72fec53fd63380066cf50aace07848be64aa5f262c1bf7ae73ebd0892b610d43567271104ebad5339225e96718ce108f228027d42f92345cb26e61ca04afd5340e3aecce5bc770d231d5cce9030206d7bb549e71d8b1983e4fc51213666f774d407adc7937c451c40fdcdd275941e63d08c3667db0d9618772d41a2cb1be3d07aed1683ba471a634de7f5f13d75df0c312a67e9e3572da1354f98f24fe0df62022e8cee43848189bb4fa1c685d67e67478fe80b45f7a780af2359171b9c58f6fc0e177e6d2324a3d0b3a44a2ceb9fe6e1ace60d19600eb2ca88540ab044626fef166fc13fcab3b41c1d7159d7e9bed01f08e0d1d08bdeba87fbaa77230cc9f6bf9cf333f6ce696428d7be7c81e3f3ec224aaf71b2ff256e709c556cb787ea2e15cd5e1faedfbc779a2e70641f16b9e4c1518cf427fb9d8f763ed8a2ad629b70a6244940cc8bb6449db817fe1a511fd94517e46b555f222ecd970d6b468a0919dbdbbf32f2453276cca5ea5ee9086723163833791d2543f52b36f38155a5fde7b1a55e14c754e9ee62dfbb7c9d6ed23e3d9d5ffd112f84f23feefc80e68f08677794f07ed88b12be5a3a0df4e84c30601c7df1b69c1809cb88d0e04b16b4616ba61b65626a120542ac60e361429f2ffe5144520f6ba1bbca3612d0f5205b5884e772f4883acbb8940805677b3fb31b22b6d41f4df02d89d9fc6e366f51e6947a6a522de0942ee74ea71740d7107608cdde6a56c96c1297d4caf777390b6f102abbf25e87d81d6fdbc58b66a6c419e0a9f4fc996a9c5389e865600225a755d952e6d9902a5d3036d52371a15f5784a29856b94b612971fab7e7a3abea912105107fc9ef674fc6a319dd96ee38c061ef1c180df082d15c43abae0e59a7a4c6c3dd9d641b40cf210a2fbb114dfe4c74c45ad3ce5d0a359fe35178b0dc78a96d42c7845774f5bc95a1d319b6f4bf8a6f27ccc6744d0f6661fd3c4415e4dda0809a43aced8736b14523ddc175b215d45def643be28e9339e09bc745a90bfbfb1fc9f326b110742252408893459899ad09f172b6454ed24d583ae86dbdcd8bd6e47ac9a64d5932dfad34e37fc594872e9594c95b2d6b50357194e33f44382f2f764707934d5c7e969012b69af6b4bcf21138101039eed1e22d8d2905404a637eea984d86158ea9f1f990ba3fc1ccd6818d1d2f17ed69dde4b8f4e85e1f1bc40954d12038f8a69b3477e93c187d0c491df696fbb4a0c8e5edea3b08cefa37185ed5e0779065e625fbdb7912465e637ef29f96e2fee8f7766df40306876b8def72da0fa6d936c7897ebbc05e322be0872c03986bf0794203ef4ddba98fda815b3c4ede491a9501584e0c62e0d36e58a3a7a7dfe1ac300ec715089e18f061533ccfdd67eae6326c68cb7c0578e001abfb5455267f2e8cba6cfe158ae107b7a9fd757dcfe8423479d8fcd54111a6de775a24bde2fcfd3cb07d33d6e1babe15de2274213bce93ae08c162b1597373642c874a80267af34666d70a08484b6794b8244b6e4021fc648bab72766c60ce9c6ec2eb221f1f81f3b6aa9bf2d23de296a9ccf2572c4b6f655eedf4a66682400947d557a70fee6de85a032b5c352e516c5beeb428023c4456befb5fc1917f8b9304536ff7cda6adad0d47f18fbe293addb5be35b6f84911a36c8ac0bf30e7170268d44d4623bf35ad19dabcd2ac118e789ab6fa1e47b4f701f9f6789352f8a077df18701b214e540ccf3e06299322d253c248e47abe764c622aeaa25ffc9d26bb2201b0e13c5780c74fe46f643c9ae5e9b21044c4db666a4fe0fa4d45bf0cb525529fcdb06bf393640f4dfb6ca64bbaba39dfbb7f4ded12986acba898c8210b2039abb25de5c075fa277313107931c5571879f800bcbca86aa6d0b5297277b6834d5072f522e30664bb1a3a991d40486f4e6897a750844981c49849ce584f7a02b9a080ddbf1c86b99c9e364c95e86fdd4de0d99e9ebb9a362ca0fcc3d39d0034326da726608c9b029733e119d093ec39b20ea79984c4e36fbdcb75ede349f419fb4fa80edf404c0df1e85a031a53ca2f413f52f820ff655e1ca4e789b27d791c5f70e6d2950ad097b64e5663eab198ec2cb09dd19cbafbc2331bd4fde4c8f7079e730ab891813a6f1145001fa03fd3506f6e41148944059e25fe591bace08e109177bfd5ffed25164616dc7bbf4131d0fe62510299c937b0cfee7fa944c9e93537e309c0b1c37331bec982f503c8ffb64b91ad3f7111e2b91ff9ec372d7e37532b5d775c32d29ca773b25bc53884d07b93aaac4b1906b3c8154a419146b8f3312444555ed62f7ac4dd4c3793862902fb3f61574860732f748ce89cd6bf83b6d4ee3e38b969dbc99d60ccc3d001cc2e99fd589194d0e366298ee0075c1c5dfa9360ed7157ef230066ddd2600020cd06973b2c0f1d849cf546bf4e121184c213e39bb5a66aec53f7bbed0f6369fe45f6eed35209abc5e040924173902cf6f2864f1628b36e3b867f79818f53a1c01a7bc64477e57b40bec564364581bdee8d88c5a51c829bc77ecc8e8b6dacf8e5540b982e78d9fbfb4801ec208d620d41627db90124addfbf49c29a04f17c4ab66b9fe94aac83ee6854b744e5039aed4f9245b2e9ce951fd3fcd82b2a9d1175611d0f9737518c6403e1ffcf6998f71bdbbff45ba6e25c6f761facafbd69b4fef9d2ed31509b7c8aa683490b1e5cb0065057c4a90ccb40bc75a59232cdd862e6d22e70fd72a63e41c4e075488ff71ccb26a7565e63a4bf06fbef3f975a1bb33d17f4f09976468648f1d29f0e040f77c51cea86b18f204d4bde2680bc1e4301e47407c7d1f8a73d3b00f98156d7f4e364d8ba9f8e3940648dec7399963b53b7d5faded30b66e1d3eb735f3ca5ea6721429b1d8d87b9a8fc83469122d16a225e40bf6b8d7528da4d47cd2cbd62e90d591bfad40fa18ce347ef1117289c4379024c8dec5ea854b5d9a72180fcf85d64200d2e71d085bea8349011acbd6d88eb5f8bddf5e8ac290c393696c7857eeb03067fd491787c856d31e24baaf1855935f9c2e14954bb4fec58f7415d178e166d2079ddb8f40d9dc1086b2e488190c6f9a191dc9bbbd3365628fb29921c342c3fe725fd445a4df7f83380300b41beafb3cdba045327fde9d7eef2860e7355a4464f7d1050717e322806cd91da591b898c7fa32b432d9ef9725c7a4a3d943e17e541c096441edd40e7cf6f4e4ceb655c89bddac20778e206477c3edbbfd86f8242717b1ebe684374f6b5b711a001333345d0111d881deb9debd9aacdb19474530ad60106641e0a4956b644dc48d97fb74b57f82f207949885f6cffc751e20951c7b08ce03e8946bb9a52b50265b34c73adf8f283156dacf015021884585b42a0485d579bda3d2d556072f2b9351cb289eac87758a9941daa36ff991bba0c1a9e2374c790b55116e934209677e0499536c8384c93962dbea5c013b018b96a37f8c246617b5eac6ba7659f1ca88f623ffa5bbf810b1404bad27cd3518e072c926190034bac2fb370a629a9c0fbfe0b0456a71782d8567501075aad6c7633a84c058830c888e1207e3d8d6c6a5cd5a1cfb36f9ca16551493153280d1ed09bd3cb65bc1057d88699b4808352b96ca79d477f399b2661f5e6112071723847631fac04de313885151e0c0c90df0bcfd25b383db54b7b6f8a42fd1520a325f8ec73b335faf0e0889c50c6b6daa2b1b9be09e70d3f55b518c786d2d0e1f9d6ef7270a67bfe166e864cdbd0445f32d071a5fb79c6f48763c6799e5cc536b4971746cd5e0fd932cfd6bfcc8950e3fe3ee7637abbee2491b0dc0c991033b1189617b0542af3152600faea6ae3d211f50e06c565bb4b8afbcadb72f6a5513c603bd0200ed9f688346ec95f7ffd2d66dd628f403c98f706ba5bf5cd6ffab9f4495a6f3bce9b89550e022eb216aa2b7ea351209a5619bfeb49324793bbc5a3665872ec629323eeb3d5a0e3d271d38c36b3934c8882d8e8d6c89229f8493481ea7731e21148945e4157598da6be316b2a437cc2e69d1c6d5e84b24ce1b4f3bfec7b0271b9f382f9a49a88ec4161ef9a5c3188722d875b7beba6eb2f7586a1846a6394db49119282e23ff4d8e8a216fd6a8beb1e9a87131e8be4a4ab1778a9c2b98f66b31664b3842ac74874f5418b5ed83ba5ad0e0d0b3660f77a658f55671531b14eb092cf94d9c9341ba46d76dcb70081f006dca516e0435a50e2d0f1daae661d4c45f47548d1c6390c000538f2f5181c4d27056a7f358b82afa2d68a67c6bd5c28e8a590c325b421de86dc36bdcf767742ed63005d91d4d5f88ccdf4e3ff410e4dd58c7101e60a830066312ed8c4b366f67c26cda3c34d263ebc0ddf4e744b0ece40ec76ec979b9d11bafc222a735bdcfb21d0a5bfaddf4dea743256e311600d9a32647c33b19331ad6b6e3c7a02e4dd6b20d35942c72795a693f5386e1feeef37acea1d5c78fe8fbe7cc83267c3fe8edc15f7975d307693d96a57599eceb5dc0613db7d600e91502132a0b795bb2dd2d886efe6c204cbe6d724f3b1f5fad0ea13cde58040cd925d5e3ffdf2b3c1d4224f3c20c9c932f86cfbdcd968c1ce8ae3228de9b9ed727b24ac713e85d86c70a1c813c60b8b61befa3546c6bde95a51ea0f34c5897ae18efe39046a3fcd4d0111924a41c2f645ae5eaefda46f35ce89cbe512f404a7c6810dadb47caf748ba00f79f446b7ceecc46d7f317bd93eb40f8fb3fb309feca50e64673b03aa45a0a5afe309c4943d9b693cb18527a12d16ff73601372acb8c6856310c6723e8cbde7faed8dcc3da45dfaeeadc9e8cf67b2deb72266c894f689da21b34505c33a495a0a6b0adb38b4e970af9e975c309687014d8a058b954c54ecf4cf015bf992b96d983e2959355d4a1b8b5d030b7e69541b27c6aac128c97c3c38b497c6770158672d92cf41c2278d39dba8e39bcbd28d146b0c98ac2a58ce0aebf7eccc56d77ab11e42fe7fb7640bfe705b8edf9498f03e320574aa606ebb1d0de6b34ee37d31399ccb1f7d6677b5fa3508535039dfa852735218cfda90e1f27014cd57ceb7f484e42257bc1d32a7c636346dd813be79778e4fd64393fa8037d26102a7d10ea2b7795750dfa7a9575e31d88a81c3c5055e829c10315d1fac1915d6ec4915f0934336411384ddf3454d31c6c9d47ad74976730c2e37da0d99dc14234251a864b5cc1588f27ea5fb53634954f066bfbdc76bd58e3ad83c1088b86e5870699eff1e45bbc681764db1343b25243665f4d91645e470d48a3fa1171ca0832d9e14127a1df3caa37565859c12696522ff0bf3d37adfb5c444346be03cfd6da870dc514247146f52766be73010a07199c9b8fc25e6d76ac60a8cef7f51bdb31e4674c4b04e5d33609efcc194860a046886640159e219c84163af0d2b2381cae409f7f91a0fba41b3004441fc8dcc173921018f6142cfa6dcf40942ffda2431286edd91a2698f80ec9a736ea8467dfe620b8b0dfd556c3eff71cdba9b53f446ce72f0b7883c95bc247ebe2addec6e494edc2f8b6d29231bfcfdb343d2125f5ba5ca200b9082d4049b49b400a8a4b49aa9a4876acd2308c061ed4d4e373749cff4b6476072dd591f4beb7f0c7772d62d37ee49921fee63e0352e53dd235b60b74156015f63fe061cb3e1b3bdd481cb4e6d9bb7f5bc980db0d04d6a0ba902f7dd1d2d6d4f47ad5b034752919cfbec3c1ac1b075017b31b456af1e26653a2c2f59fbca09f1ff0ba7c1761e16cc7edaed56bc5bd3987210216950c0f569688651ca7e250ceb49667040bc1dc09a5b1b452f42125ebdd0e21955d6cd6724fdd0152bc36e81981e77289c1e9bbeabd602fb710782ad609398ed08ed2d8556bafb1fcdc2a09a01c8f55c05ece2aab8df457d54dc722477d11a7a84cf92db6b92809527d5f37a38b258892d11f1f991cc57cd509a344e585036db83e6a77eeae938b39a44f7f5e525208c47a2850a621817b63b4bf1d24b066cbb4a7618e7abf6cb61310590539daf50e72fac9e9555e5f6f2fba6ebf0701e56d876b4d4526fc3ef17235ed3eac31be8b1b5f072eaa09bed706ea07a1d000658e87203ad11079dcf212c4ba50e82243f90692563961699796d73927d1833909f2344672d4ba7e8bc0e643244e4097ca8042b53f76c6b8d3da2c120f33ad144bd4a176c81e7027b3a14ab5202dc2726dae610564bc03dda1b059425e52f56bb98a31095a6785a3de8d3dbea93ea88ee623e63f71abb0b16c5414c5d436fdb60e24ce8a669c14373b13b033e89c951090b8bd220c2da9817aae75c1fca70c315a6807ccc80dd197a8e7d1c9b26cb263cf2532bdede8dfff3f05923493d1b22255f0a31cdbf5c62bd38df59160b71b013f70b2e873581857d38c1a21799244f1d835a71816ea834e5f72dd028e792b492dce0e06eb075cedd4c11359efef848182ac5e6b29b6a78438b8213858a0d958d3b44b22e9277067a0b9f43b66155cbbe42a113718d1118cbbc03eedeae60d4b485fa902af273ba007dcaa78e96463808d03a04881e1bcda2fadc96656af9018ca653a30139cba8886c8563833b53f5a173d13e7a20627f4cfceecf0f6cd180b4fe92b070396c9b8e503947a2cfd0d816988c8172e2b679c2896a2f97d74f6c4637634f08683930318b5d420075d8e51e4328edc1102861ef4560245c4367e94e6fc556f374d30f5ca70c612b05b4b53da13d9b4ebdb8316be6573871257a47646ee45c09a524528c8b6083905ddac36443058f4f0092b8ebccb268905288b1e1e8682b8fc41408b2e25906cd9208c1bb3c79347a31c93705b316607719282fca73dc50131b7676e5e35759288d207a7d46cca5aa47e8d4e1cb943ab743d501eb841f5cfa934a211cb0e452e6be7a720d72f29ba5651baede6a2d1e84853f3037f7b1dfd8081407a0f802f240f736fa8346cacb7cbf55e60dcd6ba961d43cca736c8887b5e5c66bbfddb54da1dfb3e957123394340a9b9a7384ece6c9eced8107ffba9df3a6bd6d8a24b8cc133cba49b353f03b924c4749725e47d320886138fda97792653152e81c57f43f642bb66b94670ddc01749210311f03e8e16081b1475da66f7d5d09a00f146bf224322201b276415fb69211c89c5f5a9cd3d46fca4e4c650ad6b842add2bd087746543bd35ac8e2c782912f81b3f96d4b013bd1734f250675e91e3f45b8f78cfec2e5e191ce056b323c8e0144a8f88819e0bfaed2b5d1b8ea8189d849901b0f1556f5878a177ea3d91866e294aabe59b87af377b0f36e4579f3a7d809173c92749adcb613e4f1d4388c8b22c20e8cb60554fd41ee12a22a94e7ba956a32f51213d37607b93ef1803d4a5b8f79fa8ec510a80a6372269f43d4cb836b09f53660d2dc6079f03b05e751a3b80c0a101cb34ba86bf48ecf3279c1e7d40cd9f8d763f73e04bff23d51529ebf4cb628f9ffde056fcf3e3d4d208d9ca9d7d153dec0fe9cfcef26932ac01fe3afd491c9d0f4b818ab20c829dca02c6f796bd9a51ef413f10e996143b8155810cf9ee26794d963b0f5c857f91e47b17e2847b98f5bc36ba24543ccd9c9d8e9bc8f5fe0dcbf1a2fdb9161f735f15fa8c70760b2dbb85092de25591524e59f0508dcc5331ff0e9a17281fe00758ea83f5ff679f3d99af64fd6cf950294481f076f5bd6c4aace6cddce8874ba86023c951d6dbd973fafcd7fdd64c79a47a9ad2ca296b3b6a2f3c56dbedfd84804f4ff9cb4d8ffd86e1f2cb82cc11b68bccd55d8d659c77ebce70b3a9920ecfc9d863b38b979bec43eed04f998a90de8ba1e0e1fa09eb3fe499f8ad5e72c8cbac32a6f14047ea1c7d303dfb4877180fefd7bdc0f44105813be499ad7992a9a0b6c62f98b6ad4dce3d38b6a64542cb60a7f12a5aee025e2eec6108510e375c154623c00a0a0c01711b8c2d1609cd8a837fc2b6b5fb827b0050d7471dbe74590f26607c733d61fe91deb36dc30bbcd9c3869b63c7ac6f99d1e20ee7a9635167fd7096c2328cf3b9201dd85522e03460ad70271f1c6cf945160922ddbce9e003e70f5f143aca5507786ea9634a14d81918cfffd38a8065e064fde2ba5892fd2e21901d2df246c6fe0b8479ae7ded9f094bfcb7989b0f811e75b76661ba36eec253d677f5a619cc0425cc9269cfb937f01ceae10bf2152da920fa8345b02d5263301df461a82df2451e36f17dfa46c46449421084fc3b220f6b7e802b5c9afb406f7b8292fb0cbe6d51aa00af6d3b44363ed51dd2b61e36d7b46fbb88e29bed71f435d88991831700103d26722eb7d82c7572ab3d6c67e03979c8f0b9461c1ce7b7e5c0a96ef5ccd3c76bad4b9c56ffeab212e436dda478c3f5179b584df433200005deeb571cde26ae36a259104db873fe7564f3e10a8c2f4fff22ede4988d49d0b7c10b4e350680744a6d8f52293a8442e7bb1975ee50259c9707ce0782e1f86f0f32432f21bf31e65017097452437436816b487963dd3298b0d3cd171c13a7e0d3d6ec38e98d699120a6489901b9302a977bb82b525546244f37fa88d5da06dca653ba4d7d02177d87b25479d57005c8535e9c76351133d291200ffa3895bead6d1e4d92dd84c49d682fd66c1f9f1d8ff1d6d732ab1792bcf7d614c3cf2519eb245ebb84859cf0662437165fef233191e01247ad9fea8d01f5647866a93dad864f5f7beab881e52572db02bd7e0a3fde46416e6639a12cdebc787f96663e3886543e5d677288f7dcba9892a047b13ff68c3aadf858ec3d8a50d9ac2dd0e6f282b8b5a4b4efed2513bdf6e8ed3f774f8d39b12f8a2ac6085518912ca63521b75544351481a3e1d5a6a393c70a6d7e2fe8ee6716df4d558af74ff8d93c52131c36ee43d99df0eb2d975e8e092baaae3bb6cfe2313af09f8fc2c4e40bf936035176fe14e90e130caa6ff47edfde6c71805cd26180bd9fda2b797032aca775465808654cec2d463596642b8876a859e8e72d817b365c5c7d4ad7e61838cac4e606e890275de5df0cd715976d8a289c7f6c171e37c0b9537e3457795c2fa869d0279b99c7003e7c19c9521b1957e3fc7b064e7b1193e55f2d0365655c3dcdf673245325025486c7dd826d307d7e7661422439440b130bf8cf171068f27d7ba14631c36be203ce2d33f4f89abf4a15a5d7c72ba7bf7b8ab60aeea6dd267b883cda74d0737ad6993b7bd7293abf67d081903cc2bea659eec7efa53514458b662fa8df07ccc9397c3ae3611b74e63c2519ae49796fbdba869494677a547e88282d14da9a2811e3912344f218e32afa614cdbf288955332fde468ff45a3e01642bc3fc0a692c9e518e9a387a92c19276ae7761174c4ff33ba580957b03a01b982c268973c00dda0ba40a5cf17c8eb4314f9e1f791b32656e358f542a921e37fb9801ec246cc36f0120a3c9d01c54ce38d3ca449b9011b15cd4a9916c5ad395ac703e3111aef75bac56ce03ef1ecd1419d1360db61c3db7e9dc2f707091ec4d1753426398077150671f71b090a644885f863bc5bd3529e76cef39c3f86229a4668bbb533b5bd12dc7332f4248bcb082aa386d8b9282657479b6f5931f0ad4b29fc9ca0999feaf56c7743ad4fb068dc73bf5c3c5aedd6f84e35a45f82ee37560d4211f3609284fd14cfc6a0013c6f88907f5ce026217a40cddb3cceb1b272ec07a53dd9d611d5f517f72808a5e95fe99afa6d25ad32ff26f5d9b0027fdd84c7ae5601f5417dee6c78cd27d6d6a7199ff504a20af1d192bf2a4433c4c8aa8a77f246fed948d7efd933caa1d99118b479da309b3943dddb2dba42d624c0f8f4e6840a384186c7146b37eb114b39c5a9231a137dab7054521c8bbdcc55180795574f8f89c4c86c5b83b282c5a755913df7c3b0644ebae757e481d3c2943dc0977199c3cb77fabdb5eb110fd20f0048eb56c382bc58b22a1b6454fd78a3a855915f3ebf6bcde34e11ffaca9941a39464c6d6c2132ce3d68cda0cc472b4d6ccd63c2d38e7f2116724050d5f52dfa03e442e19485e3d1621e6749b6114f9fe6ee0cb6590f6e44add9f4f63336e1ecdb63ad4b912bf1ad830498e6590e803a1ab4c456c0b8907cbe4c5c9eb39811264d03f881dc31d48e2542a6f3a903e28da14a8a7a9e780bd2916c43393f6f29da97a8dae75ae07a3a8b591a03c13499eaf9ddea6b0495fc2516fb93748b00bfe91c2b3588448af4158930d1a8556d5aee1fa4e8dff3ad67987797fec1ae61587561beee6636fdacd611faae2760a1334330476f406261515ce185a1321fa9c901d70aff5ef501d1655b25645e262ca5d9122661ade71e67e72728656dce2865259110d5e604cb7631e8a319de5abdda46b292ef9fbabb93064284481fbff0a6bbb9f8b02315e6b098169fa7c0eab7eb0e9c624e63c2b3b472645bf3173b508efbb5ce8c1a4f1198a6d31f94e0cf3dcc532de9ceabc1380d3a09ead1d238606c5ac1ed86a6dd1f184d22b7b99d2e34a80229e43e762f0fb07fca65876661dd83d74720a3d25186e287cd1e463caa36b24a51a5ec5992e1a539977fea02020c8f49d36052cdf566e10ef46c69fed394f6aae75e5b904076f81e0008de04e4a7fb3d0c5bbd8a1257ab3d07fedb254c2edb176adfbd18b50829aa0ee919b57533b426a16c4bbd8ae339f959f9d2fa74e75e46598076ca055c79955332d1ee135790a402b396bd898f0c6145dbfc5bf1295bb8376c07ca2c2ca8ba3fafdde02890c507c8052af9d9cdb5a91c09366fcdb8ded952f5f9215a87ac1856a442d76254bf5964e22d273684f597a5c1ce695cf52b3cef2b1dae2eb78a53a9493f5e310218c36e80e731d3195dc772dc16918527d1e8d1fd3bc8b4317afbf5554c0bda958c4817e5e499e77e25ee57810c94744987f2fd579c1789443ce795ea0fa2f92be6e74d9e1eb241638758642c4694ace8adaa404d497fb26399c4df7987e8f3cfcb93a491586ef62acd9b324ce3c1d04d7c06cc73d8217051644c71df091b9b0c93c1d32fb359c20ad23d073364b24c1d198152ac245151fde255ff1d1f217fc68330f3a4166269a000c08b9dd5d1777eb1c257bb483a53dcafe0f7ac2488c841f792377615d9b172174e881cd404d4d9a4dfa0e042dd0772d5f30c025da921cf808266efacc6832d60cd5d54173338ef17a57f5e231ef8b53dab3b5c57da0c549763f9394de126335b059edbc7162f66a160c4cfdef1b2e631f187f848b2fe1cf938390843ec5f93cb99c2e714f5e5e28d0090e10a09df37a67e0cc7975c5aaa7d4d33457d95283f72e9d2e4d9fb6ad28ae1fb7f8fc26916e94d0ea9b24af7f93fe766ff15960e519cda5fa8dde9df9c1ee4ac3d1ffc475ed7aaa781d7be4e4f062381b636cae2ed5e9d7ed5b8b0b359274c781998b0205cdd5485f6686de3ce0b9f7fe34de0f51c02db134ed27097ac7686e6b492f432f43d85e3181536a445bd8da939f18c8d71cc8bd56c96f676cbf9a008934dd0a08cb14431ddf60b3f02ba6fdf23341e2ec3eb4433476d7f0a8d100d616f6d27b82d8ef27eb991f57711eb1b8168799f7250333ab54beebaaf33a32a68cad43915dff70d81c607268d38c8c00cd5b4dfe3ec648cf568b99a8d8e3fc6234286b5b8a44c18703f7e668e6b866d0b6bad5d52e04a545a92fb2489a605129b7e45afa52155ff6a47e550e33f587cb46d6eef07f6dee5aef4d47ffdf607ab605816e67d519cfc3da61c81d28e34c67bd2204b59b06a2ac0062cefee1124dc3d3a302c0eca392ce6274ada7f0f61e943c4e8dafc9a1bcc00b396657bc7016bcc56920a98e18655bdd3a7db6b3530d0ad4c8ffeb5167b35f2b1cdd2703baf3bddb95e20edd09b299153556bea59c2edef5d236ebf27362c532493c00be650182313902e8e99eddf31b556ae1c336ab0750558d207f2be8c94f5fba075ca4124647d03db0116bd848ee5f76d5617f9f4e65f3663cd6513575442da5bf7cba2ee9d2f07c81d755656b34538b6397fb986852a83062b38a9fba04f06991ecb0d0fc19aa812c9326b0703a487173e6e984c2a579211e9849f9b6f404b419d3fa64213387c7234d1da4506193aad8a324dd8a04863c50aa0256723e1524abfe391f6e5806fecf5b123d0ebedf24cc1ea6b5dbeb071bcc201721c6c88f5bb9baadb04f2857cff81207fadcb08509e8981adfdb1edd33d31d3da137e7f5fbf808cb7a8dbab898d09fa98b006951b2a86dcffab6588892f52be1d261ba6dfbcadb114c732964ad7bd3743359f2098db50efa4723396ef64eb10d1fa059cef558e785ff5c59879c5091c1bc566aae95f4e6cfe94d39fbc4f7912f0090540763a1100ced71597eaa64c475906aeb158af8c66877bddebd7a7dd6f687c28ab94d2e81188bce7dea95d06332affdd5a81f625cd65201ef7306973f93ff3c028c21631fde78804fce14d8354a61c8a1d8dca396168327b87027dc7daa1ac4f34114f63ef81de4d2ba15e47cc3cef51e1623c0a0af61a848b9d9a19a6456352f190b527ca9528f1c9672b537ef702c00926dffb12c2255729726f236b419eaf5fc7d8592439dc7bf6e3ea365dd69caaca8a690f827a43ac9dac904d5090dc4c39963de54255ca5f59396e4b04208d832d6d9c36a3e019fef4c54755766a9f65eacd76f02d2238af7381fa11ae7700263a4020ff2999af81d0039dc28348ff5b018cdf8d3d5b5dd3a05a38a535adf10472cc816c9d8512280cdc6078892138d47581e73ed1d5a253a9c88c2b6327a95449713a878efe0c065145f2a80b76fdbf2f64e2c935ccc43cbc8a706ea9b782e6280e17126ac83478dfc342a761c5ede5ee145c122d452bce2e2133064b1ae013323990d2a9635c2774f18816cf6441ed96167d7d642dd87911ad27f021371cfe076dcbf5be3d040f23e6b186b973a8af67ca3f65ead6f265dd93db2ac9471e7746eb60c3a2a0c11bd7fc45eec968c994fd0447985d05719df84dbbaa3c9362dff7efae9488420b7fec0be7e446e2fb14c8874aa198be154194e65e49dfada9d166db8afb3fa74470c13d09cb8074c5a66b1c9f199bafe7bbce329b17ae60e99959ac442cf31108c84d5d7dbd0a67c086a172041ec3da24b61416576ca245e4139a99d8459d031e56cf799c1841d9cb28bce95619a5462ddc34fecd3647d6dd44b1256cc266f26a2054ac04d3cdad22a52765b41b386013cbb042e8268f1cc011671db9803ffef4042a62826ddc8adc89a6c2d7322d05cc7ae2a7bb760234b3a98e721c991514705e2ae49b12395887b032376a37d0828ed60f68debf5959d0132da9e952c85c5550c220b1d8219893f8ef405fbb06c64d5d4757803e710fdf986bc8f1c74ea9c29d5f7542fbf8f1e68db5ed510d6792097f18bf4220c71499001e44b758882d31ecaf970d092dcd490c4494b75e265f290ed8405f8757c14b41306367b3540067562a78467398ce20c4e836d15cf33cd96bc2b04bd3184b1e579ea17b05bbff183fc92ea53b41d6dbf8fecacf7e99297ab8794a48d5f15f2ea4be1b3cc35f1c1ded362c1bb0688b082ba7fb746561533eeb4ed681e4ad902bd37e7074f6405b7387c57e3463a5fd773d876a139e1b3dd0f4a419e04cb4307f72dde3c9f5149efc4a604587b5b942d2f21e557ee329a0148d345c6c5ad5b2a57d3cc24d122642b41bd6c6cab99555cae689493a30387d6209d9fbad5946991d7be9b87791983c413b79c572c39c5f733eb170624f580372f385ec9edf862747b2ed0f3961ea5558737c222e70612ac9381aefd5ac8cf8d02b54c6065f1df066c27f56b7bedfdb66db54bb891604ee1b41deb24fb5f54bc819b5824ca73e2b1b364879121ccfa80f5adf6994715a244e5bafd5998a748b72cdbdb2d862ae937cf7ec87aef9f12cb5a256b815a538d591cec363ac7f635d720de9a778195e9bbc5c091fb1bd17f15a6c6b5c01bad3311f54ae4d1dd8e9d1389c1ff8883cc8c007273378f3d37d06fa582a844f97084bdad8181bbe9bbe20446ebfbd5f746cc7ed0516e5aeaef148389e894181400d12e9d169aba56e9eb21c124ac7e66186abe3f0ce1de135f015606bd868f467316c87206b6b2441d7522e864ffa5c11bc7eb8abd66c3fec40f0ec541f5a3b34809ed2492d90ccf595ba9889ff88ec3192775d818cbde7d0be3ddbdfcbb452050ff5275c1b554681d82abd2ea36b4184d10c26c71d95235beff996caacb9ce3f23999c205a1fbb1195330724e96281797348ad13f367656a27c4b8de32c35be80f07d87ece6492ab126614e167cde9faa4b58068048a01aefeb805e65f5a5c103baa6868669b51d91a5a82a3b9b9be6b84e0d9dfe2c921d4d4b231502494befd601150d0ab585138741e109dc8158320e0ffee1eacde636a4992c6d3b61322a03210d80e34ddd720e4b14121e7a6d8eef3644c48bd05f72d15fdd46f09b6eaac12834761b2308be07c56546bb66aa57f7b98edfb55dbf7da399b3724eee80859acef0f1d89bc37c939ead13e826aa1cacebe72bfc040b3ca70fd0b77f931ff3ff81070c3940a63ab7dfa61fa53c58d649e51b2df047f2b6b767d3c18af215ec546c5b2a605cd0ce02251e10926ede884222c03a1029ef9a3edcf338d99b7e9eb49dfce6772fdf58c4f09c9f3d7b770df4810fe0c4ee79de86d72b5fe46d23752539ac6dfd4be012317413faa174eee05bca367cbe03bb6296c4cc337ba0331f68801542c116c553f6c34a2d6b34abdc50c1f1102ccb59a3ca498a56d85acb1bae78fca07f36b903320cce98a04f2bdaf9b1f674b2fcde7d1562059a73ccbc5632d76499ea90a702b870bd5c383010c82e5199ddc287f599032f87f60c20236760cb35716fcb925b000ef1abdfe4495585e285d2dd744beb6a660e49eb4d999f263f7da2be865e88ef42a4299bca75193e6bbf59a7fd3809007aa9f8df3dca46cded215e95116e63e45370a4b853b06cf52c3f63bd31bd8caf2f667ecc55dd6f73828e8029fc09a2dd6397338e370e5d7c9cd8b55ae7aa496012ccea4f8cdae690f79fbdb458c4469aaa00d073bf30e7851063c54571b047393e09fb95fff3e7e11990fd75a7f6a3a309fcdf4fba23f502bbf2c3adcadf8975273bc43b10ebfc11757c1f39900128ddfceb55b73eba77d7e73bdee827f2ce8ae7f5db73e5366bdfdc5dd4b24f066697ce40d46add72068e45f68bd1a2e64b7ce272d3561af0b166b56620b3374c7f77f96221b3ed2365f56fc2a3182ea6e3925d48d4f3e09546f9acd5856578f71370dd22ec1f7882693f9e575ca88cb95169ae346b327df92f47b4bdf147ce7ad4639c4df255e3e89847c4450f2c0f9777638176b33eccf02d427de46345560d7011b1ecf8f3a70a46dc0dd4b80c92b875f5a52f3ab74c18c47a9ba18a293b63b0b5737b31c0c243b67d3fe6b788c2a30fd6dec032633f0cfdac6f1a4a1b371065beaad8bd4bc160bced7364eceabe5ea554bdcb82b5149f549ec97fde693e5607fcd28721f1183e9ad8ee816c6f2080acc35f45888385735303f84bdc25146da61ea4770f13a25402c5e0a1859f88e8d3e4140f4d20c5a650c58a55033d0c5617e3f4a6538a5b0e565bb29ab33fa006e3fcf733df5441f26a738f3b90c0a61959eeb4ef17b16564615010e26cda265f3c857b4821fc74b3c3c04c5c6d77d6043bd02fd5bc3646c12116217544d2206e19226f38f7eb165a9f09125da43e85eff1ba6af8b29f921465935a6cfca3eb166ca816126552e3647d201c2e73fefa5ce0b3f8c4643d664fe9380f8fdbc596f4246caf5c90b599900d699e92ae6a8026f76b6240a2cf60c93493bf2def5300a9e82b74766ee49c84c4e0e8b88048df078c5db68a45ab2177ede062e3f8078ed74ed7cb5e48b1cdad71c3cb957a9239b3660a465ac5dd11dfb0cfb8ef86865072e0cab8c29638982fc5f4c877d19a66f544b5259c0396b2b4034b99f1373223933ebd8c2e0516776b2db78771b04f0f9ac06edab9ddb464392ce1282e57fa61da19db505c674a05b0abb8874aeac43d973597db0813a0aba7e236e9eb5e27570c995ae2317112dc011a77c9a45236df5bb75a654b8e336a65a59052f1db82d45402ddf1e10f03616cf3927356a63e7178fe4333847ebcfade7498c29339dda1c9417159733bc60f810428ba37d939c0dd7c6de3f9699da207a0d247e2d7c99fd9f98db72d46bbbd2c25562e2d8d450575eb4f14830822b9fe43f93b636d381d9a87c0891cddb0c1566c1d166273dafb5427effef92e93ff9b7108f3cf32f5d0ca0e3d1b5495ec0d706854be9b0cb7cac0cc73411d8cd2944247a202080ed1bc5c5be1f823f86b73d98abbe80f662b3c69754b730cdf74112bb2f6714ba9484b4bb0b6b4382d4d3523d6be2e8706783b83182be0a27870ba95689a7f3bc111340d13580aafe8baa8674c0fe703a86a53e68111e4814ad351eb936ffecff4988a35f7aa152037f0798b5909a2adf9a82dd3f0b28de6eeb26da42284679d0848f2cd537db54b437def13f01e6a3d3cbeb5c576e109a9937549fcf689cf24bf08a2778cc4c9e52d64269ddf5d90490f4b7e36949e394d52f7316f93e73eaad0ec6f8409b4f6192bc54b26ab5cef20b79d6aa79a9cbedd8067a744efe640921fd47b092325c397b95f1c4a33541e669ea76509b1d6cf51985d3ac8937da7054dac2535ef3bc7588e1909faa7869f1030bd8f0e15bf0ebea6ba0bbf8c939049912b5731d59409fd856d8180a7a80e84d8cc6f0ae88f4ae1159f7afdccb5b331ab11654cb33062a5985780d345ee40e0f2945b9b012a972584a71952e1e20f75c183b1d2999f597d4cf2a8362fbe9f04c98f71b6433fa2c0b1e70ef4acdbb62544178648e84029ebe361a9c9ccd5c82833d70dcfede206e82bc440e522267a843d05278e49ca3311fa31daa297b6fa706f9f31fe9928dece3af191f477cb5d2688febe75ae88bf6ffb3e9b77bf0b9dd76a4d5c45fd31bb06ce61093ed63c2c7c03998533161382579bd7bb3dfc245285c9607b4713dcc4c143e4132cb071f367862b754e27cf709efe74fd1dedf7257bfaca7a5f1f7fb475f8ca614e27d0ca1b11d6ce52df6a53c9442da64a3c7798322379f02fda0a1a563944f35ca6ccaebd53bbd78d94bd22b33189514ec2f602c1f90f38ed55426717b129fec2a29c255b344fc9ff9f7070831491ffaa4efaa90cb7b2a09fce0a0da4e6a02eb33e23942f27a7165ee4b5d51ec20a52f655758fa7494039d9a34e46763767e0cc8f7e31c375736078cf23c74a1a90321b49e098bce919d72016d10ccfd2c1b58a34ef5eaa2653ce66ff2ca54ca399906cd042072a06047773982eeed48713944ccdf8a0b0f2974114ddabe639411dc83e1798001e45929ceb92abf3bdead258c843fad82d944d2c05190936bb6164247b619b23d3ddd6329d10290ad71b53dbb59ade6b7ede4910f7c2477aed0fb1400f8fc32a49c9c41fc9f81545c77fe27fc94372347dd62457ae4ff0b4d7ed430eb8d2500bc6473439ea77dcb0ca35d65ee28ba2d25e9b8906cc5037829861b803212d68d57beb5cc40a8172f0a3bb75c27deed8ea4ba8877f6db3cefafefc27731835524f89534a749180670544b559260866391e0dbcf4191baee8ce69b38b5295dbb6bdc7b1579f98036d3084a959224c0dc46589baa4d6dc931e162ca395a091622a69fe8d9772795608bf86522e968adfa154cce2ded953408b58e85b68d1ccba3806df4baf760d7f244ed8064287426ae62c3f9d5b90a7e641eab8777ee8c5bf043111a85cde8d1658219d873959f0fb0303917633841fd733f1d01353b055920dd2c4016bdadeeeaa5b84dc1e103929c1569b1fa0de7b2e68c4fdd5958e1c3625019eaa2b8f49151a467b3cb8b4f6b556b2db339619c72ad78521ff07f276b7bbec8e9735e0bd2f624f85e5d8d4f3a708ce14aa522ef6e6db684e8bdaabe1f5b3557eee546540608b085636d0f1ecbc44e19f6b6d1d587f449aefbdeae1068ba4a1394b4f91ae7b78027bf35f47a397ce529403bcc6e7a9f57e036faf1a7a1ae195f960bc793ae2692b3dc8675febbeb586ddfc25e812e5b350a97e0b614b9ece41c71f10bf9ba26c39c44167aa9bdb288c2d9e8ba0a6f964e4f6f8468c89aeb5465a0b0ed393823d8f04fe8fc2420b1a7d4a95d2a9e7e1fe274abe7671e7fa1460b1622f73670c71b1c35c18f24f89acd1eebdfae4cd6138051d27776123ffe39c89b3f9c151d1c42c5a3b0d19cd76b9eab4fce81ae4cd4d2b8947486c946f7b954e8be4f8ab10bfdb8f0ec1250db4b53e20c21d48b720e72f09c9284840a8f0ea008d49bf640d2c14a732564762e1d5b6d8ef9916636d0cd41a287ab25cba1a4cec13ebfb5bda706cadeeffde16600ad852f590695507a6c5739d5c0247b5b5b83852ee0be84584f988b4b03f20b17b139d82be6209c99d1a165cbff7311beb534ac71b516b0f10f03d06e02fe32e4bbfa1d1bf61391d34d7de69aee2196a007bf086e85a1e95a0ce9fe9a4e6a771d3506247e37c859a7930105c1fd84cc1670a18d21a1d9b0b735f7588d1f94b8c1c6995ae29e94b34fa6ac43427b9b77c22076b948016f04b6ac5bcd88dd67f74ac4d7fc8b4bba553265db6b34b3be7d9d7d0c8c6ec3d2d79554a652f7d0e10b2e48f90536ed0e3978161059a88731ca196098b19e980d8e3c4c6d846f8ff8b1a6798fc401f135fd565506fbb2de4285759555ef7de354e02900bbede216fbf24d22b41638ea2ee97d0fcb1f5357fa39e36d3458b5c7acfc04db557f0797c6001d37c30b147f55f9b062ec9d8e241a3c80d377d06c6bdbb62ecddd508a4a5c6f6398c3f5f16e416d4d91faf36c16524174b113598536579905782d5a9c7f609101b16b2220f5e2662aee078c3a137f4e52d31fc0935a25cbf39aa4ac4b9625b3f6fe93c1f140fb2a99307cc65aa7c443cbe520f54062b2a51ea16448ea68e170f61d48e238fd63e33c3f7c32744f047165cc85ef91c5f705bc336eb966bf9c74c803768da80ebc19b1399a9638d03410ca2f1cfcbb3cd6cbfb70f5b2fcb86c26aef03044195a6d672dc66c8ab246524ac876418a120a4d92825d89b5172d4902007b3731a287266df5a0d69739bcc639caabe66ba42bb9a148035d8896994cc2e9e4f7011d5cc22313953bc79ee77972bf17f8e4cbaa9a7db3e77c911206b41d184acb7f73b4c212f3bc9b2effdbb537a019eacbf114f18dfc53a25e36b7ce76754b308d6b48d4b50acff666eeaf402e823367fb113d0cee9eb1677ca19fcc0b53dc3f1946b4f655e746a69d9bc802509f52689da1478d3ee1dbb351172ae86ce52bf7ee129b1ea5efb6017f2fdaf36e39e1eeabb7c405eecfd4a204f53c0ca9e799e50c44d2a135efe6e1a6eab2173c418216bb565982879da5680c7fb8c62ebc07f708de7351ddfe26d9fc7031f93c7f317845aa25a1de3efd9b35dcc3f570581a5166c27353a1295d8a3eaf99ca0289aac2d4c508729cf395572fd1d1583f9274e032fbd9affe3592dea13cd5bd9873e8cd6e30750cabe372c9a46ba99e6713354d0c88859182b6c2d4863f6640095dc3bffeb4283fd22305a7cc9b2d0013aabdfdd48ea4fd7dfceec54334d58b6af92eabea0e4dcdb032ab4929a7b56502070fc9f82f4e426bdcef30f29ae7b3589e0da973346e81a743573d2deec6b25f462317f05854cc6b7b18f36cf4333223523c48e000da37bfa664ecfb8ca9aa75e2179be8f7a13642877f9ec9cc4f098ac56f31c182855d00d4f7e995eae940e8879151f88647a7c92fcd279c71674b23ace33af633c8b0dbacfe255dfba47398d0d69c6865c84c7231d64bcef56fa82b037c5b0e951ecc5d5d1e1000198604872308d21f994421c2e54f628d536d8ccd315821a9f4c573eafda72a2ebb6e669ba061091975b5d2f5172fcd2a8343c0ba91cc7725b389621a6b0b9af1d38b2ab6055da915698220911ca9efe533c774e6052c37958f3176de8f0b4d44db82345c25576199f3d4a8623cd0ddd5177143f14b008e31ab59a748352ff303ae70f18d77e3c73231c7b8b9d15392b4ff930df097434702a679ef7c5231ec566f51eedf9084fb8dbecdf804a09ea4d272b158a193233bc86828534f5525c611f5e535977b1b1d2d68d72841b6e2b790487cb6cd71938fab454408f056ad5dbbe1a1b54b7059ddaaf6a03d088dad1383662fc34830edf51a0fb128aed21b4245a8871232a2a5935bef20e13b62a3c80a65186d4f1dbeb47e569734e88ee0707559b4a3975e2cee53decf76cf67989155c0390882987f95bf22117cb21375dd397e9c8e45b496e66c2918d290fbd5f32e3d1be45fdb4fbd682e0570032443dce5fdc499750d6fc08d7410f5c9bb055354036c51e6733ef21f1dec24e71419f8cf6eb6b44cf9ace81118f4ea7b37c722df510f2c4e50b9df0d6dd234405873a1a68aaa24e9ab284314f04962d9b779e805a1f9c4b40c98d57c86e37b376e95d874bd8317506fcc312f2614aff93de4d52274698766afda21f3ad164b5bba817ec7230fc6d3d973e9114071f309dc44dc54c805ed6b5540691921966635c5eb6f0c375deb8e94af088164a25afa5bbcece28c71f13bf80547125f691d52f43193dd3969ebc8fb8436e06b3c24917d14aefc176cffb97e6c009623a1cf802120c3565dca75e00e17b8923ebd506356bece8bb6bc0e405c3e8037b7e64ff32efbb82cff865fcf46b55346e547313b0db97d7bf0e85ad6bfe079f35f5bc0fec339bbb34e1e588713dd3b567a6cf6a3f0e9fe0bd30f69950af30576494c1ad52e647d64a314fb4ca6425af1befe4224cff4d94f75835c0e2b913814618e44552947bb30d549d406efc9292e61de3fa8cb3a3fd9af8cc0f3c520b4cdf45d627ebc608aaed10167d736ebd6716671ad75583c5bbf5b5361bebbc95501c702cefe977ad5d41eeb37c9618fee8191ff0e07724bac8271f3921b194a36d2755a6e4945d0ebb4f28ac21ac80ad81b86ac2373e9365c361f900ffc20b1f1ec8f6f740cadaead7c24affb18ea75e1594445b63d9ad69c95234c88394f463904e1dd2ac7be56d4061ada804d4cad75e400f01794d70a365fe1dfe54e327f483dd7e51c5e4e02495288a16ab3146a516539d358cf8813d9949c1f55834753a9f2bfd759b92d30e9b172af2b0fcb2304c3e4a50b23892b35161ed5f821f678bdf83f960e4064df349dfba6d656ec6e0cbac9646e065c5d81e078fb4e27ac55f094b4874b8c13f9d3050965daf1caad64b255e322da127d61f606d86d5354039c0ca6f784ade3c84d68770f0893c18a1d53d110895be12d7ca2e1ad47452adb6ce62b2d118059cded4175963642a8836adf7f4db9a571724c1d95b316f5f328590ac33b3d838699254d7a689226a86b927d115c02a9abf5a3cc6dd4ffaa14d1e1a5e9db4ab912032cb158cf171d28dc5480a087cfcc48d5ba4cf96f914963d222aa4ac4b1e4e24d4afc936ad11d25d078025c5370531e1d3d441dcd68653ce76b44b7e75cb359026e1218d92ea1d036ae0bf34e5542ead86e94ae7f219532bb623134eeed732c69b312f23f77b4dfac6a7a6b2bcd73c0602c9794dfbd32bda733ea383d80b802d7ec86a6b7a47330c5cc9bed3b3747afa5a37610a3a97811c05bc97782233ba8bf855169a7dfb6b720317998a277cbd6134cae17519328009c5d32a1d65c95a4d48fa6c86d95ce3915c2cfff956bdb1d53b4e6913d8af3877e87b1cc7383aa663f07b86bbbdc507af51113b3d8ce5ebfe597e460029b792bc76e5e3b4aeeed9b6af6a00978d42b7da2859af8357cbc6580ed4cef0e52fcadc11e3e1b6f8224ee3c5c2807900efd2a621e2b4609d78901b61b89a66c68621842ebada28b9234366c284004a68fc1b0b28fb50249faddd596807662680f7ecffbc10157d4c10e8fc1f9fe9125b6dcc8f680df266e71514f490845149c4eef552036a694cdf6ea508a9eb0ca2563636dea94b93f98001dacae0f51b79702dde105d4cf960284c8d7c7a518925a57860c4dc9d836318177cb8d373af1a69e47f0f768e0f9605cc4eaf90171ce39e445f019fc52e31fcea1f222611aa08d9ec9dedaa086d2bef35e9723544d963cad0d9dab473ba4d58b525d2eb02f69d37b0e7a8f7a7ce582c24dafc5f93f5adaff4ba0072812231681875cc5dfff2c1c7fb0da99a6870833084dd359a1afaff8beaac2f330125d0bf366511e21d08af3a7316d4088378e9781be507c1027c19b7d3f215fa2e5689aeee57e4079a786515f207ca498013f851109230e05eddc700d0cc6f29b48cca0dd05dbd2e7d46ae061abf1ab01b096ddff9f3cf7c569d3488e424f52233460b843c8ecf9ca096bb6eaa8c3e38dad3103cb7bd60a10b7030aa68b87b4f086dfa885987f8afba8a1bc39dd7d72f91f38a4af52fbe39e5cf31ed72e11e8a038433e1929d5e5d47deb19887437bb0480001d145ff6fb999e5a1a4a98919b8ebc443cfd31f59efcd9bf11504ff6b9abd2cf5d0e222131b1d455a3acb5feccf16303ede043274f69b66d92b296c74c95f3bcac7a953bfb8ad79c6afc9325dddc0faa6c101f8a13cc94acddfb4451887e84f5e343696f4f20fcaced571aef06b4d3694e8d88d350ac81f7f39ef39d4a17f591a116bded4177e266e84c2313e31aaba59a3f04c3f56ad7f0e7356ea321aa173a74a0aa4891bf062c7fd6aeeee11a12adec73386281173908f956ae5bf5183c609f383586c7f84817fe0b6063e547aedda26237cdb6a1d9119e5f05c53b7d0fbbd7179d31040c10e2e9f6812db965ff037f239088400bc584bc3efce46c4b7dc54636c4d5d39ac7a92697c24ecaab827ae23d7a410adf747d199726b08d9ca66fa7c38391e5351d1bb046cd7e9c740ca37b4fc4d172fa305aae46777f2bfb2271a92f9919f265d46a1b14edfc22da82cefdf8155ea155f7f064b1869f20a2b219d9ddf9ffdc78b6d89f4e70bfc7e3f546d1b2bf167d3251603233fc224510f14e4517dfb5883824dcfcf3f6efb821cd44eebe435d24cb33fe666a679b6783726e45181e0d3e74d4a69cbc370a6a01edee9468047b6b49fee92779e3360e2e1baea74f0c365f827a87f0b8b7bd7f7ebb4cbb2525f05beb4eab6b2e543f255c9beb3272ed78991b2d0b9769cccc91fe6fb9faeecbc745023ea3e2982b935da2ac364e1f919574ae50b7b8f0e0d92231c956ab3af5036f0d9bccca9a81fd90ca460a4a2fa42cf4f5b4d7c9aab66753efbf00d6951b7caa3b7625dd552e53a9efa41bfa388c23f4123d575de10318a80961866b2ef745fd4d08be90dd1c532b3db1d3447ef0ffd48af2f343a29e88136cd63c73a72af2253166a84291a32b92ae97aa3e861db9b428a702cff76ded31271526e725d660422c7d7c70b33fe64ffe3058149a1cd73162467c35d73f65d4d0366fe4e7907916f9664ccd62ce585f53f9f5b3f05077bd4cd50343cb820dce6eb91b20d9aacc90ff273f3a9981f8cfd079f870faef55cea4af4927bf0ff11955dbde816f0aa2ee59c0f126fe18215ff42acc6de2571c403411a639b1154b60d3f235f8689213ac3bc18fe6cf9b876f3faf5f0fe897f69edbeeb1915c491422800633cf033f90e497744b7ff255fcbde14674e3614ca31996316b641e65d29d5fe71e8d11c34eeec2cad21981da4ff70428a420d5ebb3132d2f3cf2ccd57b68ba61b0a59f7374ce886a01166cd6806dd5f42f6eaa33121b6ca65d67e80a3da55c6d3176ba91333b888875d24e41775ba91bf854880aee99aab03c86eb38f0b221914018bf8e74fdb0a742c223e93085966ce964ca14e395741b80536f1d1e920e319df73eeca741ee43f8a418940a0deeef29cb9fd67e4b781059d04f4540fbf1ff10dc4e000edbb074da5c6ea08cf2acdf59cafacbc15924c4e7f85ef0e8bdb7ae071c86a46eaaf721f6cd4441dbfecaab9de14712d1d69c529d6811d77089a83bf7eb56c520d10d34a49532869f35916ec00d7e734a3daaefdb337849a910f65233363824021f8b64cc58e3834ee3281c73f4fb8a7223537a2333d5b799bbfd5bde0662b3d02380ae8d207c93a3f8eb7c292842047fbcfd4e7f60ba7bf8d7f06a5d911ba899808c8f76fca933aca8df484ef82ddc57b935fde95fc6513fe0d5b58f057cb595a3f09c6fd1c15eeb6fd3972a973ee29c14f87b6ac46e4d6b3f6a292136638f1d8ea0a3e5fdcc6072032a25432c19db25c9cead92f08c000a2b662e2759c6e618733e5123698bd01c83e6a1ad4bb796b00eb4adec6bb65b5efa798d1bacef3f699689d7112c9a312b8a50bdcb1ad31618883e59881eebc764fb83c40349de5ba5562aa1f9515b21cd43e1b85d6257f76acc4b9be0bdcc7e61d83f7b72e99962a5f7b45e474863293b4ea03aed083a59dfccda6de734e7c1e4c94e459ca29d2b02b9d6649af50f402a8f1608334ae2610c25d24d2fd48bebacd42ba8797f137e12833de545a2e7d3befb896e976d617d9cb3ef49176c59416b511e9c304e64075bb344eb81b669bda05915b0d53aacc472ac51b605b0b00766db6d95958016a55a4cabd0646dc4885137d8c62918afe8d755dd3a042dfa9b13d86d8aaf0f67b83d9e3393ac68056598f1c4907e5ebb53b8a8326fa1ce75dc4d056a352042f009387ee15dde5fae77c6e28ac564242daac3d011a00478c51a0ce7ca0ca8a7cf4cb6f1169dc9b97b88e25e285a676305fb8dc3052273ee64806959c087ff0e6f11137041ac79f561addfebd3fb821090827b68dcadabe42709be078f75089da2e3addf474f07506d2ca50bb53824234969e73e3fb596b1eab7532972ace8a925211c4634c1bdff28a02e0009ec9e978bfd6ab06f23f0704c3aa4c4a19524bf53b4d2531e5c37564314c047c1975b03b22f50ae232df3678f857dd43f8b4bee16b3c9b5a5cf86b9380fa04eb68d941887a492343cc220d0c15b001b09319bd5d853c3e10dbd2da48de35cd9d3979822663b2e6b3c5852270fb7e4dacf5bb1ed808896e8fbebb68802bdfedde7eddac275f03ff86dbb8d4f9a9493478a1081a6aad9225ded35cfa9f68ee5c9a5fe077ee7a8f0205c9906c719194d6bd8df8d6599f4f1cf4aaff53bd296ae06f64f115e9b37791427830a6f45639d31e9b19e2007f607c4718ecb7790b00d3686eaeb7ef541a72e9a0fc5853958fe82c0ff941e77419f23c82d287633be257a8413a333b50301120f276add4e6784e86a9c4797e5c8fb66c97bfbe10944fd1ea94712e22b7a68c10cbb6971db91d5fc139fcd43cf1f818a3f9eba3e2b9e7a3670060483dc57a347c9162c9123e78206a4cdcd6a6c2dcfdb799f9eac0dd36c367d58edfd385395330f985ee2a87b682969ddb4b81222f373b9b82abc67f203734370bc469f9875d9f92cd0ed3b908ad3390c282c1a889f9901d675d03f1ff6fa47ad3c797fa5ac7e36c527caf6c54e699cf8f7292ecdc515c3efd6d51f823238fbc57b07df7b4c2496f4e9b1953aeac3d0c7e0671aec25f5187de6b345b3866adf4f8f1b86a282023c0d5b81a82f701ae025c5ffe75682ef41801e90aacdbe134b4642e75726ef5590ee0ca4e26e1da5cbb0dd57910341dbf8e3026bd2f16610a69d7908a2678a2be45b59ba845c0d9b3577014cbdd29b4d296e1cdef710f2310581dee6fb2efa56f2d096c7bae6a80e7ce4b0309b02dd7d67f12cfa220c308262ed2b2ed10ce4b48513452186392edc88ea2ba914e72e8861ba002d6f2b1b17bf861bff328c8884bdc110ff733b5feaef8b9e04bfb0f3af2e4de868feac07544fbf42fea87a42840276a73966a1d280e035fcaa79b0471e3117454faca04a29a49388ed3b1f28a00ec9ce68ebb14bae3e2aea596d97edd78dd9d7b4e35addf1adca806d23da8a014e022b43c7af60e13f97fb2c34c5672eff06bbc605b8373411383a738793011ec19d8f0055fcf8a02a7d71aaeb2d67ee0fdd6e7757b5c15c62f22e06283766554adabebb71d302d777dcfb64e0977b87cf054c274f531cc8b1414aa4d983a4305f9cc80cfa3412b6fb5d8b0c5d67eca9bea08a7742d801a7cf2019e5bb64ec27530610a770e44b4a437f5d53285ee4dffda843306753ba62d912d72c6df5cf07bcf95c5cbe8e5834d80f5cfcaff7b3ccc1129631f49ffece3d67b5dba0f91aef9aba480986530e757e22c50c68fc91b9a22cf7f3d3688f0e30b84511490e01db1b826ed933ffb26f36c73467ea8760ebd90ab6a198e1e47b92582189319a920f1695770349ba8f8eaa00211e2111fd8b97f18496e4de87df45ce3ed5f029e83b8c93375cca9d36aa85593077cb9270d5faf506f5a3b8e4e719d5009defd218e7a928bb6bf06930ab61be1dd8b67be46495fffe5e0ed0587eca123b823544b7b08612772fdd53764021d45300e82f81a2c820e5b31589c4fc472a3709d8275a458967f3ba26bf106978b9c7e3c75f206c6166e22990270b5d00cf306f7f911c9c52b131c5ca215eac4fc365a321e3218cff2c61845ec71e8598bbed24c792f3136ff06502d923caa5555a725a16a59850ca0bfd5eb8a5511b24b099560f04dc9b918c81f99f925f3c5ea076d4f88bff12cda2463167942afda41eb591aefebf15a978ce12745fa2598db56193330d967295909452254b0f279748da1c457a5c7a5d5dffb6994dfc8fc8da45f0632e1c18ac147d7fefd6289d0c6e88f295adade4b7a66d70e88c76451f93148596bbc3c309d12ad717b307e5f032a14bd2acb143f0e261d429df7ae6c7f34bd29d56e0133c13643cd13c146125ea1e54c78a687d406e977f4710b432a8c36b818f1eb269fc358bc73d50ec8b73231eb29a1ff44557fd2c336f42750d06aab8da254bdaa2897dbc0b166411c20c5ce42107252db3ed3d0e11ff4f0fcfcd5e3fc5d1305ed4b25e7beeb51a782d4dcb5ee7ce50d3ea8a83d7c883ed61cdfaf3d36176c9503ec180c543e30ee73e286ae99d4d40b900bf08b33c01d3f009c666e306968f85d4dc27c06af6999b1b6cf47f30da1d06e66cfaa60dae71e71cc5a3fa78eef8d7328556901224676b6077875753b495d86316d50a1a5332f50ab22c55b8d012bc7f17645c6eb19470a91138d6ae341b4f43f54f865720642b746ca1b4f3ad333a31b0553d24d9d75d2dba2412ee39663621176d30c3663306c0d22d7be81d35ad7dd27668b0fac847fe054015ff4a31e969edc8fe7016cff53b1b8cec43dd55895d33d0407eff324eee1c25b5f9ce2944e7ebf574682d4e52b650191bad186319d34cf7867dfd9bfaeb8d36cc4227fc54715cd054ca57514c2d066485d57d6a31edb6b2deebb1baccbf1f37d272b8aa22ef07cc0fb120a06e19462f2ef75e3806b28dfeba5fd455bd15d7b3dd9d3c561de18395ce66703afbbf1f877dde376335e3f25e825e39e9dee62acb155b64e0bbb087ea73c1fcdade565167caaf25de86e9ecf708349eef61f9b18d7765a5db3b15e526ee9428fdd475c3a3f0e101b76f5f1b05895ddb67768332a9714005a312e7d6d941884933e927f00f3cb06d02bec0598df71664e060d2359278bdde7bc8504acb7d69b4f43790f91129071a1ded13797ed1f4ce167f54423314c4f8a97934097493a420cbea02b31ec0e4b39a3c598db31991e3d77eb982cc3cafd96e7f19517e00170041feed124f1c0654353be96b7581aeb73023611a30206b5d544e8f9196b3afd170985bda97e3b49cc46a6bcd65dfdb4eee6d5ccd579958d136e7ebc2f2473f60839ba34023b0cf774bb805b7d590f73df6f080f7d8b9b0574c41a47ede3b71d22712a53365aac1a5c248730980a71b9c7e2ad8f7d7865ce9db37d41fe67a8df61453e23497425599c200cec18076383a5fe55dd34376b486e8fb37a861d2784fa8897ecd95f2039f1e3aefbd82c57a008c18977e42fc176bf747b6d7e19a1c05d31871d5747d5f0cabe83970423926cd7675c15e96ac42dfabae98d467d0a9c8418ef9a0c8df8687374ca39871fdea76169ea5d70a3eb58ac0d8f8da9ea9d5fa833e6399eb01eb8754bcbdf1b04942db2329b6836ad73c5fdde8fe0103c5114d39599bad00566823c29d7d1747aa9ef0070abf99cbef3e1524f73fa7d0b1b8189554ff2b5147b7cfa2b0de255a2f59659760c886e896ef49705be267244c93ceb1257bf37ff32251baa06549cdd8d1f51a6a1fa33ff62ff63658f2ea520a47004ea19508d2e8bd24439b7487f67b78e4f76da8787b1a83f8b7887da973b44bab211379921715620f5db7878f16a4c397bb5e36270ccd09b56dc12dd49cd7535a748ef6fb6b8dc21a9406c549a357dd05cfc4631ab4257a789ce5ecc0ff4db2fe92ef5a152f334017768f5c456464f0f6a13cca9a2835ec803eef6f464886a3bfa3800f4a6d3c47dc0c84a9af0780496208a4cc2a96ee341d21a01933d33e592ba127fcb08c9ef3a2d0359e575dcf189f8b7080ead2468dbafa3b56837ce57fb71d34a8d69624f61bf04a99e83bf73f4b5f85b292199e18eea1037d52ffbb355f5bf5fe0cafd726507a6c8e446cd8592f541e1663e8c0260dd630df93b35b7b60f62dde756942594427bad9b6875c8ff2129267edfc55de79c74557c60baf1d522020197c0e182db3975c7bc2fd7cb9c121d92cd50106892bcb6b6c67eb071673006bb87800230f91034bae6ab8b7a5e5e522822228c7560e6144ea25ae1e3feb3071e6d62238f817af366a6c8f6ca71a90fbafbfff9fd95fc9e6ac75087d7ff52d9247e784a11ec9214e1e4b9ddf3a3c18377024fa90f24fc35e57d549e95c56af6b078ec675762e16252d5029a7a17ee1f9924024344b81b76d50c72b48268f6bd787ad86c544cefcfff43cb07d0313abc53b6d1f0d92f2d593c56046ec25b8dde15b25296b5f34bdf4522ff52166cbf8e55ef76b87ba18f12e5a1537471dd533378bbe5a2e3915d67cfad9722f701becc4e497920c42cd40c4f3bcf78be4d43211b4f945fec9c285f415e3b42b5f233143f8dc92825799d87f775d49b208c61233cdbda316a1ec4355815fdca4205c520da4aae73f0ceea0788174861702a404aaaef0dd877158cd0558ca6771726d5b454c4d9299b1f87fa5fcd2d973f16a9fba703b4809656e5d34400b140f55a6e7275c49d2aacd94326ec0befe14a24acfa3afa2d5bc804bd3929c7cee56f6b1f4ba68604a55cc5c0ec1cac925252ea1356a63a8c40165570555ee743e4c6f0bf86d1c5054702c082dc74eee19cf2adddaedc65b6ee46e27564a320ad0ccd6aac44c459f2c5d692a3f616eb62fbd894aee4e0db1464da9ac39ae115e7e5ba275381d43446da49dfa14d7fa4c10958e70317f59b7ed66a01eaf5579bf16e83ce848e4c543be165f7816ce769b23af7c80670d99d64ba64a309b7febe4e16223112ce830b13c91bafe389da0eb3dcc7bfcfa915eaa1de341cfa118c328979edfaf2769a5c467751432347077f5a3a23e87c3689a25730b9f996ba255b87afdc1c2ed2b6c4a6d30c70fb5dcfa6dba2f912f4fd237e72ea4a70df6f4cd46b0b89a9b212b1e8123efe914780e9447a16da958d8148a3f7e5b76ebfec09395c0e73fb589d4eaf6f39bae4206337ce5273d33df27bca9ae93cb2e57d8e1fecabc375979f24fee51e72543a4f020325002003ec01d0ff37fbbfa9b4fcd9f662920e19e660af539d9e9d71aa74863fa925fa61c4e99ddcee366db2ad96f07bbd4ba28b8280d2487a49428491e9970d8430b74b311244c58496ba71d1b22f333613c2bec1ac888bdff6d517114108f5a64c86f0f361d5413cfe63fd79f9ff57e18bc5358752509ee33a1bd697a7b361afe9bebb4014ab5b45f4b0d58dcdaecb0c0de331b3a58bbf342c6fac972219f615cd610f5c20df7ee61ac88a6f34fe03b3ed7a603d31b26f6980a643d0d0ec3019fa7116e5ee8f8004ed8203930f1ce7dd3ebc50c84f099473c47f5c6c370977bedfa0fadce90b0c66f37aadb06d2c575f08765962cb3db0c4e685b2d7b0fc237fc5259d282408f60e9417302ecc4b1dec958bdf653cbc7c3dfbcf0098947d1c6aa5c2e97b79bf97d28b18139f88e55d842212c871b3cb2c42fe086a16338f3ed9b53d36941adac8c842cc1aa67ecef78efa204a7c1fe452dd34207ee1ed33033137c5d182d3226d11c16926f8c47bbfeb62179bb3550ebc94376bd68a5fba4be449979b41d6a1221efafe23266c290e76e87d21bbd5d270acbddb6983845606e31be272f0f7fd3afea1eea4386b4b7130daef0af8493d05ae58a04abb2cc6c8b436cb845a505deccbdd09f1f901dcea1401e0e79c849a516fbdd4740fce64fd0e5fd9afd9c0ab1123caafaf4d1577beecd817a8d5b84b51829d96b22c91d596fcdb7ecc8bbbc91df41392ce3f22268dea8c7f15f676680634e2e88be36bccb0d2fc0dc8a6cd50813f337cda6c16524872667d334d81da00953707780aacec8c199675f7cf4b18aa3670132aaa1ee03cf8e048cfef4e20eb8b219f7415d21151a1b82f51e2127fa9ed91dc82b99e7edec267616c2418b7c852d15ddfee40fcf52c88cd445222015e902e2fd52ef8d40aa396915070e8b29ee8de03c61833a32917ae799c687b1eee0fbfbb878bab1c702eef92ba881df89f3d2e3f0b7e864581199379e6eb69a765a9204f44a10154237ee509d5b15f846d88b77dc8f66ea46ab0f8afc82a879b38c04a23671c865e5c264a889fa0fa6515f2679b3fca3071949ec682fd7ba4152eedbfdcec8d93c42acc986b82281e396080acf60646067bee4df26bac41ec28ac94e923275e4281acc7624b9779e41e2bb995fdaaaddb4cb10e95828791c2d478c590f48a99b04a89d7d8af80114cfe081208ffc970624f189b90fb8f1bd94361d81124044fa96482103798d7ce8e0c4c8e10905844746fcbbf4f4a45b46c40cb689ac4a829301f43b68b3a0a3a3a827b742a99aa91cedfce40859af2412333dafbc3bcf8e5d2270a35f865224e693dafca8cae62837f6a0065798d4e44b9328e498fb88b6d354bd163bb8415d1bd3cb324738e97fc0dc6065d97c9f6a98c26dbb6e1c0995a67cd282e53f33c1a07d634febbc7cb9f7bd9859f091c6215c472407013a3f0f1662a27a41e517d999bb4a2e4310638672f72477506b6c0919bafd0ff9b62b8619269eb672c40b00a125d8de28a00eb13e462af49706f89d950f4e18a9d4bc8f8711209478c23e6b58e49b666b8223cb3c07336ae696afc80c12c7830e65c016bf07faa0594546c0f428657406a18ea4ec374dcfb4eaf9886740a0cbea858e277521b00fe5d7c97e046c5538ff9fdb59121ae19557dd2f4d35917fc073b99e7a4982db16f8efa1c0dc51927ad8cc6efa2e469ed3b4264eda4f1a30fbfdb5f1c1593cd49df938e3e1c85e450cbf8258c07c689eb9b3da8eb015b46b3ddffa4e968ac9c83212efa4458161927ad3845144226943d2a08695055c0ccea4c7faa60725d60126a3ea041c6d99df78135b2b15beb620b1fcf4d4c1f3da440e01dc5272fcf4f3b0527e8b6e43455fbd002bc346b21fc7aff44db263e5e451d4785ad1cec297e3d14f1a90ef2aacfb2c6befcf60c564fac3f0c8852129f933cb56ba3d3f060d9ac978286dbfb9a1fe786a224d78d54a64a7982fbf3f23e5a736b8281b7334826626c62b97abb126def7c4baf593caf429fd55e57eaa2a90117ddb9c39b54509763658eba0e05d42732080ebac9cd269960306a0d07e9ca4f90fddf6effbf4daf2799a49d3ae22d3c994cd5d9c70018032a000a14a37d2c12b54ddee6ee386e84cd2d18c190cfd604e91489d79beeca473026bcab4296c76ad59305aad484b099ca91fd605ec97e77cb612ff32445eae8faae74516ac950b59cbb7a69e7b0e176913876ef9cf04abad6cf03e9377c0715fc69ea2abef64c851cfecea7b344dae9e28397f11090dba006396cad4230aeed36d537950598e0270d96c3918a8bb2a88381187b6e3d54df0e33f5bdeb800702c9461a8eb96bd27bfb0f97717f60646cd447ba66ccbe3df86e4f487a9c09e907e8995d5399b711de991a0fd00fa36386be83b237ade3908f1c9b5b27a27a6b277b8106f681a0a996f2b03332afa4e48f42614231de73474ae880cf4f6717072b7c0db159a8cd31a383c2b408fb09051bb745a976561aa9856d47a134b6e90caf9bf00f83d2b680e0678930e61506c20f46840612d7fc8f182967dc40338a12dd67f27e1aa4ccc96ac4bd7f5fbccbaa6d680dc57966ec43dace9b8724231d7ffd0d8afefd4f3682a01d5a09677910dd702a019ee1edbf0386cdec845519944beceb19eb4df2a3e0108bbae50047a94b6529bca42eb2ff2a909b2cb221af39072caedbfbb6e4301939f69372521a8d9c4be2bf5dd65b14c0bcaab73cffb8cfc01b7fdd487bd83264d46378b3809e5f17978feb5f75b8cfd96defd5081969ec9a2439357769a6b783b90918bd9cbba9e842f884967bcc3f5e20e33d38aefba3084e94808696b7b4b8b2ce8971255f18dd0f13d6169915562ee93a5647e789f36b7e16258fcf06d242799d098dbe9e16fab81e2a45f4007a7bb35e7e323520e659717ef242e099e62e378d83457d696bf543323ab87772be76b19f194ff3d8a1047ee94a65fde55b809de3f64bb915952e458e18c125a4d55e4c6a786eabe34eede25f78613da9f60e1a79bab8621175ffaf665fb9f414f39aae23b964a48e5893065ef6290440ec571e3b4a30dd2a8b603c19e1b73e2188289a41044df1fa52a7d3b62ccb622c444e959e875a5d399fb183caebaba9f5753e96bc9faa9890b70e123c6f653047dba8d42591fdd5220dada3439f83ba3fc325b4c1c37a83522245640ec057bef79b643110d45e68944b13328112b24b10bebe716456e7febed1d02e372be88a74ba666af644767fad322e9ff0fdd074257ba5c4ebad852d71f4f2afd91e61e393a9503f8237a0c54de5579cfeee28f1055d40d96616f90b3f4e1ec211c20cea3ee31ae48fba22b3142d827cba7a24b9648dd4d97de3746a304b6e59d1704d135090302df82dc233250b7513256044391de3c939f49dbe6d571d0c1c12da7688a673f653526f37232b199e598a2de3baf21296eec308405bc0e1786a0f471a5f4baef7026c29f9b1c3ee4a66ed6243dd4df119926f93cc2f9f054d25b04aa72ec665ba668ce3ff46ad207d3c50e175dda2daa861ef1da2d46e4fbfd71fccff7dbd1e052aba4198f0f1fc9a1dec534449b0aad52ec95d94263824184b2641055db8ac74635dafb171188272248cd5a1c760dc842d6ead2e104089827812cc0cd91bc826f7f88c315c4143090eb77b0536993a09e2c26d8b09833b05afd3e57bde3b4bbc519b7ef4578adf23c35458110cb54aa5109c5ad014368cbd5293b418b58b3893aa88e5444763b9fac3bfae293afc04aabe6b9d67bb0d4cf46bc9dc9ab49a201b094fb0fa00c79e6e7a5d681f80766e24166b9cf664124e736ee0e818550c55bdd80934b3513b3d5e19ccfad486002fdc7593e7e111e5ad6606c3fc020dd45a98b594e1d47af38b5bab6f04fef4593e95f3ba0f230a7d26124896b803935126250aafc1f525a45298e7435f4f0ee6bdcb9a7bb327515a50d820b8d103bfb53214ebcfc632a54d6e60b0cc8c5668443f49d0664f019c630e5e19b8c89cd78fee35bea0afb0f192f3e3644e7eb2bbc9724b88ac039023b102f89c61627a2896c7ac4ed535369a507660e73f5fefc9c8a75bf8b94a125bc2c62ab25c2c7581168d476f21fea804ff467a65af7ca7e00c08aadd6f42c7b782d983e53a9e9f1004dffc4641f19aca66d7c5469c6c77185c41a61ee5a796dd48674d0e94a20efd5512f91cfabb257021af3f1a7243c5112858a45a3b63817753f67253cd9de7c80e73344dcdc360ffb75e4d4c06a253bd6754e2e498935a96433d3c0c01ec349ab52cea93c2c6510d8f4e3d172f473b76b22bd0e734a1c1d5a4cecd24095fef2ad2bed5d0e4d7a3193bc1d83ac3aed1e7f4a8b01122106e1ff61ff664915460cab7b20404768f032da3f18f6150e0ba23c31d133f753eb039cbfb3ba64ec43f158e1c7e0d91bce0ce71ea03d7f8ecdcd767320a54f5f0f0cd04f2c01398cc647df86dab554e6229a54acee8bf8cc03fdc3078c8cc668b6e6a5851170ab16c5689addc514c8ccf880f5974cd972d250faff2a7ead28fc7441cffc7cc67d2da9c2056cff86d37f1f4a1f8b3de05d3e64f2e91c0e083709dc8f517f1f0674721b699726cbb21a24f3e36e59d0873ab54e767bb7ae2d7b6405267d4711d88e56a602c6ae63192192bca66439f5b7dede4f1b824e33e38fcf95fe165cdf77bd40ea52c1b28cbd2549f2454e3ff04f6146131a609226c2a29ed083516ae996d64ba2f4c3ca701759fc9a48f2753162e9359a53efc41375af405169a049fd735a3a6ad8b6db30acf7cc4b362d2549fff1d1eba24171248b341ef9278b12564872d7b08984d54005b1d3bc5447eb8326301af7d44dd7efc6f14a42fd07c4fc80297990d42ce5a68246f73cae709bc7b2d343c992c7a1e51cf7127c21eb31bdc3dc70bd712a3c4fba42dd7c95d3cb195794bf626c70e093d17b93906c5a98a83f20387aeee7e300d7d65862c3a63d6c56d3d817b6894b9829f873adfd62556b3f8b1f47ed244852d628f38a2d2c210f8d14e9cc4f958b5dfed892b13d26e86a71ba595d526cefb0dde7a8ae34266859eb0fa26f2cf4a79489284ce722d51d439e10f8e8760dfc00a81ce1d50de81ab9450b3cbf28b4b24448d8ad9e94e11a5dcfaa134f9d396c784714774b76af61dfb49e230bf9fd26f64e3ac8ce9457c60c997e8b1e1c8af0fba799013d5b607f78fd2658932487080f2e372c34b9ec70101979dfb38ac2c311af0a29dfdb4fa84043b236a7739a80c3437147e8b69d85ef8180652c20f767e3bf999d97e21d64aeb8dadfc113fbc43ca61b72d4463f51af7892a03fe7bc112c8b3d3ab92137528a0995023476d5b0d03a75005e132dd438a729c9600619af34a3b85612119c8fe0a415e4c2f8722a92c063806fa02d9fa0ec44c6d1c26d81cc978471224dabed6743e2961c0aace3d17467f6a3ba75fee6055d0965d1951683ee3f58850457c4bf83804418b2c12d3c75dc878d70b1ab017b8ae2e9d48f6117030627e2d61fe1e40fd0acaa85f6c236312398224f53d156694de8a03da117b61aa6b440eae0574e1e8d54167b9a52c644cdcc637b81ebb49b7657741cdd6bb04c0f2b303b5959164966d7f4cec5d34d934917326ccb10a1eeb4a99125aaf130559df11ab5ebb0e6ffe7b795490d8450cbf61eda9094bedcd64a945ee2f08bd53f15bf0af3f6a8ee4acffb6fa287f686aea6c875a4168661dd7c1ffa88d5ab6048efe2f49a92f8713e83c409d894e722d141ab6f429938a9567a59db56daf391dcb27d6a187997063e7c1293408d955f4b735822403084f53f60a9b8cf01857942854a62a943de1847040bd6f70cae00c311a717af71acd569ebe447cefbe9273fecfd696897f98c980c22cc1fcfdde46f001086a4dd2b5273a00f39eef335ff4e11ab59135ecf8002132fb27f05c472b3263ab33bfbff87a9c62a63aed8b2cd1df462af3c01b6f61e500eb96861d46c324d4e9a220bf4f12a1915791d9296df69989400e0f31275d9fe7073a9ae78587bd8e2aac0ff1d052945e8742618a246ae29b7efb887a5dcc92f58df1ea9af49311dcea9610502ff27c29860b5e41b518b9531f755353c5302758ad17b54b15cc18d47a1623932fb3ccae9e92e4e55e5563aa8bcfb5c03098d9cd9412706f0a04f64e842c345b1f58641a2e3a9afcd1766a13327d3f5006e95ed54d79bf9d3152550e43e86b6e4f34786b031cf7d854d011d609ec2eb199fc10791b21e640bb34836b17f45e1a947ca5b73cde744e5bd800e52680ee1ee6431ea1f4a0b5b88bf6bca8f2952342542c21990dc8c781eaa6b649e14bf6d321596a6b19d78504f54454e0d34ffce2e1fedae712b3a30ef6b09fc14ccb3409849a84e283ba00f235a40fc5d7a01743ea65ad0674432f3ca9dd36214956395d641db301acae028ab0960414f67501483bccc3546c0330cd5ee8d58022f6c62fdd7c3ba1d94675b7a0c5829750ffd78f101d40ab739ab277950efc5ca4172e750b9db2c2bc8c3a564ecb561375e14353d56bc32f89f3e21f66123e6cf7ed099b0051c7701286b47a1878e93e0aa1b166cdc366a71af0844b00e8d33876f9ff8dc4978b60e7038a88d3d90d80215dc33d5794ba8b741f59bcdca03076a7b037792dbf8b97908c037f059e1ee05bc21883ed93d6fdfa3bafda24c96c2b3e53411083fec417308aeff288187fa09c51b2b17aa6c6a18b9cc5869f967e773691ba02fe7c17cdf70d778a74250bc3fe345f73290c413c8887e1a0229d99e0971a1cb67c4111fbeeee83df47e1ee54faa7f6c2550b2ad3eb93f6efce71528eaa1b1a272f4d3d24c464169acf41bac5da8266121f791de06adf13824f2f8eddd1c5f54c4c6e121c8bd3c3b9264bba469390c97026f44fd8c519f06a3667649ec6f58ecc61ee09ef4196b6b811cec66603b1eb9d4cd004ea618982a637c8d7b84d5ff222c758aaec45a9783c2eabcea4749623d7d4625fadfbd66a33642201a1883015bf94051fc36070809714254c1816d0810a8b6219a65b9cc2bb0a65039b81876ae1de3c68af07c602b02896c249dfa29c72b2b265d42cbcce755da1a12ff59c51f51a38b33eb5963f2a60b086996be0a80654ae1d62cb0c6719b6df004777f5ba6aa71b11476559c7bfc575c40dc920ac6ccd92246c96712946c48b05b8510cd6ac185574301fac4f03c9e278cb0dd1468df7accf367af3704e1ff34dea057cf444a9c2f7b15fff05e612436703d7a6b7f65b747109cbe1c23c7d9c410971bb8f3266ad642269ae44f9b9a294a39b753ecce2b6bf16686202cd5e7c86c943eebcb0f35b6dbbf22fa4b5994e8ad89e56df93cfbef1a45834018ada0e0eed61e7d4212235e2dc392fe066373aeb2cab9311e0e09df8f7bc60f21dddf7d68d9aad78349659b7004bd3299b68f4f85f0e052cfe79e4ea2e39c8c7adfac89d876a92b6f1ecab60484bc6e7d804a86ef2c2edb614ff848a4e01f8de9cff03a7be0d157d970d9e8bbe32f408c7482444e62a655e9999605cba4e6fdbb1810d789e08ae14ebdb3cf5b16a48184579731ad77e01d9741316f2e71d9fd30daf5aa74e5d928adcd91a2cf9a3eeb5f09a5aedb37ee50fb5929f56add5d43b5c9a4f8a8cb99976a8c7345752ce635cb196d503f371b5a545bfcbad10ea0177d60936341477dce82d9e191ee140dcba963772be5a8daca36bdfb37d35173e73a0896ebdaf23f08533ae7bc78a57447b434e249f1ccad676e5fb9a89afff5d897c463e5705619e0bd18aee5a8e9a5d9c19880551df7c8184410990ced917956ccf7769b7bb7ca7ac579dacc46fb2411c7674d46bb1c91403095288a206856454f43c59546df63d24c485e648147961fbee18be00c745af766531a71a77f33171b5b9e19bbeb7793376dff45ca6dfebe86f7d76be68bd5a7f9644f95ceb853cbba06d574ae655e5eba6bbcc90a5c58575e709a35740d8c4313531c42c93d358a85c59ae320cba8e5cb014ef5c96dfd733c608ec7499a5d1c3aeab7610492f94d54fd4ea346ab8a0cfa190f34b99e5d8bb7f84f596bb3a03ef1f14e9602a5494c4b097fab63b857961a42aa55bf0bf00e3bc38558cfdf1443a75ee387995c59000e550dedc9df215dd261a849edc3ebfa0e370fdc87a674dfbefd50eedf35ec209671406c63fc93f3d601945f48cab16c50a5d23d67f6986b8527c78c0c35adca7f19a0b1543e2475929c2acf851788a398973d31fdc2c87c2f122651ff65b8a5e03b2963a5eabd62a3046fec92d5ec6ad9ce98b869ee40a35a0beddf6f0453457cfe11d661b5160c3ae131a11daeafbcd57cad3e9c31d818d7c58a52932e181e4307b813de86c3e03d7f1d4451513977a4846be9ebc067316351b2ff35564b34438f2630616cd7201aaab63da088a74a74ef225f4645144804ed2aef664e24dc1deb9775a1c52990ed12ebda51f42c33052729d61cbb6efe933eae565347a7854b7ad7c8c0b119978a6b6feeaf31689bc6a61dad59469979e643099864abfefb35d34e88d7d9e3ed19258f5a49003fe160f5b683419024b9f5bf020c35d5292f21007d39b2f92a46931fd4fe2e6e09464b602a83547f8096069b8d867992021319f06c62162816c71f3c8277d35724321ea059022ca1871b21c6beafca0d4a65d397e6f6f870354e7adf644d7aa9ee15f059b6c903dba4e7fbdd74b346d622a96c8eb58780bc859b7479da986771388b33a8ef53503e622b6759af024d0e9a862a7add322037cd70609cdc30812c3a3d452052307f0dfa4001132b015857fa8757e9d65ba6aeccb03b7fcebb88ef390c50f31bd2363fe722e2d73f8c8a9f3b9d5fc49d6473dd18ecd42f6ca5633237f2ffc4880debebc1b0a54421786db3fa0b9b81e41c536edd65782a79bf1f619559124f8b0452cc3948fbaba137cbd853f40b4aef31cde9f1166cad5cecfef9436ae552ee92baed3875f37c97e54545d180f2a0bdf784e490b921d6f19708b34e581d82caa9a56e2a24932c1168117e121f83167313487439279fde9f3e186e275cb2b62190988e1562d473f4a75ade2b99c57f99c3aeb7afeb9645554e429f6ad490142b8b9b861340f8e8a8cc85281c957d9c9648e31486aa57b03de74f2dfe9bd7c69a489dd39d4bd4ad0d90cd7bab92810e56b03185da6cb6cebf9db94ecd1333a17273d267756dbca08605a5b94e145000d6f1682169d5825aff468a70b2a482e99aa064d3f109e404d0d87c31b1ae42aa1111425f92fb716798fd3dfdc771ad687424b79fdd4ff90177cabee76e7b3da418e3bf1a1ed03ac625c9fde358676aed72a2808573f53344091ac8f2866878c838bba6044f9fa056e45d039f1ae3477d46af065beec7101dc5bcba5bfaaebe4c8785a942b8bb9f274e65e679360ebbf829ff30996672a58aa8ab05f492643f61c82033271e776a462c9e193ed084bd9d2aa10c59cbf605acc1e4fe3470a62a2ad947b7fb351a7854a7f6fcf13a04a083d3dfd27207aaae1148b5cf2d6fdb91c64a71a9df940a3f5df1e8ff09b21962ac38055a33adb17efa3d3d092aa703a660e59e8473dbaafc443e194f9523159b900d5e1c97281b3695a6889982e56ef4232962e74ad39405113d5b368759e2dbdc3aca45294591015b9b2478edbcd97891bedefb35efecae4e05ac5fb8ab26790b0e0bd812c9137e60a0b56decfa9e86f5db5c9fb58e3e0ab3c916abecf0b3a0ebe8c7dab34564ad299ae0532e9f6d738b0bb24dbfc2bc7a784f68517b5269fce47ed89233437ab475812cac547fd043b7df41c659f073d261b2652bc187280a862af9f3c4d90f9adc6f3d38722801a67b3e6f1f8d827c9dac3ccee1ec4325fdee309f2bbb42918dd54562c3f7735c5b26e179628959b02c92bcabb49117e2e716f7e79851c53ba1e20117f847223a245c06e004281c94eccc52b28f45a1e55e5b5d23c6fd95c1ff02fce6387ca9a515300e37ab05eab8ae389f81dde1abf26cd2276898eebb2ef7c8b7c752f1efdb6d1f9c98fa240cd985874c34f39e42e6ed8e423f0bbfaef62d8f40a9774133b879e63a8886438a87be0904ed00cd8b75d865772fc2b7a5d007f06c910a2d53ac8c981a2f2126ab9ae61097949dd6f5e6fdb3b999df1de53c823734269545928b4d4eb742174f5ee01362ee6bbef134ed3b6b09e600338cd8d3a69fac3195d6a6f402746838dba0d8d5f38565734b54f2ca8e216fce6c216582074fc3d7d6c60fae576a134ff943561ce9a0c432ee4248f36ae7e222e265429f864f5a15a4adb30298d0f44813a4733d28cda5ec0255b0175950f4d4ed90c93bd02cb68736b2da8d302df4c6f7aa83f4b2d37e0357acf51b53b8c0adce01ee52e35b93c56702d3117a8bb7bb61a9f7a067d887e5a62b8d3d35ba85fecc3ef2e938326f24221fc42d51eb7115b15cb6d81bf85fa867e2040ac4e1c0b490ff6d1a7c77e44dcde46f9dbf49b2a9e56597ce7893cc7359795768176ce5129d7e31e6a9b2645f7e6b02cfc80e081179e87945718b68935c4aadca6325961e7b6c9c5ec5436f4c8219394ad41f0391c42e75e9f4870ed94cc02a5b8ca6c0919101b7d6c138425419901db44a75bcc20e9c0284fd49f57c629017f3dfff37b1230dbf9aee12cb0b611d61dd245efda11e05f6f7666344a73492b72658018850bf43aaf0a8b8897ad2068d7732682ad722702c76dda7a9effe140ecbb87b9f5b21786830be463dbfd4b58d5b94b3314713a4880d870463df0a7ea2ab45614637b6758f547e1d96e1bd6e14383d73c8393ea634bdb74c4fe412feba28ae0fcc551183a60d7b416f2c466ede2f1b2e92880f9ae880765dc9ded9a0285aaee29f6d31b7fc1dbb297c9f1a22e6e3fc620590a077b41d7a9022567ac43404639150375af55665c76f34dd21b6745b2983ed76ed083f67f0d5f2ffe361cedf985a47f5c4bc35b226ef05c4ccab48b49089accd54c73a4025e749ffcd211b6646843e92f5ff03b4903ff47931c672a66ae0a2e2af1a6a4a37b5ebc091855181af3566b989bbf2bce4df5a1677b89f51adb2954e8d509bb08761579ef5f0dc4efb342aab7f6f47f172aa907aaef446a1a9e57e897c2bb7cc62dd0c18c81bbb4d05a7df7ef7fae34090122211cc86ed95679753275f48123c7f64387b5fade0bc0f1fe89f8115d259589023d1fdbbbcd9f3b4c73c725912c2c30865c5582025248aec7725949d6560d33ab42e5eb356404a56d0a99497853d40bc73a4c0888b03dedfc31fae3d95ace9565a50c7aac6983fd4b92539bfa5d7a7e76ee039b0d08f468986a956afee29cf41d746db5e23470ac1c3988d84d10c18ac5697daddc5f990ffc00abb40235cb56c702ca5446de8b2ef73f717f0bce07c09ad07801f17728e6deb65b1605ed23a5ba6a4456b65ccfe591e823f24dcd3e3e5e36a8bf90f668bca242cb7167630da709c570ad5566cdd2022aadcb09ed12fef3f9e7fb528b32cc3dcbca650acc30a0de5f7e5171e36d0ca1a2c84ec05a20f1bfabb8267ab3978c212cb0327f1b9d77ab48c635f7dbfe83adec3f379013b62317d04a126f4d912596780f4e00a73975c098c767af3c90202c181ae7d4d36edfef7f64cc35ae9d1c85182d06e9e29347555873c9151510e4601d14f802bf8114ddbc8460eb3e0100ec37c33060578d0e9fe377082fa4e18029957a65fcba1b523c3925374b45c2bfc6b3abdaf49c2c82078c181ac1206990ce987b19746a524e03385f9ead02bfd6794cf7327eae1990c0f965a79681287f289e56d177341b25dd75d9043c6d717576ef23a1eeba6177b356c7b07736086fa0999743652ca04abd0b662dc90d38f4c11631ffb0c593deb61f265fb49641b9d88b4b4ad36bb3ccb1292a38205352b8865fb6334b14f3c43d38d2a0dc2462627369d57d9abb1472a473ff3de44f19745ec2c9cc31eb64e12e3a5f2f59ec759b37e73cc8aa465186123caec09ece1a5167504784a6f715005d7e564710f249050b4c66b366b376768cf9c44f6382d7f69eb986a927884680c901b10fdc1dea0e4023d11bf8c7169f3b27c940554503f41839f12012351e24e0ae0ad567f3eb0bd91728bd295d690198a7b8a470f121803e606236b1e5edd6b20241be01fd2de074f8f081d2269b7d3fffa48af5a507303dd8eebdeb92aef618989289726f38adf72d39d57ec4100549c5c3adb3339fb0c1a08a35284e3d294fffe140c8e63ac5552484c8da8c60bd6ee88ebe1977f9033b494acad5d163c1a41cd4d5d528b1c2e944c6d956289d16ad23709d18a88ba50e6c756e747a699bdea122d4eb6e49209519949cd92379826cd51475b23668cb815b499b694438c7aeb8afbc4cbc24210a3e19384c600404e99ff7aae067adeacf7e0fb8f727426bc9721bf03bb6f19da135036993fd28ae8c13c3b3d02661f4248b61b0ba199373e1a024c55de5b42ce9255202e128fcec7acc150b3d67bec1ae9c3cdb1c5da0e267b0449543e5b1922aac2b8a2532a640fa4c8af5ff0ee3a81c415c3b76d6b28a6c26d77113e0befca95c64e4d637a9e2db56f8b844307d33a04470923aafbbc25e318a25fa5ce07f2e65a439041448ea4aaa88717e5d03ac773f8b135f7570c088bac972391a11ee8e36f3119503148d63c65e830cf0cd5b3cff5547bdd2689103f75ea6fe8d15302c214c148e6111ec68ccddd077596983b5adde6fffad8b1513a5b62397d812288b74234d905893fe9e4734c11f9cbda0aa0a943c2c3f7aa43b7ef04663a3d591fe514c337121223955a833236b12d77520bcbe3a3bf3a2689e9efdbb4648de2a8a7e91c7ba8fdbeb1a0f7f3566a604fa1af921506c6f1b4249f511b480966d6e288cfc42771f847acd8155090ab5c676bec14b9f31b644968b0a0826b6c1e387c2e1d114ccac6dd4ccf9e05990d0e047612a74403ab666af9e485e24015e1c320aa7f4291a6830a244f3e1b611a9308bffe29721b94c3defdc8dd88df23a3312bc359595b0dc9060cf62a994de9a4cf89dc090f35d134f8432d87d2d0936d091c1300d543f5ca365f106f7cd985b2520c77268dacd01c66f8f7299532b6be6c1886bb4070041a4843a37cf7809dca0777b2844c3e92fc15d60d29c04c6427ff1ccf0fecfcbb7ecad0aa8caa75d5d03f0d04cfe91050b949954829d238817423cb673402cab9fb3279661f259d0c81f1080b1ec243cb1896556ee378e0d4917cbc7a40162ac18f8dadd3f514e2f2533f05687199f80b866749be7d08be046331ca372197e23e72bc9afbec278de9ce7aa4f726a7c9ce4787615500ee412cf9fc0690c8ce90940aad41285f914da5ee9633cb7c8696b186426a1f51d9df145739179c310f337902fc04b075892cabee15c74b87bdf23df544a07b69834b359249e57939afacb674b9184c893636cb9db9ad282d0dd54ff8a05484e1c43952b38d488f674cb010af3dd7ded72216cbe09feb696e6ba53f9126fcb63fe16d5932647115ce1c7de9a67ed9c43f3e9ff05afe12ee7edb3d840e4cbf5586e9957cd9d4d5dbbaff823e1bb250f8d71288a067c0302d2e3d0601d8e13c9f00b8d3cb6b088c2887006eae2edaeba5b7f9596506ffdb5aa8300156f8ba627d59d4f322f0f0579b2fe112dcbb27f960cfd3e032d3829a1604ff604ed8551876056e943f30d8a63ee865535aee4aaee34413622d06b3284de65732e0d0e3a3008b83b1e75b482df71f21620fa59182442f6592bbe8ffff3e67e726c8fbde76eb44a415574719c486c9aa0c6b310d62c37f9f2d566520b35e4d52be50acf584629b93dc04cc69634330581edfc492fe349f3a8458b6bf45c74f3c96b0cbf07c06b60ed9bb1599ae4293848dbc9c448081b4232783e264c9efde37bacfd117edd0e0df465764be292babbbee12aa81414719ed4bba108a7d0665b7aa6c845424162758769cd9ba72bd0b98541a7cbdef9c5572da78ee487b833e21ec7972ba6eb0fbf84c1cdd28121e1bdad8af792b641b5ed3bf42926673b1081b298b413a0c5ffcf481fc0af59f186e99fcf7375f011f91489e0181718d2d0779b3eea75a75890feccb81978ed56bf0076d885d7fb8185add61a736feba510269111a6f59f6a9d7e482e3d55e4163d7393a5be82fac24b9fc9561a29446beb962519d34e96c8d5d3feeecd7c9e1612622e5d4aa640231754a97a02aaa95ba9600b33dd831ed4468d349bc18d40a335f684385fc34bc138868d57d1b95cd81df7c1e0066d40a412e6286e9a2885d7c493fefd4b223a4ffc95d732faedaf6cd2cce6ebbc91bcd71d919c819aea7c8d2ca7c530f9148be1c577b658b5f43f2c28986a28a60f43e9f4021e09be766592d7d451b96bb01ab7b0678759daa80620c01f150fa7077298d2d3c9e98940966253413797e09868736c8bb7fce6e97ad4b34a89554f6b57acfcc169b9a1a46075267136d06f0a85f50e659533929a20abeb16fe216e69ada89e2f329fd6cdc7a37892653dbb793f0e19ff5e224aeaad4cddbb07a65fa5d94f5f26b1280375a4f6d5bd8ade46efe009d5aa331016400481c636d96858233c2663121f5ebbf2a4c8b40e2f3704412284499155509147394378f973b7d3c1ef76c2fd56a562ebfa5e8dd975ff37c8b2f701f094c5c979be78e78fc902cdbd2eee09bd21744539c8be3ce40a4b3c52e4f271036995ea4538604c7bc9eca8bdfa84d2ee89b88f3b04950caa1ab71509e5ff20c5ca6d89650de37dcdac1cb79c60d85ca47bba6f71e8d89e97f47aa7be60e800ca4873860d563386282b6615cef30f4c9ade364bf4f1d1899b9c6f7f7fb8786cd1a46155653a10d18538f2edcfa501ed5bb6ba982f98060d1656801b7cde7498f885e83139ea531460c719b128085d4b3d11bac2444c737d8d55beaff509256d83286fc9c3b5cf383f93caab28aadeb367410cdbea479de8c36f95483320636fca2590e2666b697b1389b5d0dc14165b70f2845cc45d1d011e263a47ffea1967c6391d0e200899180e99e3713d87a2acd6b131198859c04a077ec804755a002c599f33253c7d2cf0800ef56e85cb2c0de9df5abd4a3fec1448d814f21688adda87447c087d3b9d7e3bdad63a71fd7fbfd541d3cce4e3a3a26d7bdbfb6176109cacce808c315a6eec1177975362f7cdfcf692fa9bd078cc8f8e5d22feae368f55d52e48319caf5da267c3d20a9abe6cb3c0aff7d8814059599e3792e553584f091b8f7d89e30744b499b59f537d271cb2a1590f9147a12ed7eca998564aaf8a1d4f1d9d363ecb4a7e32be24caf344b7df5bb34635067d299e39b1485e86590e05b627636fae7a4de8d8699548df08eb856b300dedaf48358a7d3b78d549954eb2769dc81c7331e6e72c4597ec3f6218a458931426561a218b2ab25f11aed51dc9f69dbe990ccd62daa79b8aa0e341d7f2a0a28e58e427b577b6e5bf69bfba7709cf003f0d3bc2fa1695a91473e28c6c660a910f738080896b8211487b1d767f664980ab1c4f0cc1889c2c64b6c46675d762382fcb39b6f3194ba20d0082eefde5243412320f6c214ebd904aeddd62a70f752b53f0723c95ab4e3a0a33b0456eaf3316c49d274d38f615008b9aedfd7bdce6c691833c0924fd9d9bac9666f63f5a71874c29da60b2e0a90b0e9b0608c4920ecef96119250e810e701cec5d5107a2af1d1fd787229830dafaac151053af5c270d412ec16fa658baa11b8bcd122c57b27fecffb5380dd518624ec648bff93de98f891f2bbd12562617744f6e08aaf4f5c7724efbfdce706208d49f11ac209d811df08185962630145d21a3967d6c4345864cc7a72ed9ae4fa17aa7b41f0883bf882ec090346226c4e72f9d45904790ee30927b0fff16c6fc768a164adb49bfeaea525e92ee7455508d1db7d8c01f091c14df2c7374735d87b333150f4ae39eecc08627ba7a60a7fa19ae22007872ff813cb63a84f5c03273e1d5f6743841e7ac0b48560835d9f2adbbe2d9e7f57c7d58f6114883204985c24bbd1cc26a151f9998ab751279763a5862e9df5171c622cba8aa374d4ac49d86feaaefef8a7011d857beab2efde3f760f1f674725fecee051a6f66ac8f593fc986de72da84ede36ae05ad87f87ba45c40ac77178c169d8d0643dd53d65812b31dd4f8f70d09599e2999919a5cc7459311ea5557d8414a5b355954b918272c185f1397c4bde660dfff4f6ff422f703a7a764f08a09a44acf6694e27b4e5a334f47e2ff6d3800310841fbb873bbefa1cafa0e0b37bec912d3b6d6f2d7489e4db871090f5f571191dfed6add5b96bcbee28c0263e70655297716c7a52ccf1e5077d76ae3c287329fdcf31cc6329ae9298308b166945ef08ec70774f68a203d72b4100b4a4e606dba55273474d551b7571b06616d90c334fb32c626b362212298d9c485ff93a51e3ff13bf966bde94f6c5a43fb8e6c12170b33ac20142af847ffbd23dbbca2b85edf4abe4db3f76bc0854b34378ab02d69787ce9c6e47a6606f42d35d7aca0f99e6d9650f97c31df058ee4c9b5c7d8a592f6cc3715b3a3c6b3302aa80eaa88357f14a32e5b48b9fc1ce49b5ce41d70b173a6f0fedb882b524bc33c050538e1efb7117c62eadd2aea80c3055abbc1576a21b40661d6374da7db69dbb8e89b8210bd278611290f93257bd16fe1671c71d470b55b77cd4186dc083eac904e255ede597968f2990d18077511d08ed10fd04aa93e10f4c1ea060dac2d8822bb29a266a1fe328903c2bf3a034812843763b0d3b2c0e19705a964cb29199f4c94f725b5427a12c5679dfb3b5c5c020200369c017bd282f01d0e02b9cf9c4031ac07315a10fd9b423227c7d33d0867dcaa3b924bc9aeb6cafdf2f9819ba517229c5e1a431626a2afd7f472976b1773596b978e1f9132066fd10604bbdcffe86114bffa5170581ffa978bc4aa00e2188669764108862210ae2b00cfb1550a77de14de5f69cce64ae59ee27c91f100924d265c401f1a12b46cf913736999820a7c5297a006e11ae1acffe118b836efe6a5e86bbfc9ec6a4564ca86bb96ea79304196009a06fedcbe9fa088a3fc461d587715fcc2f7a65f011c22d8ec705a1ae53e81edf2e3e91d1fc43721fa5cb75cd132e81e93eb30ede8ea12373d599ed11146cb8a90ce22cc8664fa45b09d52e38ef8d1434fb9f2e6ba277a32bfa40b27d01e8f47e16d0f9d619e74e3ee15ff71216a5b7c34c2a77d7c90df0cc567d7299a560285446cf3ff518c79a76b55c66d583e4180b8d16abc557b5c2dfbd2773cd8216e5a64edc1663dc9dab4fc9cf62f17041cd640d7dd0d746250364649bf2198e3f8b1232ff415d4a2c7ad1aa5a6d37c628c037dc37a95f6c14bf14c62d58a2bc658791b4746c0549138ea09704e401af0a536487cc956574a939141916b13a3ee222a4af7b82791fc317993751559313d8a43580bcc959841b84228bdec2f3c2abc8636e0b2213e9aeb2c53194a182a4bc233dc156a848b30c63af7218906fce43c3eba64720da141e8ecd3b8343ee0c5aa9eb41d3537fd966b343f62c0187f75b297b4c224e2a2ce6ac9ba44cb868a3c01324ccf0bdb72835bd69ad5111b8f034db237bf7956016ecbf129488737b3fa6dec8df999731d7af8dd84919d6b84458c2f11ffc4be2279f9bb80ef62af0f762f25dd91e4f1a49b8ae6dc2dee354edcf676c49c397733f3ee5738fb88ab1725b9cb4104c4a15f1f6b93711d51ff5399160600f03ff10a50710325cfbc28996bc58aace66354fcb0a983611c25d68ad1b2643dd95d25a226cf643c7ea667543e04f8b9a7973cdc957c2abd409ae2515ff038b25ff9c894542594196ae76129048746a107849af7352c22be4e2ec12ae916eb8abeff4c1e6397018e75da1cce08427982370c0f5265b420594e0f8bce1c555721dacedb3999c3183ca28a0b817f0b3124d80549086455e3205949832877a9856ffe0838e6a568e742a83641ce9b9d7dd0df636c7e09033936f8eb3f631d6e181af2649d7ad856da6aed4212896f9c48c433c9df5e97986007026813395e1ea064a51f1242809413eeaf2a46959d737b2bc84c4153a1caf86ab48ceb0ae7bd599ca4a0bfdcecaad4559918f54bf7436a5e8509ba70304ce885753913d6ff15dea88acf33a7351fa42e9a69dff8fd0bb082aa930cc1a283ac56da72dc99d7ba643d71c44735cec7fad5b36e468a705e43540c6a6bdbb68f00752eb0e2ed3c8dc121b835026153d15263f7d56a53a443ab043d6fa564a8bc132c307b3779c5d435e50505ce78b8f85f15793a39f2bdd7b5b5c45ed0b6557f9cf3781e8c170a03c17def139b12b6d3346e77c3d38145480c01a68e12380e1a8f3777afd0ba4f352d080978cfff7cfd42bcac012bc0a19b3aef22dcea3514c56f0e183a3512075578accba9c9d13656c3f262440d663cb4130021323dd4e6fbb57e05dec2abc7d3b674fc1e9e46a2a48ca418fca1718d52b4d06e82d8127b0ebd0830502822b408395c15f86a5842291167a3fcb59b20877eef60870e2ebec8a24ef57aa01b390dec23626f0b0dc8f5ca771e7a17e5961ebd4938cc94b778aa148a058a5a96fd2bea96da9598d3f62e0b804008829970fc26e425643e9b3db744bead6dd859a4b5caeb2a45006d3ef46ee7fe464edd646f5b5a055c96019faff0c951bb169962246682d6b9b6419437a088e713c31b7d2c41978cf0ea37f70382f772dfb10eddd4725dc71c45c025a5e6d3f3c18c69f23bef4fad669f70644d22f09c01a9a9bc7528c0a663b475e5f7bc8463e923c2c2ff4b5cf62e047e87f39166acf750fbd9abd7eb1a07105ce5c2645fb3b4383947cfa1e73e4cf563dcc9c49f6a79601d99a67aba8212d3dcc26838a208da1b4eb124631efd12d27dd4cfe890f4ef8a5512ef7a456c014fddba1f3f2c57f90fbf20786e7f7c957d16b4ba998b816090bfc0782a491477d825d98377977ba2d2abff018326d88da9c5e5866a63c02966a25fb65307acff0b1503e96788d606a928824b5cae8bf9774742310bb8dc4e9e7f6f798575f9296015f84c140811b04313c8246c9ce4afcedb447d277d14153bddb44f8ee78dfd0af8ff59a5f34796aa78c48f83575ca5cb5bd61d644fc5903052238ab9030d9c3d6b00a53157d484cdd261a43ed1c63b6241fbb8d96deaff500aba104cc6899aa4839077e3553f3e3458fd6f6a5450a7f3e5aeded2e1538f58a1552c026bdedcd1b0c228f76705cd71942edce6058b93366275f54612b53560e2b741c48c618d10a9ca35463dd61d4b1e82f9018e1e157c3e611375c367bc0e9c96cd4db6c6ae6be958ff1c8c69ac17ac5800ff6a1a6c73111d2f713523f0547383bbc2adedd39ccd6200077e7d30d3912e27a169f052b2d9a810f6e6d9d5df9a8a79a0bea27269f50203ea2efb7e40537f75113f3ade8a9b8dacf7582b7d9afefe3a954bf4de731c19773729a08f1c2bad852a23efd6b855575302c58a133a5e3b00df9111bb9b808a920c879e201916806daf642d3314c272e88124d9c1dbd9c99509ce9fa2c88814baad89643f2e1de9035f49ee6ea08d2b59d518694b708b8ae6db32bbb238a4e1065993ea3b9fbe9ca0c0926f20e46aeee09a5c1831343343a6f95236ebf2168d0b7ce68c285afb25b03e2bd5fd07c963b8a08042bed437713bc1bbad095b6a543d159d7e953f9fc03339783edf4effa471c6672d4e302914b414e476cde7e72e3ee9e2e93d53cf70281543afafb090fc9e61f3c457bda5da9d3c1bc770f04c473319ca08bd6d1f3be7cd1b70d4840e11e1d826d3eba013d0990a4ef02dc7a22a208c0ba03b2cd634e7ee91b6f6fb832d9781de06a2227ae21e85643adf337a3666603f6552f633babcf8e92de9f061b538a3f603c94276d819d0f5669ec93b8f332c4fd414f57e52f0f8c96bc6904f6c10c3256c7ad69a7e6d9c44eb0f31b4f2be9264dd800d5e9bd412ff2b9ce8a1b8b90ac30751cb682a47379f4251616e9614f6d102d21ab5747fb07a579732cc8de8d79c7b8806f6dfd5dedfc683c53e7675796ae799585951b222f0182c0ec8ec8bf8c2ba205562c861af59c5b5a373b4518dbf9d76a0e5701ce82f2d21d53975a9d8a6c420e53600881ebae6f1e5f2cfcab1c1e5bc6f2248d131ecf73dc50015a7b649d1430ad3caeae12daab8cb8cb63c0415750c7bb8befc2cf4e21aaa8abaf99d38f2023870c0c757ad0c72b8db9ec25a7183e5b2bdc05aff4283dfa3af81ceb3a4ac59ad4f79ad7f1fc4fe863e3643704ebc3aadc0a5239297f637a3d326f5609b9f69a1ffb4c16b3fd2413378317216db571032c7809600074fd8dc17810f1c1ea2f2f8885ec74908ec7bc0a47bdefc8bf5a2376869648ad75a40f9c62e9f918311da6afbd1a0537ecd16801eddc7431f1864175c18a177908d71bd746c3b667b4f5ee3b7c09f7d6204b372804fd38d16016c9e390cd9922e9a5e0f335b7e4d0a9d936326f52c039979f5eda0d559b6878c9dca9447dbf83e27dafd4e544e83dad5de937480db40c545ea7b09d89a9e07af198ccd48a10dcfc86f9eecb44214e6924ffc81977c5fbdf1c6bcda03f62a3b88942a3224dd82b20e3ea62bea5321340046305cd3f6a4b7ef9bdba905c3c3942c43a594e461d35d956e42ca5178c6fd6bb4dc010b38e05d1a516bfa05f9735a678ff82d65647d79f1729043016e6474b30c8bffab77773ba001838de4032d2c5029644b4c6f9d2a3021277d7501ff494fe8f6f7318ef74ce3b1b259a69ae75ec82decff4528eaa9156ba72a1c4c5239e5e266132eb8f12247129f13a90e1dedf0f427bbd270af20026ce2f2af5648dbeb598de8aa4a23fa82641102e8710530347df831fce64b5b9a165953ff1c4f9295412443bf553845578a460cddc16e0e167b0041662bc2eefb8dbf3b139857bef5bb3bb1c461fb9a7f73dcbb08fad6f312773565a5e78bd5de82ed9d62f8eeb1d8b68f360bbc0fd08ac3e9ec476cf54fd375526fef25b50459fe372af69d57d7a68ac20ee633ee085c001a6c51ab7a8a5e1875f51f4030ca31e54655b53334cd404a935d23f14aae9ab18ac5f8b2d5ee089415a21facb09a9679a0b33653de0612acc95d9b2bda97b3ddb22018ededa75980731e06a653d716c7d238b24bff9fe062840bf2100899f94b55bef0617bc8a47c1752a6f59b57821de7f897ba33e4c7c7379ba4b5121f290897701557f51b73fb472d073e5415d609ea6b0b7b932b8016009bf24700d4abd458c9944358747baa9d86c1a9923e9507a823a9e74b4ca2ff1df61ed49cc5a26dc14cd960c50f4bfdb2415320ae112afebff1971cb96764e8a23fe9f4c78d273568d0366947e25d5352be1e153558adeef495844cd366fae8be2fb792260d5e1234a2194131ff4cc3bbff9811eca7d485368c6139438c8ce716402e7ebb318577007cd05a660d54d3a52442f6f6b5a22d943fec35d6c8187ef77988f70132c33013b0c12baf47222ab12f4627aaf423e2c90ea71630d3e2d338f3957ce280bb49a113bfbbd4879d39d8823c3359d62bf69161606f37cb6d00a30be9fe420992d6d351fea50c33790ac3f5af00a1b21e1df5448efdb7c1cebf744994ab60bba5cadc6cccc95af544e3eaa0739880dadbc7b2477e8488304bad1b8f275d0df9273ff5389f3447eb6f9854ee7ab3b9b5b700b24aa814321a99ab25b78679bae1f9111859ae228c297cafa5601d557c6561bfe194b8154b50156333d834b07ebe6d226dd62c4d39c2d348e71f861e58fe4efbeb71c3e7f2ea6ff25e6769d3f424c206398ba83b4899260f17f784e990999e6d18227dafbbdb2fefd0afbf9d590e799a00660e15783309995dac9744c9b9c1d227971c167881e6955c132ed670fb0458d0fce76b4a458646a4912f6bdd2cc0bccdb9995fcedd2e12b05caa5b4d0b96ed20994b512e89c63569d6c5fcb657a3f373eea685b6e86b6a7cf66c884f49de1b0bc8eff71e21a1612deb403f06af22f90db3fc360d01f607d78b7beece129725fcef5af050967d88197473e3167a4899d1ba7b3d125a9b756e45a4a80bc8a9c059946ba73a0b72d25f09759dab03c92ad2e91bc7784084caa07369d91d3cb2bfe5f4e41ba1dfb7d3362805cf22bba859274fe8bcc954f28f236e5ddbf46ed510dd1d06298e4a78d830ba07821fbe3427ed1e3cccb29f3b15db2f0b60b90f47707f6d4e326ff06b53aaacfac62c05a9053c7eed7625ff36fb9864b1735321bbb1b4f87cc8273327f74a89f320d55e040b564cc04ee7b90912a00d498b6aa279467ed61e92dd0ead0a8c333c64cb471681ee9154190efdee6ce9b6bf9ce357b58a0c2f63133c72735e2747824139d877efef83d404107718ac8fe70d85d04471985e872bdd4078e047ccfcf7413cde076aaaf6455b7d29c3b4dafb0ddbef6fc9ddafbc8726bb14b783fef0ecc88efbb385161132992b01de0e7503eb81b330d0f98ede735d2ca5729ffec4e9374789431c755cb4f2bd37758516baadbbb603e1d717488473dfd62ca83ff4496c780d891777693c7759acb598dabc233708d006e7f4b354bf416bab948624c2581fc366bb49354cbcb73d8245cbb2cb048432b336fa3ce5ad627894c69dc7b8a80d78c72aa24f2139b5c8b2eea1f403a0ce3128e16e36ed048076bf33a97a3ef222bdce7eaa9c62b06ca8125bc1e88e2330a94df6f71bc07be375509f5e0164b72def1b4848142550b44aa1daf9411b19f84c71fc4479397fd5f0c87d57cfaac3a6c6ddffcb71c4c7aba9f0646af1f8b15de70f4803fe1ea0a0b6aabecc1e098a30bd2d8f7d90f19b77ad5b741ced28b8d2a31da3a17975c001ab084a59f1318a291cbd67ddb954534f79ea29e1f4ce39754ff25124a7b4dbaecd533f024a593c0606ae6954d03a525554ca1750bf7f876b86e952afd84399273bf7acbeefb19e064611b14bf654e96ea6d13ce9593f87c045c5b77ae488824ab5049183dec22080a6131cf2610cc1a5be8c48a27bb043d8d4e85f2cc02d8ae0981e76eba89beffcb44a96fe0d216260859137cc9205b06f41db280e2b55060fb5f03d76c949f9ada7cd236cfc6ba018e34eda38f1ba47744c3c4cc95d8d1f6d917d681b2b250637c913bc698bbcb4814e8dedc1f0817be881e207ceec5c57cc21babae88ead20c7fde598b1649b444cc5fafc100cfa8d3d1b0e4f59e365bd45cf76b6515ef56ba8e68b901637b7d00e9f00235012b7db97408c01692ff6612c5acf378db2932cf2191bdd9527431bf82bfc1c75c27a02c51171a9eeb4b37d261a3384f4477bd5fab179e408077e8b16d3bf44c4ee462e9b8bbc6863f8904d1e671537a93a0efdfa2759b1fe9c28d8c2a10e8c5ffbd3a5d558008ff51c20295915c52471a23661aff505f948c304cd6afee8bf6e4a797ce0ab05370dd6c30edc1edf19a371843fcb012604dab4b8f4f32ca12c6bb16905d461d9439bf636486f325d6c9eab0ca4ef8e6a285188c1302014fd309fb53f7830655a9a08e758681cabf0e6e858f6919b33098337fc288e0eb3ca0ad3e70eae5c5d56d90559a33b87a16eb5c326efe5bd15bde3da2f3e93dcf957e681f0bd66a75d99fc30440c706e8382ffa134fb513df1478359a0d9d5a1e2716f904ef0fc15cd68fac83156aae8f381090b5da22f12cd01f1d689ecc6ef354b3b9a5bd187ef4c3434ac1486f6ae0dd0a9d5a18d313fe2a7029d54ef4f4363f883af382790e093abfd2d488a830b187eca68c4f490e11df05508140c6d91af286b643c9be5b0c31cd940fa164ef05945a5b963cca7259b4130da8b1f210e970e3e45a265f5415daffe996e2b6fe24c3561897ed6a28e2d9adc4f9690c0f251d08c44b47c1094f0fed37e1f61a837ac36fa573a2b119873a7d39c19aa9c1169fd161abd0f19ad7d5b73045e45888cef8bda71598f25ce851cc9585f55d81895b39f5f23baeee5fef4c5eb4ee4146e03aced164ccc55f09d27ffb77b5c1055cea835a49a22bb167f7f80e959a870edde4a6ecbd6dbdc73b53fe58455006de5d5a5af24b47c2c015556d9c4078950cdd21e869d1efbe204a4729e3b56675a2245869bacd5db8c0f461697524f4f64a74ca197a0bec7a796c1f87124586495f416847524595fec12a865acbbe3c4999a85aa976630822b541dc38f64b57247d55d45437599609f6a066be89a32a550940abdbed029e63bb86558d08a8069bea9086e0435e3e3649be12a767914b8b4012610a6ce74e4a6cd5aa33456d208df4f545f287b8305eea516d4e7a7576d35ceae76c376e38462e9703f3004d1e4d0440e444b73b1fabeb2ffc1bbbecdf0f02e5b7710e535e85fd397b70a49323a4002a56419dc79c5eaf923e6f7443222fcf6e7cbb721268fb4f7b5fbfb7cd9f4f958c851291e7812449905eedca608de2c1df81dc3b8056e2e2bea478a2db07979100e0d52947bbd010dfd6fa4d89ee4746d197f2e78e0927f22b8db35a7829728e8e6021cd1462e797ae8cc22f24889c0c9dfbc9c8454825cf87ea10eae506959dc47f0b35b489e3fd68a089d931af7052ac481d7852fe99193e5735048b3d765bf4eed8b5f467344a2e3ff429591bc88ac2fe8000cba6c36f4baaaf99b3ca195dd693c3bfacea614e357d23263f364925ce9eeafc5c73de21a7eb7be4efa88ec48bf61404937f361c711e400dee99f752530320959e65f423ba4e4ad65a64919492fc65fd4e9676f3e8768611e9908ace58d4b9e4f68eceefe3aeb7bddb5e5dcbf7dc085b0840a04eb81dc0b7cd26dabfb655484b675637b3fd3350ff06a998619875628333fb504556ff139cda55966c5a315c3e1401fa954ed845410c31f70cae0739db5171f28c54768275d309c9cc8fcd2253172efa9fded8fb51f8cee15863af0d1293c54b323ff29122e8ad0006c1176fe0b9b7d95209705171484603a7876efd9659c430488c766d57995d279c3171a7b4a28e562039fc3a008f5a96ad40737aac2661e780b86fbf64fac455e11651de21bf5d22b0544c5738ae55ee78c8b1830f8c5d7ee592017b2e78e1e6857f75e3c4417b20bd7c9ab30548bc602fa3bd9435f1ab272047e654539b5c075c20c54fa1d8260a6305c0b989354f3048478b4c5708edd41791d22baa2fda5f0791cc101daea8b1891dc30414a1c78d5e3df06609ae47895874f124526a0ad4b15b806d05c3c934f6e56355b2a94c7c8ff032830596086f94c5292ca768ac84464d70fb2f1df32b7b8304c7c96413bff70f239f6464367b2df6b4bc86b9efd2059ca0ae4537f65b47e7fccbab91c9ce954eef6af77ad25712340ec792efb9efbecfb8845165b54d7fc5f598132328c95d4a439ae4cef781438cc30b652b179509c4d3f7618b58c43289c00cb77bfa643cfd3857c4152870d47675628a58266df68f8f0814a9f458ac72a38c95cd3ad49ad4af594662ab0641e0e9a340d4229f345c67d9287a184bc357d23e2c856816661d8365493d60c2010d4d5f831243cdab2cff0ab38e9e26cc681412e8a57dd76dbd82afee35f69f5f3664343c9210e148bbbcae46fc2a3a31762997e1e87a28e64837078bf2c0727b2a7a28ba67c629fee266a33300f28c3593d54e0516f74bd836f5d2f940117c00a7a1dc78ad496f5111ffa6239a3855dd19057d23974defe9143bfba31dbf9a51230a28067f3488c988b8de7efcd2682bee1bd02e5f768227722ed96bbd83189dea1aebb3164b23ba9af0ea1c3277c1dbf3f51e9ca0f0c54c4892934eac6c730d0bb46aaf6e7fc4a53b27dcf9949245a6a748f0fe0cbdc62f8219525015b4c2fd26d456cb21084b6d49172e39734cd0a4d6a614c1ff0f86536a93f08b5912172510cd32d3797baf5ce9c7131e6d5ae5369a13cf0db43f43db9f6e180ddbd3b23f5f981dca7afd21cadbfd9de4ac459d7e4efc9018383c1b0a3765dca36945a61efc55632ab0aec09952fe8c857e831d3839214a6886ad9851d92c295736bec3e82dda5075315f7a3e0555bf6a65008063cdc658552c70498ec2ce31d49678f04ed4212d1090bc0751ff4777ff58f51df4150618b7e24da0621928f11ec9ef716b270e91914ac5f66ce03e49aefb07f3f6710516bec9bc92c54a3f9ececb5dd0e2d45d1c94fba4be5cbbba7a57ed15bfe54bd9725a92427a66bb391cdd0b236d01c5b3da58f0b94c16642efe028b37e4ada6f647da92f1eb68306db97a851f2fdada2960285932e19906474d386d7b6ab023968b145895d473ebc8b98c4b28f5d279c797e387ad73889c46142f0de2f7c39f3361caf7ee0f0ede4339ae5b23962a1f16555829848e5b07c00a02242af839ef66eb023e60088b63a7fdf81984b3c6ae5e5f20735ef8bd515cbfb88dba9696b39710064efc118d0d9961fb630efc34f5fc583894206cead7a7f30fd15ea32f6a77517bb93e012f636b32344922ec0da440cb85337e6e354385c529e5d96187a1f4945fd6e9ab705d285b59d642cc5e99fea1ec9df1432beb1e7172252c6e4e7bb97316ba949c5b5d90b2d0a0dbae40d0498efa551ddd3657c0a914547ed882240fa138ebccab40804d47990193f20f92f85ee4545177d122b14a4f937e66b0ccc1cbedfdbcfeabee849cea871e95b67f7d6493d735feb574e7cd450328b2126518ca5cbe5303795c876788e2207652b25953e9194d7b117ffbc72e44c04daeaa6e09cc00d43fb9ae0c929afb8755316f0902250aca3132b371a23fcedabe9be0be7f4c371918fd656dcc68c6055521baf3cfcbb9b6dd1814bc2e33fd6301d413e74e0032d1cccc03ce524e146fe0f3339c252686419002692654f5635b200eba4c12d5df5c2f99c4031994ef05db7088e1cf6444bb48bb039c1e06359f3067b18fe107a901634a486135ecd7941732ab5d676b8876765aefbe291e5a96149720b14f414723e27bcecd8dcab86cbb5428c046ee17dedee85b2471c312d440657993d6c5bb4a8127e2148d8e06bbce8445eaaea52657ad18aad772480f6fb8514eb00e5caf0fe8af264cd8efb507b67ebb160a30b7870ed571bfdfa7f643efabb6398ba353dcac6eee6b98774d98a9ec86e99db9540bec84ed1dafe2e770b7153032a8bf3ceeeda7286d7aa99f09f7757f415950b4fc044a752ecbba15baa534f797017b9c379e0d6ac9be1ca8ee94f83e6c5dc1c4d6e4d191ce5a38c2a42de41fddd284c5a4f9375d0bb1ec320a164bf1533500422675d2c1342cf302ff6507157cf39e403a9d6d8f21f6993f2f74bf49ea92f48475ccad8eb0c096af10ea3965f6defadeb18fdf50b7b70c32be4e447a7e9c71dd7fafda1b729d1cc0670d314839ac9afd0306df3933f952e5eefea6ad62f031d30c5786f5007b05a30fe49a01067d0d803d11b50d8e38b2bd6c53f807ce69481fddc82c96cdbd6a12261c3afa2194d8ddc87bf46a2ab4231496c1e603f11d231c708dbdfa9650a9c9b226cd547eee0b3c245ef484b034a88f3647a2b7dca4ae73f49543efd1c20508a693334169cd9e4bf81c8ac7e90dfb1aeb5f7badf4e5abbcc8520edb434d23e0a5086e3907a6f0a4d5b943f559fd69316ed659c06ab14f8011402bd8363cdcd8e2685034efd75a675566605f68c2ddabaa5c7a92a7a2fc13ff5fa01beef0c9c8aa5a7c38d9f43598663d58d2f64a72ecf2a83925aba49d32836acbb2609b1756dfee003322578538888ead29a98daed43c275d9e3180bcb8739369480791be0ad61d9c1c2dfb75097dd1bc8e7f257835fafde36961a73d96e5620ce9d8d0a369d0cfb4e04e19939e48708a9cf1b79c4101ba05ea6a3312a9086bc643cdb89b0f7cf3c4ddc1de026ed699928d96796a531ff20ed5b0fef5716c54d9ae9b359807d15b9f8b6ce83023ff491ffeb3f0a5c208e97670bd2ee20831f52657dfe61775740e0a613da2627d941edc101c35d30e717945a59a033ffc6864a773ffe05454e3f1dbe0104f165643effcafe5e6b4e76ce18e351b9c45ba7267effb547ecf84822f6195c7ac53051115bc7524160e6ec57b8dfabf8aeddfd7ff2190fabc523f54fba0a4733bf2dfa900fae808caf5c2bcc19e6f443f2ac5f8d4122afa0647234b3259e8fe60e200b2dadb3fd96cb1c0a465594ade947e6b1082d505f7086a08319b903832a95eda645b2352699505fc4774a34243baa8390171e0c55954a7011e177fd27df45bfff8b7a14366f1eff407268add5ecae30efba8aef6e3d763b1022f511856d8e97ac0be6d0aef6d40ce325d27febb0c98a27b5fde445386dd886b51782e5580e6b4a8f10dae49dafde830826d13bacd65ffda7a94d2534a42ff15ebd2479acf40d75700be9bf0ddd742bb1fc5b83919b86c499668df4841247c58ddf375a7f7639f91d2ec9c8030dce4f583ff8a9d358bec9d738fd0415ba7a6046b97601b031d292d7ba7e94c9acc4d90b17df4ee69d6ab5d4cc866809c3c1aa8744cf12d714b265ef7895136eb7ca89b2ed4aae961a3559184bb64270b3506d1deb8cd0185ae32d10b3f6197b8d99b050a2f476e116e378eaa368928b9b14c5ab2555389828a49650e364cd66f08a356ba5562ba2b13b89cd32c38c96109d51e3e75da334fd755c00024f183aa237a24715c9ae320ba24b7da742a3aa7fce90d24c368e9ddc48c7a1082d6ff363dcd2e1ae74620b9f97d6e922ea4b9c1683b4462d4771ef02acd876f4064009abdfeac0857706f5b811c54efd2af179d4a60f2cd2c0751b885a8ef143c1bfac2a90f09943736a57997151d08dcb3e926c8ee608267e275313f8088e79aa5dafae0193da7ba6fc6147f76da3d4bff3841dfb7f928910eaff64d195eb0240ffe2cdac02dd4fce0dd87dd5b7f5bcd1aa4e0ecd78cb1ac83407eb397848969bdeeb76e78e8aebc94330d3eac56581660c418b4b4e874ecee01da2af4f47fe0c044ef6fc3e91793c85d8c07ad6435d36b4672e9d120e39bc4e69e84e0030037776fae61aadfd07eebddf937fb81d8fa56bbfbfd3bc44dffadf2a30cb9bb446d482c543d4e6dfed48338cd3956b970e7b1a2f5ff49eea3ba037b4976472af92da38cd77cffc537d474c4b03021b0f36614da123029fe7bba1a9a526e4edd0249bc16b92e2685c7e0115c435fc591cb1da9a2a23fbf5f1050e4ea1a14be655820442021ccba8bda0a29e419ec095105d763e339485922c324be7abe68a7c7c9073a63ac6e60afe7a9eb0fb1982f30d26873f517ce9f376b2e983a315083dcb04bccf90a83d0e3174bbb7d3e6d0fbe42fab45fea063730305af63ef3ed62853feab51094af33ab9f8b80e3de8a8515c3c1665f9432ea9a2758c5d6b7b0dddbe0da62c5fcefe6c375f0674fe9ca3b78d0c5af0e1afe2266b794964ee7c6182522c6e60e549dd9b97e753f4911fbf52e07f4a6ee32d3ddd6d25b32ed968e7ddfb2cd15e8ee4d77b1af9ed539a320b5ea19e1ab87469b9240479018c4957db20081d33bbb94183274d1d87bd6f0fcd0694e05577c40755ab28b09c08bd6befd2c875878d09d92291c1d6425553b1a5eace56de91e6e5d75c5d56691333f3a89d4025ddd186eb14b1dc1ed48b7d392b3be42e8ec7df6a4181e4d5316a0d1a1188cae26efad32aae82e54e51c9ab2806677384aea10433d5c00de97b73acd30671b9ce268957800d8fa680383278a3db04cd8d6d05beefa2a2a796402dcef33692d9d81102c8139076ca2fa62b8b165f13d64d6871636052e65700aaceb7d3c1ba91472ac039fb3cc2ad44b2574dbfbfba66e6ef8bd7aa325e223418def3ad0fb99d44d25490bb05eda788e2c34316b5e5070ece9c3da7277b7bcaa885ba5cc17b91951ecaf8c49c34068b6d7e1ab4f2e32ecb68982cf4e0bb1ff3b9ea48e547baaef31e8c7cc6b04816a715d618a477921339c65c62b7288976adac666ea29df3b19d55c51ce71ba76c8ed4effb3fad139624a730844b35a0e2760166e381fbeb7eadcfb2057f5c614c13d7f9e5cc647b953fa9d76251c7fcafebc91e78a56c8b73ab9941e90b69bd4df09e7c5b009f36b5a7a7c356fda2bf752ec9ef98fca89fe6d6351873b3c4644a002ed0c7d5ae0e346419f4afc22748b8efd9c67a6c1d03144ce5d3025fd85a7caa4abf27dd7715b401643103898679385db58a0a846e35bead3ca5e34f40f4364d07a14b5178065b73af229e1f5f870891ea3c1b1db2c170af8697b7fdca49439c268c465622904b77b3ac6e297d8ab355cf7d2a1390cce1706fd30d43ce7e671571ad1f66a1427ed342f34f3a63250e7f879c95331313b2602c2a4b83a0cfc27d99c030f84527d3cc986104a11315c5b1adae93485f313c01939bd35ed4bc8891498a05035d6897dcd830705e17263ea1d3549da1fc55772d50288752333868d1aa508f1e68eaaa3ca74ecb691699318906d7bd42f3c6778247341142feb18943cbefd8744ac079fa2c2569ed11ccead078a5e0d4ca7898fb783edffd05eb0585c254e5edf9cbb3610468073b5b59b8a3a5c82254ac979ce6b02dd9368451cae9948dcde5f05acd0524138acf087fb54a1b7a57e03a2b1e057f560fb01793b73e0df6955c0a86a560a57aa621b038da07c94950c11de2380f0bec37acdf820ec3d6f4b9c99e3e660b2ecddd9b017daa82530ad27478552ca4df43149797348f1de30df1dbef6c72f491acc5fd3348bf3fdfccf3d44e747e9b97f97ba0f2f3ad2fc68f8fffb911e26aa1de10792c56e0dad70ab3fadf899e2c826181e1a3c22f3a317370c136f0d0cb61e977b095185094bab1aad36aae7dacf8e95ee91de561baa79cdbbab65126d6caa37436ba908648bc3bc60b72b1a2c7e156c138d6b0d253347a0771afd8f54f971d2792a1bdb7aaf0811daf8c0a86ad02233b1ae2157ada1ac3a1cc2a1193cb1f5b5a3c6177e7e80e4c80c09f7cc4cd55bd5abfcd5ec3824d607daf8e15384806284fbc582215417f2ff53b79728be364e254df9b30f9d79dd6a09694e8341d7d48e39abf209c67a29e7e5eab50f3df8c3131d465f24c97ee3e821c79fb7162b3f2517054d2b096debd8c07ad76e0f935d8ebbd8f379ab7a7545bce5a26c30752dfb9b57b5ccd61fe131da4e896a0ab39f3c2f5107ce0d006d1728badb5634d1ca5dd5e28b44d4511dd7057839663ba3f061b215d0b6eb050f4605a5b848c91724d0506bb73b17f22eb48b8e3932a2d3456a90116fbd48e4a3c10b0c9e84115a404456635948c9529f17030e4a707ac3ebdae715b7c1685f8ee47304c3fcd33f16feb3545b88ab39492f21ca457276cc2dd3153551f8694c400543b71472e70cde48eba442b91a68a6b18fa7003c6775e77daccd50ea31486a7a6a093090c72dcb322c4ba802d9baad92e64bb34ad055d5e8086e22576e327eecb514edbd4c0fefd5beca522f327f509eb0a743e568df32f8a26e439283969c9c692e79040e49f7c6d04c99259cbdd12ac310152e381bbb5d4ac2b521039f2f3ed1158930b7e56416532286fcd0e679fc137956390309cf53c90233e32e4c0c6d818f50234e812935f933e70ae165c8e069e96a23f78ff144594ce436dcac25b21e53360dbe292d723458b88bcac8e1efdf7b1670ed00b7c10537e6f4610db4c283f6b5aaf9dcefc4371b761387b590e10f5a697292f5db26c72b5489893241cf20025cb1ba7f2d8c65292da2654df5b40db3276675710326b5f8e7008f4ad6612591fdb34e9ed50f789b8119767c64660e6f39c97a879d7cd7f4241736b96f2d6b5ffbba95f0d350ca5625de959b7ac4d89171493e5c745c04a55f95221e8d89b3fdf4988293eff9d04c185b48872adbea537d59b8a6fd9789703471ed2a88f34a2bca9c80d7ced6802236f50a9e9f77119ceb1389299887cf890be3a5fc41e341a06e05adb4a9a46ee1ae98f8c014c6e150857e5ec542001409afdc625acdab2e54f63c8795b9a2ba23c57e40dc3d30d5ee201cb40638ba7889cb93ebbc6b3de1ef3b10d675509e07f994351b5b64b143fc36974da46b01b7e48cdbab360b233cd92a1ca7544b0d0cc66ead0f58b61ca47a67a3d21f2a831aa2c0e6b8caea66606e0ab1d803637ef67fb7fc8576a6ece78367152d8a24736dcbc1a82e649f4cc0f99c382fa1ae9c06f3f84571dfe154624fbe82049a8387f786d9965ffa0dae2baf375d00fd807bb8e0b24119c1f81bcdadf6b64a5c23885be246a99e003d083681dfbe7570210fed5a02d91fcd2c2bba13f154b6e26600b8ee3a86626d3b8572d7e5ea2fdf6583af138f6abf19b04ad6403c073a26f50c368cb86b8eae39091008fbbe0cab04e9c94bef62b46a3f55ab15009d2807be11ac046291eaac5d5457506f68b25f7746e88a33a69488cc31332deebf52f89d661d7dccbf98a5f056c7dd7a151f605998ef91dda49d99278341f06592caa12ce64bfcac9673f1ab4e62b00ccb73b7482d050ea5877daddf7edeb930c0e80646f603643460033b2f88d5856631ea09c7e02367d406fd4b599f278e04bc760d766bc64c722857009b160168449020ab01047b574376b14b3f996d4a7edf0ad01d7c5ae463cd88cfbbe3cccdd513d493e208446f16b41ad6c3dbbe7a1ffb3bb92376f8bb83be959f89d5e9138b9cb49057750bb5df65f8d6e76a0c60dfe163cdf5e1acd6223e5164d9108f34b2b5ceaa9f2d8a653af8419f04d615be5a536fdf371e14a07c7b1833167ca741ebdca9abf94570bbca88488d720812b412e2f68d530ed2ce2fee1b5339f8d03125d7d1f6890eeb7f46800f71769587a1b8e966d0b338c136ce992fb9be78f13c5258594f170a92ac10030398108a4c19cede9578e6c64ed1c72f3f4b7feac571e4a67e718210f00191e7ccd45a5cf16adb17888a6f1c9a3d138a51b75c720f8a81e8bc0e88c50e823291dc8228522b64cb00f6d8393a7dc1a090575e74a48f83744ba915b984104dcaa7f88cce86cab57a98736b8922eb462be67b4b0c024ed05caec4df9a3c7f7beb5cb13e923c54116d47b8583fd83fdd2d9dff5c825cf00fee7c165dbf327decc77528315cb8fcd2cdded0ee0fee8a445a8e2c2540dad0af7f4f2b1cb47abafea833d7ba3b5b23880c9e5153ecaa1ded2b72d818c304d8249461184a12d18dcc4c570c7ddb4a487444bd6ce9e95ff44b6ba5ef4eb967e6476822fcd8c03a3307f5e6bf10e17443408608134f1652caba34a0bdeb2270434d2ccf13816870ee7b52884dfff4f0b6e286e43b5c2b3774f5045156e38c50197989bfd60ef2c157688e30939ccbd6222ba032feed1aa3c087eba2f77bb4abdadc824cdf4b5f296bc0db6a24cd4dd5594739f594822b775a456e6211f8b2043c813ea3cd84d4ab5f1653c466e84dc1589529ceee90b21a55c1b1ee29fe720cf72467491cc9b1eb32f290736edc6166de0b758b4807d0da743859854b1987cb52638a4b942860273b2e72c21e79860d2b27ebe659c082306ca90128c82315792489df3e52c495e145d9e184489fbd9811cf1f6a0f6b17e2c9ee73d12c9b086f7aabc6c237829fb40a4581dd3d04ed2c17f6d04f686ee7ce846f53b31c1e3e8d9774a446c9b66630bb0b93a5d656e81ccd581762a0537fbf9c4c8f7e846517044ed8adc08ce25b112b4e1a8a621a12e917f097615828e380aeadec40c4e5e4a596f11b83ddc162ae75c0abf85792473a27cea4ab74baf5042facb28cfef85e3950d53508dbe87776caa7b518b421a93139422c1e31ab69c62f64e66953a206cb11ca0a18bd59ddfc6969c66292fea5baaae91a91d3b31878ea758be071cafdc15514457379449c67aa2aef28aca83d9314766cba48583380a288dba885631570b66adf75fe2dec8312009e4b15de7f3bec6d9c2ec3694bbd61d613c2a637c6c042d530d4d3d1e0485b97ab530531631f2bb78937e12f800c0ca1365f4a8831534636002c074c52d7ba54b643d60a3888855c8c097a8dbe50141c8241862c7facc4106d8c0e44d10b9396f68dbd88950fc218fc0cdb4f53e9241d83be9f6dfbbb91100c95ca3a8a0aeffb3f213a8b58337343382c9804cfd929b0061c29c727f082fa556c48084ab792fcf2d7eeae1f2f9ac34219e2b04b76545a3ed15547d4cae6e5e098c517677fca924debaeed20177d3aa8636f8e2fa3533727915ba0ee5ec4364c8830ef7c721f0c05b2dbe7b644e47c23ae71345127327bd0412c6c7404fad7d357217c5b8b0c01d41e97b13b3086dd7da8c9f3842a160a51dc15c90c6b0c3f7e0335d9dda10fcf1b96927bc6ce9deee008de250636c55056e67a115a36de891ad97ab07c00119bb78151903ea8db2d26c87643d213b9085b61013b74e18b22ab7be1dc2e4efb4c4acf76256014d2513caaf39f3fa20e8391af579e20195655a4548b1623bf76c70cc4eaa8bb7efbf2444185c228a86fb38af3148a1e1fe03f8fc150bb3281b217101559afb7c59b3bafeb6901feced58bf4107d92a3f2a93199643d1b82dc56ac53c78058fb4e31855fa522f0959c93af3c053c18c074bb043328d4f879190ab1e950ff60ab4f2cb3347d97e123a66052b1fff0d240e16de2dca97a843299cde46a63ec96b7520f77d003ca0be323dc7c6849898cee2058abc183e08c86a3c8b4686601aa33b3998d9ad984fcd63ad44ca7133e01aab0fc06e77177c2dfe4cac5a5e11fa52a20dececdf7e8e822806cd8e85afa9caa66017dd9e319e8b2ae9b5f14cce50d05ee8dc090fe400d0afbe98b2dcacf97f9bb31aa19cedfd155200a506ad594e60ebc375f5eb3aff1d056fb8ac48444dc028653c7a20f52ac1f633365ecaaf3683a04af02b3a56a62383574a4d4099ffc4b08f59ef24ad69d2aa8846af6cb99d62776357f661a141a69df897139885d75af9614a9e83e4d27e408521a22ea83c53bafe990361a4db0561541795404371cf8098621d1ddced780e37298e1ea55f7fb42ae1cd50f7e87e20b7d03dd027fa8c1dd3d891458f3484c8fb8b0312d7baf7ebddeec63d37a212a321e38e6d25ee337326fa658423e2eec765c43f17ed7a3baf1749d2d6ea2a2aaeb3a013799df2b71d6ab74c998519f963c0e9f5a43ba7cb93c500e2151dbca1ac1c93036b033d37f643fa7fe2e87b990d3ad2eb79c551bb2702d677475b5fda850068b0d1b13e91898008b04645805fcd9b764fbc94e68502102fb04852cfc85de13c5bbb893e5eb14301947a7dc9afaafd0121a0d65aeb77c69f9a2068bfe3f2a1c96649768f35d3c0685a39e7c50b9e242607a9a942ffeda5f71983d503c986a2cb975244698cda3ef0a42bf5e5321a2cd04781f1b2fa69f8b7d97530a36394ac25675d4d311a269269270b606d3aa49de47c2c144050ff0f44d2a3fef6aa754990792f5fc9b40f3a0ba9ddfc4f96abe764917c5b861d86aa24cfa93871ae3719e59d4a3e2abf1b967fc4c47ff871907d9c0c7308b2877bda51732386a56e59d529c11da5a0b58c88bce137c0697fe1d16bb14a80681a428fddfd7eebaff5b4a828cdabc7213df745cfa6aaa47fe65704d46dfd676840f6e086aa11595b829da02c3857dc554ab947cfe06ec81f257efb322b8efa4b2dc422851247094610134c401dd16d8b9fda7348ccc7630762fb8fd4a70daf41884452ce1113c59adca7b85397e2cbded22c940742ca50a64796cc71a70b02f9aa78c379cb3a532c6eab9e10896364c1a17a8566fbae9519f5b2c4013c04569bef744bf419f57768462bddef1afd3bd7014780ae1e68728a03bf8515d755c28cee785f6cd0c5956250d6ff868118725f33860f20a697566134718253e0a04207f92acdc34bc88650b6ff091198dea94d0a511fd00de2b0969a5afe42fcd057f1496dd458c99b12ef47e7109849741c7a2b5bdb86a157aa1fe33d3551e81f52145facdbb97a1b2bbfd48d24c8ea711aaf31a5d06deed0f27e4fed5217979ca149af43e1339c92da9763d276d08e26dd5616896a8ae7acf0465be6985f8a5cd9599ddcc1155358b41a9785060e54443df6f48736e198e4300d6827214afe2ec592cec897aedab0554b8f64f38f12ddb54a6a7cfb7c70a63f06dae16dab7c18393b2294e1bd5bfb9cab19c9ed0a4bbb7a871e3e4cc42156371f55eda653246d9f3ddeb231a8e482d4b6e67a354fa32570b69f978276574496ddc507dcc4124937f04468e6ec4ed877639c7f9d2364d55ad94fe99cf3592e6f81427e8945006cc582b2c431a952a7dd87265adf3ef1fec8381ec40f288feb0334274320b2f9dbc05abec950b272d738dcad6753620431fc86d0f40753532a4ddb49857d63e795e3e7e1bdfa9a55b639361e411f3da4e9f283262c535e21fbc71871a2ff01f20f74feeae8b13739fe9c6b78fb5db1c347c329a62d6d9bf786ed9d76162b7f94b8d177beb31694abdd37611660946dc23e18448ebb5c0c6a826fafd86f13c0f775de5f8b7ea5ed379a27978e72e1787a4316a9a74c7a1890057a738d48e51ca54c5b9407ab7099b68ab57fc20d08f1dbffe9c1d10a4869b5de63e78c620da99d01e037d168da11613d5f9b20bf2d0e337239e70835e07ebe2cc492704d315fa6d170c25dfd2bf4dfb164dbd8a07917fab6b6aa5b1f9ce93eec77375ac9ead930f62415732ad71b76153d5ca65fe93a96e7558c8a7dd9429b0017339ef78059442e3013d7b0728b5c22b232f267b1bd288897554043b417c6c0632341555b19ad431f703a41bf6bbe14526b5dff03dbdd5360cc92d6d1b00d5b85d90960530107fec5c5ab131bbe41fd28ff89a111df06d8e53b6d1bc17d8c3b26fbaea92f2b5c3873b3c148d7f1cc5203fb5c0605e33a8cfb63f1f42ea90330aa864e3b98ba3b773a49792e2c6fb3ba9871690038a14a4be9652bea16cfbb643af8cf691de98307d74e693d7d4e81d59ab207d53c54c87d41d5553da93a1c9476badc76b3b26e56a000a2033e61e9f0a70ae4ef50002b1bf03e9fe2ff39a1aada80b57b3b482c16e287299e00a4cb7696c2d14344f2b3b59a89ae3da79d1b261eb3cba53d450bda64022f9bc10a2a11accfdb6ea003a9fe2e2d072cb58df952202dbc03fe34b2262c2a809534ed46df89db8c7545a062b33f77dd819af1d5a35841cc7b6a76e6ad5ffaf4c71a10f7a0a4a53a3d200cc678373894cdcd214535ebd653fc3fd043c87d32f8a936a8fc9a82c2c001b5e612186a3794b455b955e02f5e1537d2d65d30ecd2cfcfa840c82599cc0cb54db3e4ef6669dcd5ddf95f12fecf81050f9cb22ee1d3cd6cfff9d438ab10870fdeb1408c8139e37f754f4b7c7e919f0369fa0120f643c221bb752a7694706b46292031a4d488ef449ec38c8924f6c4d5494d9e0d88d0809000ada6660b06ae00f0336544c96feabd615466c45beef4f30b773f658a1c167d1decfb5421d9ed9630c59dd078c695363d5f25aa0386387e6d5d2432e8f4ded5ee25d165d1b7f34369e97fb0c66cd071f5811c4bf1aebece22a0f36e696dedd80962720a51adaabe8da7b8562da675f24b6c5aefd25d3f17f70e1b53cc77432bc8feb46e645b1f4bf9de6ad0bcfddb0c613bd6ec60d3c4f042a5ddf60c4979f80540f802bfc3b827768e7446be4c012c5276b4883867c656b5cf73186dabdd4f0546558ecf96b901ba13f63b01bccc175422bc26918bd476177738ddb2d59315ee9ca522d56297cd002eb61f6a8cd77a04c5d98df8690a3374e07c922fc47fd106d058d83acc17096b80812a351e171290cbc3e72295dd5b4870d78f8150dd7ddd6f4b10de6cab73a1a88b01c129eb0dbf0f5af39b377a7e9eae1e7fcadfb866b93e7367606190ac4e018ee10886857455764dd618c4168f08812d7934945798d10a6771e5a3e6f93996de0c4fc1f1f8134043fa500157dc7b779dbe4e030cc8a88abf5c39930566f67ce0b6e1b572dec8a856cbf107f2cedf60f805e9f11a5667d048615ece7404db0ba8e86997b84119e79454d48df7df04219efec97b6ad7da1d82e2e054700789186b8eb8a8098ac47d524372d0e0528d001bcadf55cbef7e1234cbd87c6a09eea864df94cb426cc488446612513e28b4886dbc104fa96ae1211f759356a2652f1b343356a1771ef7ef9cb4a3e527e9a109e795f45b35902a7020a4b2611905f888e277883ba85bd77618d76765c8aaedd4469084988afc5e3e90f090a5a9ba226706e8fac5387a9a7810936a30bb6185599364b68ddb87c6a726e3479616145f90458b1fb9be4408bc5b986624c1005e231c616005c3e1b8c6eaad39003f9b52319da96bd9af54526e7acea1ad1d377c8a3a707731053050c2a954d3e2f1ec14c9ec8942bc5355219a6d0b75957799232033e52231d5229b3851d599cbd5353a20895f5da9baab986d59f1da5388d6ea994b0c3b8681c1beee42d1ea520bbd3217501ad1e9c16b666dda4faaa867c59a50a0e6a32788c70392a2b73315cb003c668fb3738ce088e93131f0c7e43116ef486f50e2d52818ffeefb9295813ca587c4389fc7d12bd6510cf3de1dd17076b2d40af9e5cd740fcece10ff77b86fba950816783ff67a74d241a1dde642a0fed4d7465138190b3a052047ef500d93bc51fffed2691c04b561cd1a4339cdb5e0f861202f641847183b9993635391c3b963a556cca1137fc4f9fe087a6a261c7d8c697bcbc009a296de644cba19d2c9cd3bad0c7a03ae5b6f5be496c508d57f343e6b5e7cb53ce528fa567683eecc74942920b7647f323f0434aa2de5a5102debfe6a157ce3d5cc073f739871a8540256d7137a55e9e61becf803943c3b3bf312ede20bf82fd27f0d2657834c420ee7b87fd4f6d9c226549f359729f219a2ba3debc59a6595f9f63c10637467f4627e8a1d72a33800cb3b0c7e63b2d8503e8a38cf12e0db1b09f3fa7f59e779a4007f2abb163ad8f7bd42e1f76bd57d6dfe9d32611fcde3c9bccc9f5ce2fd18aff3daa6c023ac53473191b27e97d15af10c44030a01b3ada68ccd20f0b63e6beeadf1ffae4a623261cfc23c744cdcdf89cc0a957542721bfc6333cc7baae693b369d40e2a93ef6279aec26a0aa5aab8f567531b66224765cff6f7f6df6ce8172bede4936d3dea349af5948ddbcd8625478a12fd4f74fabc7fc797cff0e2c1fc58c2c60a4fab440fac130e0ab1192bbe2408344428a63b9a3886ce2f61cd755964a751412a3074e2643f29dbc36e79fef4b48e33eed517901fd96b133564fbe9cc2946d0660f10247cbbaca29ed6dc381f4d3931abedc28112e2713d70571fbcc3c25fab5dc3fee338c9629c806a3349ee98a56d51664f9fe79d3298a7de0dc4c74c23f0845581fc391aa865f9e18d64e2ddee4d08730fcd2ddfaa36d54900f56060e5898662b9455506feb6b453a8b1abf9b17d6f654a2814c023d28287f6e12406227e4b76462de6fdf84b174ce56770702311cb2e20e9c438a0fda3c35f0033288376387fff7ad7cdb179c2e0efd089dd92ae974a91b9df9236f1c0fb84088af1ab41e568f82d97fcc8759346145def7498a3fb25fd2917839a2431f83c71f78b2a266632ead49d9aa5b4c56e72f08911ae2c4bc94f58fd92a9c0bdada3cf4f981e1bb08d95b1069e4ba67e619bfe92ae362b52c3c868d12a0b94e318ea3835801a30ca019b0edf757a4a8b3b228b765d62c6b0fd1cd3749e6bd424caefa5ed7ef5ee15396c146a54158570ca5772f2a3d2edf768441026a92124dda9240474cdfea4205962e83253446fe3f90705e0a04ad112df80f0a32cc07b42faf2cba6785c0606381619ce942a2025f874010b84b90b2e5a2fd1774e98b9bdb6f5d07d3b99de6633a77ed1566d5e7a8fafcff598d8ad141720a54cc8f12042d1e67fb618cc2c4744c7ef9dc0428a452f3649757e164b050e806efb4483ca48de75f966d4447da7c8a1b29c9785de1241cafc9d039bcaa3b1656fb13e72bc30706d3bcaa06dee829ad0f469a3c1e2836f73b5959a4e924e17ad0907b881b8e626ae6c15f80224537b63478c70fdc127388a4d240236711ce1606d0e8e572b5096e659ff946f4d535da51b8d1224e86f99bfed85ddb2defefb666a7b80cf27fd1fb0f9f604c7c07c75fbb73194dfa19ea3442280d06e2450df25d600dcce65edb852ad109d06ae5680de2cd80d9e4631460966b6fd465ac30e4d640516ef88df1633d2dbb0bcb0d361dc44fee575e148b7326a56b2a67d39224025fc135687f5e4d0e87ac43b847a94b8ee510c546fef3165f6effd0614740e4eaa06da07ad318432c9242f96ade4e33573fa5ca639fb74087d1b1d6d39a7927d72d49179782eac18780079992ba2c2a60e398289cc60cea751da592d2ed7e1a8dea9ad9faee57f44f89abeccd4eb31c0a3d6f20c755cb30c2ac20b044da9d9f9a1f292ecdde361de1ed6619755f0df0047a73ea217f6d51dd33e4c03fdbb88f1344b9b6103586aacd091203f1652faa85752e50496d3580c5c865413038d87394a9bb1a8b505d8e0b3a35c0d2947ebcc2dfacfe0a9338fd252cafc1fdb6266e6f945981470e7b229567b2a089574f745b146d3cd4598f077ec9b8a73ac1d3eda67f07039878fd64d03a757a42493636c5761847f1d022d87cc8eb54bc7a53137e652393456d7c28b588136c203f4a164f571a7bc962a076d34cc606b822b3919a2938bb7756c36e7ef569828d43409a4ea9f2f7c5c7ea1958331fc2361d64bb3c5707ce2c28cf117f32f22324b29819b9f2d72c908e3d8cfdee339c25ee65afc6b2960881c5442be06841b5edc536a83bd581a9602911f604f39eef8058d9be7848f5bd6c0a9de7fc7325557ae452093471e72d1ddbde65fcebf3bfc7453ebe2b7744c6a370f5404cd9e50c371167f368bfc5654ce40f3fbae006598f048664ee90815e00358e4db8da2db2fa2b055f45a72b460c3794bf49a2bb628172803d66dca6001db5dd1d134cfd14bada0469f6037b6a79d7ca5e8e5f83c0d2df54359d9c003051d2c3b2c194349b1c247595775611834a5c1a5eb354ea700d4c8f9058e7c9d62677e84297a2fe1fd78ac9446503d2cfda15c50fd4e2cc7981c2dcf82498b18f6370a0907151409cf2db7c18a3eb0cd5b04b5ce73eb10743da6b4288eb10be37ff11b0a15096f8174d2f491f7a5b6efeae35ad619ed5ed7bf493970ec330b80eba5a71a2b3f35b348f9ed93ccdbc7060e26b3cca7d9f4a773293edd595672f8b5dc3a240b1fd67cab76b4685d8c6ded343d581afd14e213f0a95e7335d345bf6e41d0340946a4d972c5aff2544e210f8365969b4ad166a002cc8aed5f03c3c35d50c8b26de4856b1e9ed34609eaeda7260aeb7bf40280d107c4a2962200fe4f6475501c7244aa43c190b8304961298efd4914fe632340ec1ca04671678bf069c69fc6f91e28ab26eb19324b2047313f31e7d45a63295ae42b4041520fcd0abdb74d05af26898c83d88de08d9c0505f5d6d188e3689bd9a6a9b4b64ca84c963912bb754b235f3dac4297abd949ee8d6eb4e08599dbdf90c89a922d3ead412c07293b0e21fa445f21ff63b1faaf382ac01b1f88b95851f37f435185837c6a4e5370c9e48a1b89e8df85df35941be4ecc8a39e21a3227c2f672bbbb33fa48aa83bc62d8200bef9c1ca2051561764afa4651508afb9faeafcf7d8cc7a550517aeb24d9a19038a4829374c6ff1f5c32ba549ffd6d22c25158c6a8eaf5ef5efb80bdb7b3531ed28806a3264007ae41a83a5adb0f28d2cdca67d1412e1ff5fe34358ae730dea147d5efacfbed13acd50a602016f48d94df8a2f0e981f77a24e59dacdc34087bbde71b9de929e2321f6025f99eb61d2841c941abbc7c2f9fab5a643054a746ecc5cefcc51f882db8752536b143c197de484d3130d1e81df15fa86d7c42f2cede26882c19673f5c75a1b23b39d2f61715e1cf35ff50dc7261ff8a65ef6d2a39d6f5ca7ae8faf386007b78d6a26db2749fc03faf721c52640e3b7b8e8bbc87a8fe643b04db629ed3a3b8102acec9a6b13961bb449a29fe0c48b1abfb4f4982b03ef3c047a6624727170e9099a5010f1dc712420454e7ba9bca7c336cd1ef9f0374bb24258adc9a42382db5526d3e9c1b5d19f18a12077822aef3f92fe26e2f795e4e8ce6698c896f37f7c5d0b4fc0bd1f79b54c86a5501f80bb01bcfc2d7030a545594120d53b05ab4341843bd018a3e339bee727ffeb45dcb96a12e8808f771c0b578f653fa228733bfd44a6cbcc55a277e9fd6e7f9fa410fecc3c977525f58ceee4e78149210388e5aa76b8b241ffbef4eaf2da41fed6b99a8681d73e3c4272a96f25208e08f9abf4c871d56e7df0dc1cd54437a6724b89ffbb1602fdf8150e1a52ab53ccce6a89d00f55795a35e7a3c8f70846ff00257895151e65c1f5477946c707e35566414691ad5fec04b9a07a66873363802a4b6f158045cb9647039e2b8e2bec9304eb7b9873b169271d71a0077d676c099dd704e612744e42a6b0b51e02fd80ded77364ed64e907ae5259fe31cf387cb8db0c2a19bb7637621042def55320474e9f29829b5d3fedd471e7662e59035ee72e6e5346524203f2a6d2a550758b2308682adf3713bc608dd87d4f73bcd6eef03785ff0a4fd1f7237cffaa562341523453027281a18241d472fcf894c7d5f704d65bf7c273351e9759e3981e7eded61f9a013a805a70a5b1ef1c46578df57bba347f13bcdf36bcd4a8fbc354099f80e8792465eecc475d6c5bf3721d8fd686bb419d661f6c252f87ab7100e74cc67ce3457d9d58aa5bd5604e7a449ee8957280ef55b7d97905758245e6a4b5127e1a98deae3fcda50e30f52afbeeca54fb19ad85dd3a9b24197c91c148b22013f596fa94b2a7b23f382068a6f1ba295beb2d85432790bb9eb61e340e927c9e9d09f57b52df8b320e5dceb6f42087bcdd45251aac49653a0e16394f265468ccead878b6df6d7148cf666f661d9590665df90f49d77594e8c30beeda8343887b9425479aa942cd7b7e59730f75af1e0e3d4f59ca23aec613de3dff026ad5215377d41fa64e133b21e8cea236c98549f4a0143b6e53c632b42e1754f68457c2b563318600e1c199d37ccf97346acbb6641d5c502b9bfc707bd40dc5f5d14baf86956cead5453bdf01faf363fb095d49341c64d0e5e9702be8301235f945bf71214b65a499a0aafd7efbc5fc9acefccfe508057c5ca9258421308fe22d8c529a2eb49babc2b00b69bf2204a9cc8685a6d5022837e1c20f57c45df03e1115433ac0a7e518da2600f917bb58e1808a1ededba57031d630272cd2c7a9f3849e3dfed115e34e257794ae7143baedfc7f88e9138340ad25da70d14d7c6a0c7cc128bd0807e257662c34d62f0e2aee20d8454d02e194fdee1b36ff611759e26b12f8d03af319970b102c344fb8bbfd99bc64eecbbe2928d3beb3ce83e0dfa7f3a45a13cbf7dca9b75d6b5364b2cad3445c25d00f6f17721dbed2d3bc7ade52d0214dd67a743fea3117465afa7683b3ab020c29bbbbd09e112cc818312146a498296a8900cd8daed88f4961f0879f226781795b686837c276233fce3752f7867902fb657cff21faecfda9e201b0f55b05f9c54f96c747d76245d50f9a6ac272e74c6c4673630b9aa38968dec7045d10fd12fe399836e3bde9d4e62c0d36f7975bd37025ca6e26aae82b51f0387246a00c70886a5fb4937f9c55b3cbc2bb8f36b39d80bb1801661ca9138cdbea48277753ccdb3b7c91a9b3c19f6c0f8f3360bb403a4b03fbacdd9e4a415c9cb70387497af59e380a38e96ded213352f9c5fadbf7343f50f642b0addcfdeb83ca2abbf398f1c65c9f13a989dc7137e0688d494cadea9ddeb8faceed9137e4f00354ef56605ab750aadfc241261954863de04ece280569838116f5534b647e5022e4a0e682c8af97c19082c3244c65b93ad365bd1cb4f326a11367205e820ad116e6d43dada12301ccc4d80a8e2516a54ebd79735da5cd437539f65c7c7fed51fd14c9165e9ede549717c4b7e0d0b44cdb0fb64edb4f225eab692cffcf7114adfff3988a650a431dc86b29b0637e25f8f84a87da447383a32d1f440f4941bf6f5e24f439552da583d0c34dc140fb818bd0d491cad4ec1b4eb076c4ff035ef8fd66d28052694e8446e3bb5ebe799624f8dfdab1f633934b94ca2c68c8f225a3ed3a6a3dee43fc810e3bda86333614222c38a9f6618ee86d78190016b0bf5ed2afea3b5816ed9e29c9f7edf27952f49e6aa714a3799ffbd8d381fb6d8367a84caa875e020f26e4afd819130dc426d017fa9f695de9df7cc5bf762159e10745e2fea91222925e63dd99711ef87c4dfe24fb35544ebfe4611a33ead6e1bafce2dad9be6b8cee1edce4bd1fb5a9d035a2cd44d9f9e58100797516392487b7a3705001dc679db21fcb3c460c7f5f362568c029a33c14bdec1159020897dd27142962bd829676c09a7b0d71dd1a5cf58b4ed076a47e08e3464f8e749c614fe1de9f040bf866128ba2cb856364105e1e3463b2275b9ea35cee9657138894df3eddebecbeb70eaeaecda13db8603a9ec2958d687d7f2169996aabcc2ce6010e2a7f7c78661473d0d02749096d75da270de6b7c6c22e673e38c1dd3613c4260e86bb15ff0dd2f2cc6d069f8c8b0f3fd7508f48e7d68d5594c8c4e3f1eb0dec91ea486f2d993a56fe979f1574d4ee9e4510cb5e4934e9a99f7746727b97c33f20b8364353364135dc1295b14bcbaff610c4c38b5908f139a5d29f060d29e483905d7b36999970ec076a17f4ae651bee2330286f8a959accc816e7e8bef50fb34ac2e74d0e2e63f193e87f0cb959ca50596fb624781a343d990a3dc6e9fb3fb6f5bb56aca93e551505284b57e27d8225b94a5cb9e208be687c94ab7e0000288c3dd133af3760a396d76501e27c4cf0745841188501a88fafe478b6761c57188f8c127d5fed3b94fae888a60462789c687063ae5c164888f052b9824fe25c5e0acd5328b38bbfc598ac4ad94fd032d63190db1b42f81ae5b217d6f1a6acc0be11248e16a372acedc9fc96544bb8e9b4623b0432070099e4ddb6aaa72a4b359c79df8126040872586ee40ddbc38d748901512ba6d477b02de4c93ab34d0d27dca7faa8c3e6505d33255557cdc138d020cf4c23ff821b3da4b8557673bb9a3068bfacef67b82dd3c9348d7514f67867563ce383e0bc5f2aedfce5424714002c79150401f94b0d3799fb6432d5aa561cbf1ba025050b99776fd07cddbc2dc72d283f6dd4d347e78df526a95d74811309d0133b3227f8497269ba047f7343dd291884ec70bc06eb223a073f0510f644569dee2a37d6b582df15532afc4ad5940abf5b77744f252b16b10725d7024b95578b39ae9773cc86678a9be486f2c1d0e25b87512ef61c03e74ed11bf256f4ae90323555474d4a1fa587afd76b14b715c843c175d2bff07ce45664edc0dd2b762ae61ef5b847bafee45a84d13c1b8ad73aba497aef747c78364eb18515b3267e3b40ec6a56cb471a3ecee36144e7e71ee6c79f7739866a1668a87b273aeed35552e8538f24afb2d59a25146399f1fcf2434ea94e270a148f9aed53b799adaddbe081d97ff1d77ede884fe88ace43a6573a0b54c44ad628823a7f809615e2ed3c5e513efe6442647437387163c26160c6d9e0439074046cc01e23cdb1796712fbaa650b49ad67859d6fcc6d8ed03966678d551a5d68b6955c630a1caf50a5e81b654e10191c5af124df0ac74e7fd14bf4798611dc4d3a1f7c85f8665f69167e911e70181fd5600ea2dde2ebcc55ca1d4de83b074548503e272df85cefeb05aa0f6c0475be829be3ef1fabe2eba08a992e21df2ddf129a865c47afbab6658179fc950f0341db2c60e3e680aff8a3b80ee7a20db872979091a81d6f5657d2ab0557519aff98b60a8338d6b1e87a422999dfdd8c8da8d63e689efbc9518c3a22b3c1d21a7b0e29d0e66c89c26f2e71eb78a07ee56aa7512e6220ccb19a2ef0ad10b74325a7f9e940a43d032ae8676372b8525b3fef2004509ca1d3ef5ee678222ddaca343d8698baf83b7eb862c89cc5d46715bebd6c9b2b62d8c4bc510efd1dfa4687879deb333614dec40b7a686ed17f61f2cd7ce4c8ea0b2862bd9328ebf389ff8e775ad195bf3087a59c1dc4ada6fba5124a0c100e6254ba68569ec4857258b8b4a2e88704e6b4f3179d20b5d8dee7df152724a960d6f2f489e41558c193960db37043aaa816b220134441183d4cef92132491a0d1cab687371929688e04cfa1a1e271651df80ae5ce00adb86ce04bba1b962404a8eee8d5cf4aab334981e6d78f3a9b6a870e688bd87e69e11263bf81c20cec2f421c56e533ea2c3d1f6a91ff1ad015edc1b62125e16ce3ea2a77e152b25779c7239347cfd6a131d95ecffe50d2d10f8686f6c2c637d05f60bd27b975894419c476d7f09e23e76fb72edaab06a8f622f30496e8f2c95a42c6c6e8eaa44bcc3d225121b266e12ba0f299a975e37c4b0efa07ad51c31173ae3f6be6ee56f7a8afaefa0e1e8f13492ddf89811d606c24312dec33fd514d91ee50c86e809eeac02db51255590cc0aaa1ebbe3534995cf26e0796395a693d3704fc2bf1473d82b2c3c82ea902a467abfb0dc4ffc51d68284cbff2c8c352abd6d6d9d6449e773c155bbf0e2faa1d0dae89a85c9475f7974f96651e88b0ce76b27859ad94474d62d3477e866888275c1bd2e0dd45e644a85c72aa62c054e0abf2d486fed5dc069baa3f9b7fba9f8a53eb3f6739b3986d59718e6fa82077a4640c44e70ed1a4ee38a2f04101b43f4612ad0300715f42ecc4c472226243b1763442e65ebfd1468cc63a35841009019972f35672633ada59e22bae3f77886e2bce89cf629ab8bd63d601bf1bd0ad9f3e9d1a5b7e23d6f1a4a06edf07948eed7dc24e47ef1d4d37339184df036766f175e518d39a0333285fc17b302bf94eea6a3da4945e61e57f2f434f9ce5b9d1507bb323bcd028fd4ffbb589259aac18f9578f43eb616d4d1a55b8577edafabb48130b0ea55fa4a403b8ea228866388cb20c043fd5aeb44e50b185b1498155d54d1ad898e0d8099431b92e421cbf6d6f354a532a6fc8835e506f9948e7a7b4aeaf8487a9df2c05de125db8ea42457a6a05353ba3125e66f939b3f188363a37c68d0bdf0c87602d5ff378208e73133fa59021297d7748f3cd3ce1d16897001b1837f99e10a4d94ada849632c656f5f2fa4f3c6ac0724cc15459211a53709e3a426e33a8c24c85e33e7e51b77e135e70c32d4900bcb53dc38161abb2c962267785c1bb03d16d1020bbffc25cec92f374fe94f2fd613d8fabad40a50c0bf9da7d5123a4d65d24b809c4eb79dfcc9a9e50360003d04c1ea6b32a636ac421cfb3cfde8ad175f4d1bf86abf62775f1114ef43bb9d99685f852c074a3f03cad749f0f28e7e702651c6ae38878b1e897d63551ca7a72511490f9948f52f195d7a2dc233345a8c91a57be91bc16438b609a1706b21fe85c36b3c482834b8ff6dc7352f733e4a0456727340b6a3de1d68d69ba4208b954b3f9f2c7bbf90bd858d6a854b7e7a8f5e4d3941f3f51e6c38eda4195bd417606fea4404b4c3e33433b2b6a5287fd06c7366b2579b7c90cfe818479178f1f580e822e587287593d8d1c789d2ca1e654a0e7c5b80eaa6d5e6396631c5a8fa9023487a430aeb738558a73aa9c2d1493c65067a03ad2804de2461f494075da0b23c792294f9d2ee865cca43f4d4246e92cb309174e9e7b54187b573f3f40dacca014e26084f9c4b74b9f37690c9f24ec4c3423ee849bd4f4981ec37d307482e80476ff0839922a7fc2eb534bc3498372114adfd067995e8949daffc0744d552de8252c095bf9261772482515d5e11484a0ba64dc953718aa2db57b6e037f773c42d096914ad0e26e0e0dd32cad40a1e1cf30175e4a823e801faf1e1909def9652570dd8c93450fab8df5f3e1e0c7522055e840dbae1bbbecc0f60fb5f67e779f45683359b7d2606ba456c7fe769d4f892c5d0e18b911d4166f4b62a221bddf81b492a8fe759b0f9062d052adb7a04d9f65b1e1889b094f9abdca0b355ebdaf255282590f05f1216f732bbaad44a366b113f70fcae888d8cc060d37c3068677baa18d449f2a846548433bab38010e8612d13ed24c48b19009496f7add780fd261b992760eed2c81fc019f0b9c906061339b4e05ce195578bc0a9f626e822f1141c85714ae298783ea2ea25f576b792387612fb44dd9094ed988b62c2f0458c457ac3329be73f2427bcbc2bcda4ac57847c2ea4eb7709e6bfd8d35511139fb75977381965f9df6608cf05b4ba84405ff80450efb6e3e39af8b6f2a96cc6401864f2ac3610e539985c107d078b70f88a16804fe9927531183f44fe680fa5334bd5ed372d60154272acf4ff9426998b7c625f5909f0d8e277756775bbc86f7c762c72f25e9fc27a98a04b11f7db1a3eb397c13e68d9b57f7744be7b4baa7298d870c45304f33dbc7941093a33c223b57d593e92fe465f196f87192ddc727c2aa8dde5e67c24bd2b2a54102af6fdd8862dc279eeeae31ececad24caaa63cb964b5cb858f77a90cc7263a7109ee2eacd65f232aab0beadafee2017e8533f3f0287f9d16c5cc8728a8238cd7c3cd6a0472c89319e139293cbc8853d4af2af33602a8e2755bbc4a99ce0729f7d4ffe2dad4eb971b72b3b51cd4370c84154ea0a8699226b439e70748d1d970769ab5303161706bd3e1cce54affe3ba5cfecdc89e26b87c2aa74251556a9b1a68e8955f73f8d1d1da2f5a1ced0b6f8ad03a15fe08520fad5b92e07e9aed12712a7e56aa3922db67293457d369f05ba67d7ac0311123a5210ae02215eff6f7e9b2bf1769259cdc3091603797935a1fe293c1bf32614b72ee4fb269c863f3e8cce45ec4fddb0fc87bf71bcb93340d9fca96f1b51d00477ad86d8177fdf45d199c64894ec8c821b06c31932876c94c7c70a697cd618e9ce33bf45f485bbb42f193f2057f0c630524aac06529f6f63947c9aabfe7399ff7113c3fea30d4a32f0cf6d3dff4b2e7e7c53f899c8d34bea4a7e7000bef17516aa7e2b0a31b020eac9ac8453db947510161786090df29c6afe91d6cf8eabdcdce044e70db6ad2ae5d3d3e76f9d45e9b8b0fbf3eabfdd48a6c36c72f3227b7cd97d7b40da6e60644583e77f7003166372eb17546750a93b2dec4df9557a470a0f667c153ade82eefa7f45ae965bec1c705b853d670a76bffadf0b780c7464b3caa4d027d93996b9e5d7af39c7eccff14e9c046ce1216fb9b9b02e8c3e7fccda6b4eb22e55431ab0b448d4eddc74a01fcaa4d7bc7e5be91da3f5e3c1d9e2044477be1594b3447811586e6ed70509fa34f3ef5cfe75f14a936a00914ba6af285e399e4496a72dd362873e8a3234bb2e59ad6c88fb5fac396f3470549c0a66a52fa180bdbbe1e6414ff037c78b5eaab2094e401c59ffef968474e0b09f802e39d02b9a1427e625eccf41a32c46f8aeb6b82747720f8b20e4b096c3c2e419c86ce2fd38bfdae1bd4145828786009256135033baf4bd347039840af5bda5c68266a78d6ff3f988fb8d7353952ea0266fa34a0f7b8fba3f1cfb587794a83a4c783df6e94fdb5074c28c66c7ba26a4c94b5eb8a645da49d19b7fcac67dd64569ff2185ef1dc6744bb43aa7ed7e4b21cdee62dc635daae7e46ba1de417e75ea457047e8ae24ff45c642877f84adf6051c4c202d9434a5053229a28c47d0b0f1038af9b656acc34f1b07ddeba3fa9015b6899c348b1ab76be3c37b73ba64f01b8a5c2b0aa35506e3fa8e80b9ceef922f80355f965deb4523a7092709b29602053449e33637af102677c32a74fae088f5c9f62aeb10e3d30b69704c3dedf0ef39e82671de6b23f93ae8c3b5b53722495fb76ba7e51ecbde39007f76bead6be1ccfbda0da400276b9cc57a5c164570f0ed6f19d9cdfaca30b1c399ef39501a9c3f0339f9352ad3817739a13aa0b9bf9d04b18313583ce11647ae5e046e6c6e4f8b6e14fb0c2fff2ed20fe8b3ecfd606019446caa24ad7d256360058f1bad744e265bc03061bde9777509f976b405f482aa5c4989bbc984293192c64605379eaedc1e610e698561e65d10bf3c70adcd3d5b790e918bee2a55a422f9ef575af33f0a392d19cc3936d7b5d54a06cf90cc84edcde1c8a523a14f1a94fc0f45dcf38a15501f626920dfa041bbcf3d9f3071465f365418c7a3525a79c3e34959fec2d428e9f0c88efa490a2d13b47b47b391ad0de2b4eb24beba059520493aa6bcfbb0417e46c75101ed6007c6befdc625982cbf91b228a7ec5faa955a568fa1fc8d560a5db13af30001961c16a1a6f7935defc9888b3eaa47faa3c3643c01831a0cb6ba5be5e476b5ea90c7699b31041653cedd64c164b8870a7fed7f8a23466f2322863049a2b99917427e37692e5362c5282087dc432e7f5c0bd5f3df98d8156a9196c17efecc0a6efb15eba322ee9f8597b9f33c75a97bc8af6bd577b6b486e2c2c71f85a03e503ae1b256752f51e3ee35ea3ac57c6128902bdc92f5761dc3dc3aa7d0ebd4624b6e8c71ae6de4144ea24719fe9286a84c10547bd5c257d47e2d4ff31bd44fbe02e6a363b7159c5004711a496f7595cc06e577b10ccd83302c55cf3dfa2396fc1278448cf14e8c53ea3702c10959fd9a1bcf7c9af499559df76a97bc650dd1d1c569829d2e2a3b6044b77ce8395de24bae113bc20aed49b61183a9b33aa25f9ab42915f7859f2677347fbc0fb746cad71acab2ac890034c20f499cc4a52d4bc228b4260976d5c07cd3e2cfd9fb12fdcf80b95225d9469f0f47f19e38b78996eff24e63559f15e059eeba57f73522d4d64d32277fb76682a20129a83feb036f9b82c44556b44471a666e41e14c360a8ca6a77fe8eae87d4fc1dfdadf6a3a270de28f26dd28020bcbd700aad08c72d8fced225639e7aa4e7ff53bd4253450348101faa5bcad6af2c2e4a9e4a50f2e2a71b406f5b86f3657a63bdc64cf853964e6c1b39471f740642bd0d90453757c11a0fdc1f7a1589f934dfdccbfdf68b9b324d4aef9e57b53af6ac4528bf5c45e687be1fc27ec3d8d4ce5a996b6019c74d1996e1505ff3b7f30179e341d8f3dea8a218f62016270920b6339ce6170c2932184a5ba03d7ec82c5bdffaf605af6769575d417b48c531b2d6c4c3220a50eede58f1be257d6cbd9bc82326470cfce64b2cae00246045508cf108577557ecf53d4b2d9f53be2d4aa36b6b89f9ab6dff1b02afc7c965acb18a3f3a7a3502fcd7025f1eed027b2754c1ea37dd2cbef627cbd5027f9761a2272f50f045f3cb1965b5006c24689870eb539da1dc107eafd31cd3597e27d1347bc0fa3c1ef17d987882398b295ca6bbf9410c628a066f27ab7ad7db394e3171f6664c2e41f54360483ac4a03e1fcf1635b8b7ec627822b9ef5219506d4ba4c07f16bb3e1a1a4831f1ac9edee96b3be3994ee912e9518f3357755ae28cf05a0c06facc4c72d59bf8b6911b39b1712a9962507c5ba95daa1ff25d33441418dc84e87d2e9fac6ddc60e413b0591f313141e04102bea3764c1ff346eecd6943828db3020b836dcccb7cd3f3d6c07eb8b4a22c34800ff1d805f0f489b7d0d3f2c708113f587f10c19fcd2236b512e118efc012290cbdcb1282dd72c7f499e474e37406bb6432e898deba408501fc686400fefee53de04355b654d927c64967226f93fdb0c6aefc955c2b2d93f863f957bd8af39a888c4d4cad92b4104e6cc9c7d6ffde6da6f6fd38a0c66272801a3a40a356124066955c8a8b1889a3b93b20f43cd4b854bba35a42acbf2d0e4f9cdfafb5fd883d9050131dfbfbb9baf63dd20ae0367e880423fab1becad536f1122fe21a859e6bb0350a3ee3b5896eeac011206ca23a393bce61b5a9ce0be1afa484bf8eb28d294bf004e4aeb1d54e728ae5d78ad48532e0fbfe3b857bab01fc2dd3222e871c457a4b44c3acc4f73075ec97ec0481896fe83757a0fdfcf4ccdd87de08170f5a664f57fbc9be2f9019d0cde9e80ccef53335f63e7562d7ee5b06a993a4d750e9063055c614d68194133cbb72a3999e73ecf1f991f9940c61740d1aa8a63e2af24a08d35e00d957415bb228f3ed96cc9a1f1d8dad1d6789dc550639991044ab385dfe168e68f5fde8a5e95ce6c47568d0e430ee149f4e9eff6462f3e1ac07f32a437fce517c9086fcf480d170feecaa3590443b6996df74039f83bd81b647e942720d95c846d4afa51f9fa4a81e9429b8e7bb816ecb65103fefddea2f47fccda77b759f17f8e99581f078030b7d0a247621035a89121b53945785abd97fc68b0cd905f21cd086b8434b10a1469933d26f3f32dd066c3d625488f2d3e1214f4e27428b82092a871dd57041840d506df8919db74e08a3df6df935f07b90c08b878c409b400046484607fa387a3b79dd038dae8072b285b5b15bdfde237cbed74ae70454e3d77c2df1e4eead446cb215ff4fd57567bbcf28ff6a0fdcd3ffaa6e1abe22579064ca2ea1b78ccdabed8901180c3bddf2e8d07538c065a1964c236b556f3692d9332df016c009feb61bae994ff421ed2c80f29381e78d163dc172768bbb89edfe12358fb93b46a9728617533a2091bf39a9273f48c3da0db8c7fd77b6bbb7d9a218ee8ed0bd6e4306fe22cc6e1ae4aae3bc42da3268856071f7ac61c12ec0e72f07fd88bee81da661898f2a0bcdc3f72d7c19e624df95b71b6ee6fd0d6753ce3c1d77049903b3376587c6ae4fc9fe64e8e0b9b611cd584fb9e5ad0a883501c0f2bf32e9c93409388466ee5739ebaa580696cdf0839d54ed9611055df19af6c77bfe154965a8a74cdb3bb59a4fc4a384f490fa704a160f8eaa29e0067995873fe15c34b893763b882a7862c9612a046de538292ec87fe85935e2c63f483533c9678202defd79a19fe953f0a14de8554ef5179631216473fd5f1d4ea8110d7f9d240b7c6da2afc6319cde497517e7f3e57c546b54a8aed74c0ee027eb785b1245cc2c60681f7fcc15db4b10d0b3dbf2acc47a702ca2a8a192f0b3327f3edab878a56014c7a750b15154b6f16ce95d7001a0f1485adf8de9eeb02b634a17261aec7f66b01412a317119bc2d77f131df0cb026f268ba4dbf3c594cd76c33468ccef342bab31a68c9de9c25043cb37aeaa39e9290051a5dea6cc0c3b836819551590cc6b9dddf1159c77ac69c68c3f86ab861afee192bd514570371c91afd5f08b753b42b47ea5dd597748f979d5822983427f866147e9bd89536dba95e1d9dd067e6b6ef5092085990398f9ea4199d137d1824a64a3280ad9a341352abf0d5f93ad3dc728fb6c1555213a0c5a0cc40a86f79e18eddd6866582e001f8ef9058f163edd2d5794b6ca0b740622d1c676ed3a8f093bf7ac0edf1850e45e34bf765cef7a76b4f06a4fc865eeaf946a25f196eb141c9dce04ba81f782b87893e7d4b1e12090808e5679334449b3c326c75eced27dc4ded5bd670d960d0a9e9c0178cba2d53632a9219edbbb93c924abc42924a6fcf6b750e638caba772a47e61b09f9c7f6761a4f6aa4d44515877b8a63481718fe00769ac020fbefba4eb51c34566fff8ca4805b6e2f45b1771aca21a3c719941f0f5a2679133638b630433bf56bd4f19e82abbd0952303ad3ad5ca745f1ceb67ba60055b210ba1e32bbc164dd0a2ba5010f611f9558ef46bead0c7b1cc4c975d59f203f4e7960024973785503293477da1f2277b2f5b6b685c9cd593b56528740fef4dadff37a887d189f48916cb1fc1c607e28b38a83e1933ab174319357291fd78051c40740e74433aea801998289530b756dc34f323d0098182cdfb8bc51ffcfdda5fd1ea9e9ef141b6b1f07305cca4d104336fca78d709437af9b1713edf43cdddb9f63c84d83c0b881fc91e3f3329c3b093d7bc470bf06073aae5409029b14914f86dca2eb1da36afddaba917682038107f897b4ecb427f7527e111779a42e284b4b5c2693c52dc6fb9976bd68183f6a4c62b81626a67150efb208cd5badf6ef8314ac73bdcf7aa910d69f08adc99704d482d2e370d59603de39c32a85435b10ade77dc66e38b12307753f695d937617f62bca7ddb856f2b35d18aff4b70d26c9b93cafee28fa8eaeb791708b29472c5692dabc5c04abbdbbd4e94cca3a12e75d0fc54ffca0a9d16dd608670126bc7a88d57772f7cdf1d26d4754a6aca485de0e595bdcb26bfe5296e5d1e63c0d70b8e3a074725583f3dee4f225667c577ab6e2f18cc3c751b2d463c67ffd32a85b4c5836a9b131ba6a9623990d121a839f0f5675dafdcc3e2d06577cc02fb5423040ee8ee7f2fb948af0d54b9e2cdc031b573dc9502f5f1c3d854df690bf15f91467762596fddde70025ba034d918c5251e72d73555c93145af5086431ce908595f1f23573768a8f176d8edd3d1a5013475667fccc549cd6f56de38bd487c65bb37da5ee136b76b71475b4192bcac25f3d2c6a91440d9f90715893b69b1765e9ff01e879cd2bad3a8f8fbe7f24559abe842b3212fe3b03cbda793f9434129b759e4403eb0ee717c520a10de0e3c3b8dc8789059fa3e33208c9115a47c1bd1d78b0c56424c792bc50dbcd1dfea8c35c6bad6e29c4d7f22d30a61ae99821414745d144747397445abd2ab0938a52a8d0ba4a4db3f292122175ae325b4f1cd4d21b09736b24229acbb07ab623683e28976304edd8dfafe41dc76148d491ed600dbb9e1dd9fa58f643707df72c49d97eb864284d12b406cfa3f0dae88a5c7f31b75337cf9a065af9355cc3908d49bfd42f6d25cc66df820ae1331a567ff4815316e0ea80ad43e7fbd7fcbd1cfb4c7adea3a7d5e316bd8ea824fa7d8ad6ff9f8afa155e1b2ede0ffb283d85ef54c23e4573ff0a38170b29866e5eee8abdb6d17c65eade7f89067b21fec21e181f2eaa0f053a624c4066216e5cfea041c3fbd69f30f00a78b83680b5185c1b1787dab591d99b7b8975ea2fec3c7e65f7571953779209c6511c68f3f6f6674a96a511ca3c3b1b4f1a40613d588b34c0d154787ad07e73eecd08ab970ae405e43e7fc97e825498954a8aa2ef46fba87fedccc15de80c25b21788d117a35a258bbb38b53f9957298fa8a852f0f95d8073b39f7f05e99516dac44e82de5f49e61dc6c64177ea8c8929834d7a7df5709a616187d8b72ee740ad265b1f77400ea8c68e5ab9a875902da0fcc0c7fd9ae5161569922b89c7fec54ebfb1fe5fbca5a1246bcd0b4db5fbab58e05b5a3a8bcf4ce6181c51ce0eb40ca64d24b5c509892246915d72d622b87858f33191b31d852d811dedf175aaf31363dfb9d816a12e12e962db2ee4bbcb44bb127c102627780d1a7aac2e4e0cdf49e00d809f510ce1f3b5f8ffa0787862920150afae216ba76accad18f0725d633f4947409027508c473cd20cbcad65d528b63bfad5504f36ebcc2f4e6cb75453de744992d4baaad4d20eff4a41c2f4e136e2ecd876998e7f2da86f011dbf50d3e9f8b5445d5bbdefff46533fb8e901f32f411a22496cc41be0a2818ad8cf612773aa5867d8b5a457e74244a43b355c589702ff1394c4f585f37c2bb5218f6f2ff03b75e48fd6b8ac05b17c9fe092fd608bae654be1a7e1efc9edfd3467a926032e15c7987def988ea45c222fbd286ed8fc70d400961fa0038e3cd59dee9d6a9f5c5b9686e4c8e8f7f28203ea7dd09bb38b8f080b9b9af5248bf01ada5674dbcee75e9616d32a2033dc593f6e2919e4f6651a9381ffdef396280265f710d1540a4aa15fe8c5bb0ac04620bad9b45a758b9f567864460e602dfb6b5a06387f3e536a08fe75c67123e7a00df6fe3a8e6054e23c09f38ae5b9c6f1456c21d6ddfbc30a08919b94f15e5b60c4113141b792d7f1f4f1a714d7fd8ac7fd4d1a79c7d29751407c14dde3ff3a6d9249b0519a95e6eb17de160269c5ae250f90b9a7cd15672eb845ea60b80e354dc933815a43908f871925630de104c9c9cc229787e8f5e73c06867cf5bcf10cd10f48b40caf10ea638614d5d106e9cab35fc057848d9db1938d696ea68b86b859a697c9b9d451bbeeaa2fb27bcfbf81ec31efbe7aa95e6d55ad4f5f83976ab8835c7c1c65f8ca8a945611d40b7b7038b1fed03397f41625a5476b65c765bbf69fe5552a9ec5c6d99d80e5bb7064b9b81e60fd8cbc2afdbebd49268529d49591e114af32d6c0fd8814481c735982f005d2565fed216976b6acf2e5c5793e785c8e1e1bfdd79b19d95c49ee09441e3005a1fb03533ffcfab385fbf4cda1991c344beba959e6500bc1096d49472bacb25a58c675c54ee9543d02d7d8cbe4d8f141bbaca069996959a708eb2191387ed9e1e156f31491ba4cbb27e0f7f7ad23a29dd1662f6fef26b82cde558853fd1211a1b373982457b34ad55306bb5201e3303ad2e3836a13cc4d4716d57fadfeda083f3af2f29a94b6191700490efa164f936009198e129eeb7023c733e755cd990bd0fedba897758e53fa14c6aa2b8a3658836722f5b98a32d23bc2aa4ab838087bfaae8e559cc72be076907a70caa041295e20205821ed734fda345d42891f4936daf2c8c561ee5235bfb12a5ae68f24ce485c1a3ded581ad6a9f313790d2f880d8426737af5391f0663b3ece3f623f8e94d1a899ba712cf409c8e817f94785a36c359baf19a9cb2bcbbda2b4c1efc74da6e48adbd22f2cc605f6224dbf33845277560f618c38fc070fd40d5c177fbff39769a4a44506b641704d8bed840e45bb38799f9593c3883d74222163d79ff01f4e835a852d3f6ac2217aa459f79c1b38859186542e512318066f0a3a9bba5f521b3baf1f02c4b8a45f5db3748b88d3bbc5f7c342bf8742fa694ab20104b414256547b74f8bb85ba41d92ede18c34cf00c56e2c05f9ffc24b9d50acd605fe31cf9674602a6f6d67d23e1d6a1f528333f83457465bdb40b6e7d692ec99c52df029d2bf34df0daccc3113ce11832b86dc367210e98f48af895178641fd681f789961df401591f3f3c334005f48f32249a79aae1686f8de4110c72d26a9ea3f92f87c05670a282dd76219136e46f0c242c2b6b8a9dc0b7ed1baf7099c6c526fe52a13bc1502be48c53f466a2d8818804fc75d4e3dfee5d860902d9e0fdbaba7f31c6ead7588eed4830916eeeb947b5f35caf0b358a32d43dff4b38c461776aa4177feaa26f45a6a7e98ff68cc00b57ac4405553a1cad727db5a69a3e6d882569cfc5b4493b92ff8a0a1a8b732c32e8bfa96a218ba6e9dff7b9420bf8398837097dbf3f784eb10ed1f00b64c2ff49c7db00f3d873eb26f98af85cb0d68cf72c868aad2e3ec0ef5ec5eac204ef1e4f3b52d6269060385d350f9e4c9555d505ccd0214beeba4b32090caa3ca2dc3326da97ffc3eb968e3791adf6ae9e4f8c6b7c174458526c6c470f95b5202084dc4b99be8e530c44ce9edba5482530a4f02125bb2fcdc5324f55ca68694fc2a177c20c45e259960d5d622462503afdd1876cf661f8613ab0a190e135b6c2a56c742b830591529af752ea6abdf851957cec4455bf445d8baa11de5f2be678fe4a33a6f5f60d737e3fb1d0c153f3364b92328c8ef1e90a2cac511b9f7dddd91a6b289964e80d776ead67f1af7cab5787dcf483db107138e076a311a1ffe7d5f60c34b2f4087cb18eaa2e203e4484356444da122d911aad5cdd5cd41d58cb5fbe65cd1ee9d5a21f08f4a7cb8357bc67bcf620a92fa5def9bfdf14c922953bb20c50160300151105773c3ea3258ddb008321a6dd68e6ff11803b1e241ec6c8c089c00b3945fe5a0b93db8623e276fd6c50eaf6644685e25967cceb34cb34cba7b056a01efda5a9e19ca352592fce5e125ce84c8808e15ab03060c31feba257852d1681ca5103c63ebbdf05c23fac295fa96fa6837cfc16c4cb6bfa49b617f6a74826fe0cd4dab9a1a1f844f2305a993c274a7f0aa87371dbf8d13c24b2b9fd3f7b82f8e4636d01903641e6640260ab9eb0f20a7df72bc86dbcfee76d5c5bfdd58328f40fc65187eac1d44b0070ed58043a2a09e15b7b8c7dbd165c71c436fe75b2ac153e9931375b8b2f681c4dc9eb7f79e86b39c3f133d6a977a82ae21b5350f5b754cd33e2703428950928705350944741a6ab75cf11ec6ee8cc0222646fb25c5c02d12644e9dd299b00da7e659f8910899ae9afa586826753da7926fa8146aca121a8c19edac12dde236b3b37317135b658cca94b6e065dc3a583336f9c5135f9f7c1711d3edb3d6a0738db262a34bece57abc9cf5d05a48c0804832aee77ee12395ebffbb1f00810cbbdc462f099f268f28d3131c9f739a5ac9cdb18d4ac0f42720d6516a69044f4ba20346f438a09697b33adf82ebd8af755a92b69ae07f6da631428c39b9e01548aa51676f05aadec70d3f499fd10ef305149d15e402b2f1f3a50304a3f692b6b4b937b094ba181809532374f1803774445eb2dca1c9d47118eb2190dedb4f946a4bc538ae8f52fdaedc711aff5392cec0a7060f7e4ee39404c52125efdfada4a446be2dd05f37aab2416ddbf425d3b7b9c7edbe0a988ed3420eb817cc8b14ea129b1813cbcc7915fc2e31eec212d7bed1c8dc77e5d2f6408a0799bb275c959ab550a3843eccead923e535906cfff63a5393240f4ec69856d23a6abbf011fddb664adac70f8ff16b8c020573c7345af7d35c3b92f810562219996e2e566e5d3b7f97cf018a9403c627ec410ccc9e3395647d56b9090c22dc5e9a2515d9b290beeedfb5d28283fb820611cff50650795d6df09e93c02e1c1f7cb1794633ea2090eb9b80d93eff22e4ea9a16ab5235917e93e8f2bd07da141ea490eb9040940bf9909a37cb6f90897585389d690c061a2bc861f3f83cc31f35253780ce50da59cc18665d12f8a7d29040471a50cff86a3ef7e615bd7db106d62016a90843683462723ddb1e727baf41f4ab8700a45bb83e9fa5c0576412aa81dd69a99f4204881af3bef653c98863131a6863f0e94ff0de816559e5edf275e544f7d442789a219e26b330e42198e0bec2a65399119e6e8ea14eb95639ea273bad2e9ceb79ee5a1d3f3626d2f9e7bd33c96533960cc2ad0a65c4c276b96d3c6182c99bc517ec04e0612b885bef3d031982c664083a2a79d032eb9ff30c76355066de7e3e79bd799717e009b13ba4f2f9dc17b4c950af08d8ea19fdddb0211cf19809773c79945521dd29b9aee26f17800ad5310b79c583f31bb41cbdb54eade377c9ac93c3397ab26d70a0de4ce6845ab3a396c1b8c892e65b5a80c4563d8b2ae7147f575c79af16a04e209c659ce82e2aeb34fd21cab5c6ed0d65e5e8791569669fe109d2cfc99e406756f8a17042281637d3ce3f1a881ffc339848998df4ab822d7855d207dbdabf30e7b864aa51642c1c067cc0fe16b7736718b88e92570d1f6ae50d771ebc8e60d24c9e27f1378236757df721811bdde17b42521c2a287b0c7e70ff166c6ac77ed07808c67b6ccaff87268f931c40c89f665baf9d10d63a906b394b7c80ef26f3dd3a8ca394d028f773c1c504ea7fac70142c9fc52d4add8fe7e37fe3eb29aba453500201200fb1f15a0886c698fc2949d17b078df6aefeb6d70e49ad7c6fecd4f0648b035fa633697daf26673e9eaf1c9d71959875aaa40884b653a3f0bc65e14f92e893bcf1304102ff72a36c1f99b376db7eddf53a7672a6642103f124cd15743b8161a81bd4c7da6fa8a1eaa86e04f7ee066b9df1280b38bda38bd770293ff04ebe9061fe00419a54291fa155d2463ac377bbc37e1585ed67a090b95827ca1c5077ede8b14bd9c16bab014b8345a792f5a22c7a7abe887131d7c5e9e422fb819a6c49c75ec39fc77eac4adab072a499fe9900fdc9a556b1614a922cc40f1b995468dae90a867c0470ddaa1decdb6e8ba90d1e1dc9433198d30c366abf8f1be7a5ad75c505952ba13e5c56931574a7ffbb4b990bd26279b304cb11b09a37d441744d3380d7a1b54410764f7d5f721cf33d596fc9df24266e2aa95c9924b121b71c01925469ad1063124b517ac8644f5b584ce0b7468b9930b95a1ba2b787607a6f61b6a8a68485569a86d8adcfcc8ad7af5cbc98e15a102b41efbd9903966c007cde9be56a64c74c6dbacd11d90b7419f21ce601eeed1758481079452b1f2207e2fecae67d5be7c14fb9340d418cfbf4521cdf3bb25f8e08589b349dfa3b10a5f3df35527eeac107973a6f002d8fac7beb1a8664a73dc8913e15154decf5fb32c70395d63950873a021ef728faf0d383f5b98cb8934623f398977dc5c14563987b092532cf7fb9e707cbadece61aadf3aa1e334dd9c1cf8600283b9eab7b8a52c9a454b79e24eac4aea78e7ea660bb9c50b567c097a3524a6b6278b4c20feb86bf3511f537849ea1b8ddef9b8b80ce041fbc72c1c5b3114059d5725beffb336d582f22eb8b0de8814036086de0fe50957c4ceb08ac5a570b72209da7204b9c4c9354bfe0144da31623589faae363299890f1ca0b2da7e8486dd5b10953d0ab5698f4f6ba878c2a6cbb50fa1f17da1b31ec9a705ea2f6cc17062b5a856bc8b9c92588d02d8c376c0714046753040178babb09082c64c6da7396366703efe7d13e9f05e324366839d78854e79dde028f7d4c5b3f936c5e57644af755d12dceb5eb5a7d73f8dbb950955d7b8eed42e92b2e0ed7f887a525b6c2ede7d8f0336a0c1c9f0cdeab71327d27f625ac4b704b12d461e5731fa9b5c3947012721a7c3800236bcb03f0f15d3c280f63ec81eb148d851d8ded85dc24f720636bde8e2aabf55de2c71fb35e7ea5a1014a495fc55006badeb5d415a0a60e5019c76f6fdf4447c4770b44ec2b5e526be90337ede6c7a594606cbbf267c387810d21e3d693b222561d12f53d4bad41a997345655b2bcda3f8d5eab7451dcfec0d42bb7e6427b5f22dc9cffd1af18a0666f54f3be51127e55489e4933c7eed525de6dcb64b049045ea18d1dc6318bafb497b5ffa502376b2f5583f3a25783eeaea6cf8cc3aef2e9785602c29bd36237fe3839de8d457f4b6b64a0154f4e111d3ebe8b78de37266696b8c80d4fcac1da3868fda6b1789113c0b176c1b9ab78a3aebbd373095089adaf23593fb1b3edcfc57a7c6602962ec934f7eafe5ba906084004d3fc52724083b323695423e739a9dbf8ddee385106d51d502a7d6e6bfe70ac8da0de6854cd0e1d0400dde8d9b3dae85f3111faf761814582aef8e0e30e967eb9989b9418e679f89dc9355fc76abb88699cc891a5b94b8e4f3e9cced4c8ccf9264cb5d6769c5a54698bd50945088a5fc170954fe246b4361c30cda1a74b74d7cc73c1d01fa63bbb53d5f9a0216cd81eac88d81c5532bd1b8457daa01161b1a51558b56c91dc8313e93fa1bc52ea38c8f1f4ac695adf238278281c143a183910771eacfa9e44addf8de74816d20a8c6b8f25faf2b3f6fdab7050409a5e82f21f3aa56f4fe2e8ec6185cec339c022c90ef759a9761487c2d07917fbcc663fdfb96bf35c8f7c970bd1dabcd7c6a381ffa012dfc02cc6e4e9c95babde5e1ac79febf084fb7d9c31f0452915588c4049fa348df32ed9ceb60162b95aafad83b8a462357458b55ae624295cbaa41fc48f81c0dd95a6f7ec1ff7416e8f1ef9fa53f6a19bf6e4e97f1f5adca47971e7af97b2a106d49c767be09c8d5ae622af49e04a5c4d1c394103e741927df732e5efdf82f1277a8f2fe94a4c2ba8d24696daaf9491b1b0bf8949018403f5e699a2f8bbb40534fabd268500f2d64a05b01e06ba404bf7e3a9f38ce5accccaebc48b0c0011e37cea46c5aab6094c044f9d6a774a53cde2468856193bbd803e8285b59da5a0af656efe0f319c0f54aab1ee345e7bc74f3868d7c4310a50716852918765be805d07a585e5d9c122ed5a46ec8889a16b3fb160bddb29ef078ceef4da926c70ae4948478f77b34e4c4c1ee76f82251d5fe1da6a4ed59d71770148931727111c94d72745303db94c6eb324ac558f821173c1987c98ee99159e04eaa37071d14189a72bf7835e57d745fa950a36060e2a6b3f8c322afe0107981297348313e9c03a4ce5ed0b2df695e2e734f2643964cb65398e6b899c6a61e9a235661d75bd038cfe6a2a631798c9be6ae0b6cd877d79091f8d2b14127a9acbf77a9e95d3ec97c269d75d1196073af95dd4f86e75c4f153904d183f9e3d7b23198cc2e6a6355da3cb589e10ba3cbcd6ea89c73142a8434df6f808211e08f264ddb89d1d6f8ebb6b4d2ef657757bfc403eca5e6d4ddc72696e74e3ddca3d6ab85ceef8fd206bd3b6be68684f2d369af8bb20670564132e1be9939fbe706e8549ee61b9795c5c80786d1e7b7c8386f564b7503912a580a40c9f0d61d0497aeb47c81e97cf64228cc6cbb19de9ddaa03525a4ef90de1566b2959a186e9ed7a400ecc4439dea9cca4311e0306058ecb6e236d088288b40dfe69f54bac55f936e04411844eb3f91333406c0d7e10db52e546512c10bdd65599ae541d904b852f4a62d45d9402d72a6924afc9ed9874b107ad2f86a5e0cd585aa6ef113bdfdc922f3139c77494ffe3f00fefabc53674c8faab8964f4c4da675a549f00ce30e0d90d4996defe1b98bacad6b76bcb2113c40e9fb179478dc358023e72b59d9876043fec49ecad16065b347fcaed2dfb8fd9f9442dec9078cca5d30cc72527d224fbdf5313d847bc010218a954da0a800680d945897d1f1caf33846bf39dfe351377dba2567b36f52149a3a80c6be0073eac5af3677726b8bb85885fc288b301795687ef81db6b894199ac6dbe5bbf28698877412feca77b8a69618e952e5c99887161760165fd09a28204ce92297de3277f435718224b0a8cf0ba9ee7006d85666665ad6cef22e5166a17341d100b46baba449c48145ef2ab5a53afa1dc3b03eee61c36f7228b45ba55ed8aa72f66dca50a82629a5c47b436a17ee604f195ea7f5beb946959d17499d7ed9c1c34cb59579774a90a4f5ae1590986bb9d7474fb4cb4797d6f274c82ce23469941e75e28020ccf9990480f13ded930698ee693841521788a7e7ea387540a02fbc957671a44f10da41d93390d0d5d5d32197970c3ec0c12de713e7107d83e201cd192d0c3c99f2ebc6fa31949a259decc83427c6e989987994c7e1c76f1ea4182b91b86e7afd3efee2fa4f7f5d0fe57e72b5f53a2abde760168ea8fd4b27208fbb20071d35fdcc0f8c66bd7cfe22753343cc733438e6a5050661148ad746ad72606a41dde42d329a154f51a6f8cde31c9e7277a2f2306b248f7bdc351234b8baba8a043cab56180f2bd0a698c054b16e8a8f38b273c5f2beaa735a9961fbf49bbe2740ffc1978928369bdad5b21ab28398b7949508c1bd5df0e2337fad9f3cf51261f6c2eac395040b4440e7e5d0e1549f2b5e5f2ba123b64aeb2eed5fecbf949c8a8efe8eea4be11274d74f215763526eb644fc8df1a8c2c85fe106d0d6d27ea432cf171c8e657bd32f5025250ee87eb9c2c3861a445b2cbe5f58abbb47fab81adacf6b147a961e66bc653f9ad3bf1ad5cf59e2deb28892eb024dc666d26652e51e57cf42d6087ccbf08cfb1bfe4cda1d2516c0a24a3f235b287ea744dbfa22dfa19e8e956d0de4ae7b6c7f85008651e3f9ea86d297d05df712a89f3bb8240dbe0f72afbc9e12118206fbe46294224b8af1dfaf33efb60467bd8a1b4d3afdd936ea5b5a29a43908921a650c9ef318f9d7ebd2b21363640367c0a816303fbf1120615b9d6fa2ddb43a7aa3979302417dcb12014026819fb3dda12e0e79d6d98e2c57473f4f44f9432e33d0e4715786dd66c1ffe9e88a31bd7d664b73abd32bf2cf6f57503ace7ff5f33f50131caf8c2d0f350c93af3bd94041363f6b3a012bdc40b6fed07f14ff5a01d39146050aa37d0ee2f9584c885f5689b62295ad78c975d76f1b605375699218a8924f3832606a961ea2f8dabd411eb4d2e95fdaeedad12cae9ec9a82ec2bb15cfd779327cb4142e90e68c154517a0811a02085cde8164f510e718528a9da7eb20bf38208868bdb7e5db7c5a3a2d38f6f41f8fe51c51440a58473bf98ffbc98e7087ac0a447c0bb4959d95fe5c62961735803ad4c8be951486e0164fa425fd5e1e988266f0737da398580fea997f55d58314746355055ee666421de5b9d0687443596fe32670001c5eaebfe29a96da6157699e93c0bf3efdfc158311f04dff965564ba95d4f0416e86baf906376dfa0886fb793229aa2d7645b499a32eb0849957375085496adc0b812931c0d9f0b753bb7a54d78a417fc42bc21f8a43795d968e636b7a3886ade455264bd37f42e24615a567c46e2a946a04d722e74b5de011e88ecb450f112adc6d8c2da50abf627718cc1644f1a51527e2f6935ad2993ef0c3a0e01dace146a2d904074b738d8652d508cc75b0e5ab6bbd109517cf14788f5c33da0079164cc69fa6b76bd74d478844023c0519fed9d86a5b31423b537737ff5c409a907c66ef3400103431176f526d3af3bf5b4e6a61585988d9a93e1b73dea45277685cd6702dfbd50e6ad854127f3122f022add9cfdf45ce6bf596b250c1fe3f6ee6324880caf25ec93bb05e63d37470fd8c4d14d02d9678542e2077238ea17330aee3082c63e500c7e99d74451c7abbf7035fff5d35073ea13e4f52f9dd51a7eca63a1c540eaed30c8d91b8ce2ef4dd12d86bcc3aff4f59ad87edfcde77a5c8f5ff9e60c1c686d91fe3f8776eafd1ade3e46449bd54fcb989d79ad8b624ea0e90409d5d8548f8285d5c86f39af59d88127572079cea9d7e8b754debbc4c713f06186f9f96e04f727d85968146caa69266c4735f9415a4dbd9aac04b7b21d1c7e34199bf89e6663e0fd008f06c486478c85d1b6ea67dc2f36fae960c8981d57f7f13ea51254e8bdba3c144b292d25ed639f49ddbebceab4fc72fdbf8bc8df7f88f789c4cc0638716b6be63df395f7f8118c56f6efed3c08f0c983bee931c2264cd895a7a971aab94f1e92ff58f8627d4cc432dc4446ef1ecef6355eedfacdc7962bf9c07c93c40ba7fe6a3aaba788d6d1c6f2859f32b718308b5099b6f095e9e04c5f6ef8660c1c81f3a15e73e7f506cbff4b83fda47e32cd100dcd1f15a35a459199d605f829c3cb32274455e5001d8e3244e3630b8167ce872805467f50e0aae3c375f008f7fa12cdf376d17df666ab51e4d910eff28a9c91a6a6ed25b33eef58ed54888a63a6d91c1b0e7c5e9cba4ac6ad589d89ce91b97a20e6315d47cc655da6c91eaba11fb7249326099d2ac2130af077dd79b5fe85f76344861011fe7fd4d4ad6867a3f61b14eca0dab03b3ad429dd36abff6d7f25dcdf1fd1c6fcf3c4822ab56ac9579813736374ac7ae5b4bbfc8d4f455b9e2201ff749a4b8e872aa3eccdda340373467fe54442fc65fd9a1146db909c3bf19e3c721a89dc6412d138a4783e3109f8f702f594ee821ae5a890e5b9295b79c53799429da6e13996250a3ffd87cc907b36e02d286d76f1d4d161b120be6f27b8bf0f8b0ec93510f17eeac43cb111f5df0686b445618cabbdae98663cc39aa7f2a650c1ab84962736232bb442a619d23fee28531060a6a95d41eaf744177f783aa041daa6782ebedc05a54f5ff095c79e802cddee625e97071f53b3eddb508fbb5acce738ab5d61b093f148f71ac6e3191fe49abd13f5fc782460d6faa1204f65c38d2f16a6a467ec33c7b7a0167c62407900edc9c546026204bcdbbd80598be06e7113351d16ea808443e77da825daa1dee73a86d72dcdee16ecda8adf719c766f26931f7ee9bf99825ca9f4a4bad7cd71aa0d5fce791a6fde2068d5423a214bbd279f9b7db703a9eba4f1924aff83bd2b29e394c24026de7a467ef384213b64e0c773d6b91caf810138f532347ace0debd9bf4d808a2fce43f25f382f6e82087639f2549aeac74f2c91cecad85e4029ba1e56070b748d8dfdbb7d0a3a2661064aa7fb7b76353f63b0740596c48f6a94e7717192bb8a08689a13202d8bf4b7d78f3457a1f39e0d022f96f0d4acb9257c939326571791281ab630ec77a69adac5176c066cbfdfe582a48650b97dbfd4d56a49355945d77ef6cd501509bc0de585ae9d91067d146092c8dcede12841289d5341e479442f506950b74ffd9bb02073515f2dc2058888ba2df3ce9d58f0806a0012e91be642c79887819b6a6a1298ce7b8383ca7940eaa2abe5e1abdfaf34d4a1d0bf4133f6d6ab8d7c6fbfafb9f24bc11cfa200d7a2eb387ebf7254b72d1ff3db648864bae9a9bba65ffdc622fe97e066bd638b1a20667f74259e5f16869a777eb20f31338c1d8db0478e5aa53ef064d619ae8d274a6865419407b3ada7dbe11694c571b8d037863ae384339104e84c012d936361f26df00e4545ba9ca2b3d2f8f60597f7d1c2a3ac92a1f7ddaec99703bf331cfa0755f102326cbb13866f6f2d4d17cf090ea585b98400f9d2be0b10da4b4c86bf22029af04005c60f78fe8495a9e2f0df1959e33d5384723232d2274b215102912a7e5dc4d752924f39ce06ffdc9a8fd64650a96a361d719d7b383d8724d53f7057d612dd69f3aa3161016ed56dc8b27e927f16c3f36cf02276d12033054ca7c83fd140d2368e21a54c7013c7b13d916aff9a34fd3de9ee5e20c1babb940446af72f1b2e7272adf7d1eb6328aedcc7c61ef3ef8c6b897946efd62e35ff83c29811b2b3fb823360df6e8651d35fc6f984b4f7e63d87c089abb31070a14ddad5e317ef9dcac239e2062eda038df18116230176dd4cdd7ce3848701e9e9630f29138bcf41e166ad3c2a0ea96f4d2b35526d1db5931659b81f83bfe78656a65c23d9431c3cf3ec8a0d6a40b7138464842ffef1d7b20b2c48b6984b18390b53b2d1490700753e3fe2aeba36f9452a6b1f51552506f72fb101ff42f66ee8f6c85c70a0b2c92d45b8f539c665a413cb82cedeebfe041d8e270ddd4e7c8ffa8fac8ee5ab34ea540f92ebfa0bc4145255bc75ef08fe95c92f01fdb1ff03d8e171a44f410fda7c37190a4b07ac36a3c9c4fbeaffea0f675ce4f134f9fadab8b46da1078b6de83077bea7449b4ee3a9df52c9ec9e216ee7070ae9855d3c2a363b3e01ae2796353eede8e7b557be469e7db886ba8bcedb49d1d9197e74bd33cde5a2f388f6202c0c12ab7c0c250b7c5e909d0198c1a386bb5bc86a8c114a650c2df4b545b11266ec6b7e6ed0b6e25e4968f6d8c3217925403ae792004a1da7a240b3e5cdab0d1b90b11a01a08f2a34850fb299d2d8862d44b38dd0a18694ce0f48528708b2b46f6fa18598ef7ea6c4c1d2c2f252944ee64e4c341663ee51ac105032af6e41d3ae43b189a0626739fe0c02a85d8f0906ab396801e0b1bebc84e2350946fafa2dfefdda26d2aa1794f236468b2a89a67c49627a09895b86b96902870da598d638c08d813db396c0245fc1dfacd378a25dba35a8e19584e6bdf3fb6a83aa957cb9ecdc4b28e4b269670e2e93b325f07aba8e52786dd7130c99ce6e7de07b1fecb55be3a8f19993fd3605acfaea49fa102214eeef299b11ebb18934035c4de17ebcd70f76563d593d57707950d781f5dfb8dbd7aba3191de6a43fc57c7bcac9801fa49125e3b70b4fe2a14ffc087209dc943d3797f7815e534c929e2f0ebb5200a71d4cc451fc1d31c5599e41712f2e17a33c9d67f50828ae7db521ca1e92948168feeec385b2ed14514a582746662a7ad5b5a6fd536b32ead20b7d33b58ac22003ed62f9fcab0d93134dc569d3def00c6749609e7f77ed2216b9ac2361d09f6e3f1a62a66cc8921ba5b9c79eed336fe6da0fd3712cae106bc0c2b703487386fa4de61c4d60f79e2fecec5af87eef59dce5df611f9a90edef3512ed44e0ad8d85e8c79615286bfcb0237bf8e253364f15deedd6f717dbf1902db612552854dfa4ab89f6938d8705f17308d2882c35955a49d5b8e7c89908ff944d75ffb7b80ee477e8b126bcefba1cd8d8875b32e3beab2683e7f1c0d705297566844e7cba9996dc8a7e0d02c3684c0c041f3cebfb696938008d85627d52966805d22aaf921d9258ff79f1b44ffec07a7d43c6698218ea40bd0fda2947c94bcbbb7c7f45513e72779c9213bea6a6cfe356876e8ecb17bae1d8cfffad94f6aeadceb235842d753314d70f9d73fb1c0e5c0686a9d46e18ce223ef9dcde57c9eb4c3f157a61a815ae4cdb22f9e89194baec6b648f00732875154d648f8c0f759db2cdb2df688c9e3ec29da5688c4ec828a78f547a38da941b925e8628800e250a76804ec56097f47caed75c56b9594aa5434b54c1e0c74079acaef91bc79696888263c9decfa801fa03c528edbbafe93cea0593df79cfbe5c5ebd3deb6bf478d3598e25fb8d1299f417f03d62a9ab9f0097739cfdf07535a9f78f38444861fafa32ec9bf944209eb97e63e6300b1f461802e60366b5f3f4a44e41db9d7b4242bf92cfaff1fc9992471bce880b0293b47b4b76ae2dd26f970a0014997e1e0d752fd33a5a419ae857a54ca2299bca0ff8101c752fcbd140453172db05f4aaebf80b5026fff5442ac5bfe0e49298f164f47223bbda7e4b0268f92dc3e92b2676dfd2a377d1b9dec6ef2fe431e1b8c150d99c3a78e65d75786fd52f70f5523e9dc1c6aa359f575c3f794d652cb55ec13416d3abee9b2f4ab0857ce5be7f4cef89015da29b68166e047a52e3ef941379651226cc8de5a8dd07d126415be88d67c02de0a3ebe1ba1f6d65643bcf15bf311c28953f69fb79817f4dee6d1eaa5256e98f0c50b43a529b265011b65ff09545c94967d51540d5665e18cef318f76368888dbf0e77385d8b2ee6806d0050c18afacaa0eebd42b05ac76fdd9d6f91d852a9ac21b2157e79a5f2e35718402fb7050cbfac56985a46834da421e586193a4b47d247c28637b01676dcff06dbc9a0d38ae7d6b3198c08e2e657de033abb410d4a2772f4b51c3a10b1ea86fd8ff73a6522cb775c0b5891202e1e024cdc10d8fb31b53fce7ab03563b2e074696f180f61b0ba56103ce59bb263f86f5675e73a3249361510885d1b9a11f7ae015f65592b98253266975e70ab2d6602977757f9260d67ee43f20e6e5666c0e7354e138a0a81c97b8615265411b591556c35417ec53ed41c366329bd86625e9ae9fbf5e387ed38e41286941fdec5ef9ef17fed16d23e80a504fbe9d27601b03e87bdf61604172572a5364d16ada748a404b77c51dacc2ae8e466b30cdb229b60aaca1aa564c8836fa32b713c5e18d96d2746257b888f3c91a3be9aa383acc85ed3ad74fa11de4de56ae858651c7f2be199283f32041bfb94d548a91d76b90bf002430867a4f2728531e3f085fcdc267f38a8345f1bd89fd45c86544c33af2e40b8b0bfd5d22642d860f1437198f47850c8b08823090e50a60a205b58f6034eecab3c491007314fa87763624a7dee640ef825bc25616b1bff7459ce029372015bce9412d81d8c24f6815821e7b8ef0ee5b05849c15094041f2032a244ddf700fd221a4fa8c251331501ce82d0d7f6626392a47d3e49d134ffdcc6026606684c35fd1f2e1f6205a23a5240fad41a75edf717de60b142915626d2949dd5287c657d30b60297de8dd26562b5fb822e9edadcc0b934cac6837e387a425e385942e2f8f33437195abcb04208411f1a610fb14d5165f52f6d9aa68a482ac3a7f14ef7875af1edfea8ddfcf33eda0c3b17dfe5e027804c3d2a83b184a45501d269dc4e4baa2fc519b0fbcac18e0037e2204a8b44914e0c597b8aab4b7ce3cda505cc4b1ee9f92856320cf247de9111383ceed12181f5c170196a75aec72334822e0fc2735897fd5e7ee0e25bf296f23967d39b2e36e22e778afe9bc2f01385690327179984ce824324a66dafd1fe1f534553aa66ee4f98aad1c0368f2b3184bded80165511eed505d622889e6e8698167eb015bed7f994fe551a33ed3a7dbaab0ac6a1cf56f99305d123f198e637792a346d0ddc52baa4e04d218ffa063743c1bb64d36e10aca4b9c32b14316b1667b56dc81274e81369572ab7ec51ba2350133e9667ce6d3ecc61103d1d15824355b65fdc1af6a8f918b071cd7901de0592eb0dbeead891f5ee2ac957c1bd695378690b631219bb180f806da9becafdfea44ed362ac1f3bd3655a12e8117297fd4aae553a902127d25818762a9f3cfda5484866d49c2fa5e1a503e9fabb7792995eb5f6e19217e0218d31dc1556cc80df2f7b63b5c48118c13bd18d29f1988345b781aed0768460f6e99c8c8c3e52ffdd1dbd41a97af5bb44e1de295024bc88b77f04186c0a31982ea51ffe13f0324b2e8289f06305871a5ac80f62bb2c04143bde4186953d0b01140e2b61c523794f0e6b7ff06516580306c4e620b85ae9fdf4b4cc0140ecce7096a87feb05fb4208b2d02b49030e306addbf32332373027a404bfce8c3a7efa8200c39626add512886a818d8b526d0b6ced025bffdd62e9f812aefb5b4fe6ef138d03064d94122ff326d889314eceb341c46e5714b73c1f0f42b834763f81f3b47f4db911a33c7a447a4a2a996e2e3fc90ac3ffdd52ad9f621c3d43c8a7947e609a51e7577d6c27a6c5c3e0cbfb87609811b526a886d8ea600b10dc5a521d27007b176f3572d33cfc62927fabfd2619a99ade79e62cf612245abfebcf3cef9cfde74629c07638b134995b18597676aff28172c7041bee0d91ac4e9b612fb89894ac94ce5bcfe5352451a8f985f2cdf771256586b6f67fcb6bd79dbd0976f021123c4a6ff13220241360dcfe6d5cc7a0a514ecb57ac04b86f7d74885d51785045de700d10d65f9437849b233bbbbb12381471d43098d985d7c99c2df64e56ce29a979a83f0584cef76322f8176a7f9700587319e7c1caace5b9d836313bbddad3a82f79772dcf14a27fa6d41861a01e4a63d2cc526eca0f264bf91649fba4d8b2e71df0ecc3a18b710cec40b44add3e8f31d1e6bb8bddb197b1f1a71e9da0760bde1c98a77b072ad226cb39e8659b3c826bc4f73ef2369597c717f226320e3559fc601b52eca86b80bcf2ad04dddefc4dab7c99e39db06cd9d189840b5da3782f75bf99c20154d49f9a3354848f0033ecf2d22b221b92f35b43a28e4993ff7fb2f4bc37f488fe6b72a70aeed6c9ccc31c4331514b8cefbd640759b715d5f79285e327c44fd0ab5f2f3d57a66a4aa6fd7e9c5592358981d93d271047ed54d70447c6fc5bbce43f2155280aa657de91cad990e68b740753ddbdc23879e8f5434c3d95074181536a36c354a6631d504494e35ba73ac23755630e12c3b97dc1b22b4f37b82a486b197a37062ee010965b3b6756211a9503960a39d5174d936e457d56c5812907c2677352cd3e4f1402bfb18ca4a56b27a1ae4a6a91e34d3e12652d38088dc775e3729179369f366273d4208e3cde8d32563a8f859442e0c922023135ba0411154f9a021bb30fd40271f1bf827c430dae639039ac540325a6cc4f58ee436b3e4d9485db90bb5702aba9060e77633902a1a910a0de09eca622c922d0f79ebc1774c9944e46c8183f6d6aa912d7c58bce24052e62d71f82a1d3262f7541d5847d10d51baffb4aa2d90543849e7a496e1c42eaa4096b32aee1d6cdb13ef7a7cadb0741c9d40df955955d5fcc391f23b60bc0a00fb708b48be9ea0bb21490d781aa637d8ed36b51e90963c5f9c1bcb3d887b9e1543782a34952f6a4cf157467b1689b7fd502aea76bdbc3003f4aca93a81ecd1aff47a41597a7530d85e1838dda87382e9b6f679f35b1efcab80b159c1776d729ede6fbef0ffb31e22df3d44c92e2f98bc04dce7ec5ebf82d1d27327b898645f1dc14b15f5b89ba81386f8f11ef7ad5a5b25bedf64563e4149daa5cd4029f1ec082e2f171e445ec9a331d6c3933daa01e0643bfad37616060b6a51d3054cb98ff2dcab0c9728ab22e9e7dd8483750c0fea8a113bc28a037f18975dfb1c1aba19eecc5c28693a41cccac3a265dd3e6c0d86e0dc02e38687902477be974bd831abf038a2a6009d4feb34a265b8b2637c483a0633382a9ae3bd2f3822b22f6772696a9c4fbf5463a15c5ef965ee8671c7b3be5eb7d298e4df7bea08541dcf412fa79f2c083cf248f7063ccf78b4fe2d61bb9fb307a48a421a7be7db875beaec07d98dfba43c7430fcbbf34ab46c9785d01580f86ee1f1ddd886606a65679ec45b5bea9abaeb58b8aba570b63a5c50390f2cc371b5e91b0a3b27da47c2f7412999511748e490bd5a7323427d67a8cfe67abbe0bd5161cfcd17a912575dcda3922a6f80c6a47475775aa351f172714351718ceab976bee79718e96d7ee9a0cb0bffb0256c5fe70030b44e2406f7145a67e699a2b06922c6d0e03b9d8e0c108c0e0a55cadf68a5490b6679c4be24b191ff0dbb9bce487a80c5bbfe6a2cb940a9d686d63a6e4f29639d8ad80b8ee3375cb18d5bd756ebbc036793e1fbb855f9e5c5dd810ecf02ac219802ab4e832fae1ab2de66c4ec52fe5d8c5decf732c302bd5eece88e76718946e17dbd4e9fc151a8f0a8d7268b435c7d56f11d22a6b3e8d924674a069934f510349eb3e6af6394c789625777009e9e504ddc7562bece989a4d14b28577881a7f04350be92964101dc4d8b8cd75640672cd8a33ee15113529f4172eb6fda766cb4fe443d68595b181bc4b52fac53767f41114010ea383bbb582fbac6e6c033668ef35f757964cbbd3839b6794c98c9cce14f36531698e64e84382a2f07ec088c77183283a906932943177a73169279b036b21b40fbfcae17b6e8ce79a70c82485c444e6c7d54fb6cf3b855ac670b8f347f02bd63eb472a7e3657df5b481777377bf21aa2a6329ff786c89609a8350b49c0b8ea55b65aa6db6faab2b427de2a8139c908cff516d0fea7615e4d3dd5b9dd737a6ea97a80fe6281ca53fca5661a0a626320ba60a7e9f9096d7f29b00f6ee473889e66f4ff64bb318ffa047c5498b602e543ecd59d1580dd1d976de9d02c10922ae7bb82715cb4f9528b0855efd8b923f6099c6f02ce47bd7bfeb09c5a4b0f616139367754040e0306977cd9c71166dd52c6dedd95c9faa3a95726cf113095e4c025fd2aab930bd37f4aaa8e8c947d21be995249816309ebcb60165bbcdfc420236e3931c92889d44da5a34f9435000c179ad1e234cf54ecaff9e66cef73f6f73b633aea1189f4928cb6d3fe15324dcf1f3b63b6ca45776838ae4d81f0f5d3828050dccc42524c7901e0540ddca6da763cc9f1a61216f04380bb585e64285a9b396b647bc5644e2127c139a43f00e72449cfdaa3f4fcfe495716473616fdf4b7f4b42129e2396c5b3eaa4100d8739fc7cc5e24d00258e3d9c448377e555a48e639b2fceb49eefb7390dbc6b23e5280f6abba9fd81f4fc0a132887d8013b2b33bade33ecc7bac3a5a083db55c44de7d84063935be8c0e78bb261e0487d05688ddaf914c507247e9f34d17223013d44561dd0c0bfce9b7069f7f1d128d00ae4aeca3b3ace85005dd9dc8bfc4b028202eebc0eaf0e331985414bfa3408a0eb90e27c80b5576afa474298b4f9ab41ec6285dcaa252b91a261d18e77707270446a73643dddd0985f91db349548bcb1545489ee3cd364777e2367b4e5d975ce8220c806f7b4271e5e34bf53bbe35ad59d5bec2dd27d104437fe8a774142377778b36522418465ba3a5036f931558ae6e3ca02b2f18d23678cfecc70e8eb026886fb8fc47b0cb4bcd5ff38322319dcff3cf639ac2e0d32c14c72800e8d37efce36f2ccd89425b9b1676c4340906207e3fb0d3a27a5d6b9cf29ec426813d902d209b0dc4824baf930f66e819c9ce17f14c6efa1c0bb49d4605ef7ab337b9837f585079721340cee0fb55c9dbdba64141355f0b003ea8e840657f4df94204d653d44d9fdb28673daceaf0dab70d74a8e60cd8eaeee53cb07fdae66b9fd3f97f81d74b8cc0d91647918dba7eee72fdeb2d428f5f24e9d6b0b46e27e169fa91e89e3376bd2843b50300e71651660125edb863db92ba086783364028220b6f6134325f9f18feaef07ef48a2f6b7d82e1cca62d5b5b1c4da55a6688263feef76de269e8bcd676672d5e36a31717831433cedfb0fd8dd2c4bde061c7e80484f5f41df7c2f33656e0981f7d5f6404032a73d96134a7889143b601c73e93dd0251368140261572bf1b2ab82f03979fd23e407129ae52c28549c2e167f95881d72ee6f9ca06f98dd5b01fbbfdbc7d78a5c98579e7242d47b4c138312e94ea2dbb60a7f62d9bb39cee29a8ce2eea3fdaacf95951a728cdfb0082d3a3df40458f1603fba797d284f00e9fde030e9cd5e62d556fd627e51a348b48600f9bd3c332be07128c6c88e55e2ed301065692d5e9bd67771ee12dfeb5f6a98f2f58e71107777bc6f56e81c810a5f1bc6155a2c535e2a218a882a6758339ffe61214b8bec173bd9be94d25d4fb4bece0c12c1eb29a2832846439fdde7d78755ed6998f0560c268742a18c50334f99001bc2f424d844ed58730e7ae43daac4b5b95e8a1c14475a9818ff2c2b3747538acbcbbb2080e21c3442a69bb552cadfc34da178eb8c844afcaf43ac7320433171fb803c69b38682fdbad6644ed6232027dcf689da0950f0a2e74bde434e0fc96965a74a4f010538dde262843b2a144106de7a7b99d1bdb265ba091d6fecb6cbfebfbedb9d77777da501086e4d87e775fc765c7bc8c5d1fdb7694e93059a124b332592f0990914951784acef564c8793f4fe17a5fe7aacd310825efe1878606233f0d66ab2aa09047e3b7bc3b8c984715f77be217ca3c7dd84ce1d0ad92d53588c70e765cbf002175c99079e33428db8b5bf489ef9975ed22a1b90fd492a936e5c9cf4141f4704272d29701b1d01c97d717b976ae2aa1a72d239d6d2ed3cdacee6dc3f154b875a51a11d3744f6eedbef28d2cb4957484e7374f1271171f5750a3b160550f5de64e0d4d6355e5c6fb697575108be296efc474a5b1819f747656564778aa055d4d486d2ad5d04f1534db9e899f954cffea2ee28586b6968dc9bc1fe0ac3e7b8c72151b6c1bec89c18ffbff36261d15b81a8788f871203d545618ef28905f971a69af87b01f3e81ece510252ddd71883ce48cfc9baf253a8c56bf0e3b28382bd557b690314fbc9e9c1fca40bc8fbfd6d6d4f2b9fa9788f517a633a2f1a2eb9c37d2075ae8fa412167977a5376a0355d21da3af9bbeb32f4ac1d6422aa48153616b0b4131a72cc363be9ffc0c2b7fd4f60dfee0fa56dec191e07a9ef4350961e368c5a1a7e020e8cedf20c3fa6a2c2496152f7a72350dec5fe1d2256190f8998a4409d74df6ff14a43fdb1aa035d13a34825b13937f436a6683900a95aa57e36ced9df93cd37b70e8729e9524f623634f4985e2e31fd8de71b8e9f6dfb9a6fdcf3c47a65a0a74443cfaf95aeb5756670f56830c5d2cf1ffb0e008972c42da13eb423589036997813712a483149771a88707906e810b28a3c033024bc3314314756b86136ca4ce534214c4f52823df16e27a55dcafbd412aef61a566d2d6fd00dfb21f765929c4d74bd4ee3f2e84a71231cb9a8e4debd07f04c75dfc5755dbe68076fa64ced65fc5a953dcd681497c122f87c5d9fb01b235d820b101b46d84d6554e8769db175c6c1ffad3aef72601e83868a94c3ab82f1e7f6d25f1603dcac565bff7b2fa606d2b73f840923177880e196c2d7854b73d0eba410ac57aaf44a4d88fd420b4fba3a09d0d8cc883bd7c342bc9b7f8dfde7394eb3cd985c386b59a3de95d238e32e0faf3b0582c6e02d0a682cccfb0a93d59c37598dbc010096c7669ef077ff0de913f3d43248c8c3f7cbd10937e43790e6dc23af3e1b1cce50fd7c45c42f46c7c84c78d13019bde3d14f3026d56dd09c183be8647a10f844d9c3980bfc3545c85ff23d1040a4f3e659045ceaeb5d84cc2f7e8a90b18b728347cd3bdd860b62476771ceebd05fc0c22bf2f9546dcd0523c50384a2f378fe6891872a21495ea042539ed5d19f7262532a7da2633a6defdd513811d0115bdbe15d44c269ceeab74fc625f663412c5d56aeea884d7ad8d9462ce9319e0ea05bcc0b6aae09beb80258019c565c4e19b2162ed8c587640ca459f3956cc3dd731288ea8a11fbd95b8eb99d5176c69513b8ee3180a4774db4587972e21b736183ef5cb6bcc9f53e84b9f075a9a8f1dfe515e933a384e0676935df1ecb45ee40fc4f632b2b0571c8506987457cbc9f8fda2effd56bc9c53c354b2e9f80da53a89b3e77f508d39730303a265bf2569c6b4e02f960c1548f2c2c719e1a6b57339267065eb9ec82432f380bc3cef09eb28e7d72a3f8171144cb6f22f8b50110b378be546778b859880995ab2efd926b7a92e15e5646d8b0875d6d51f2ba83ec442dd4c535c6d281db9523e5610c65ed919e36c84355d2c9bf64d6608a82ad2f8d9e42b47316633cb58cc56492d8217ac0238cb5ef25621730aca1bb0dcd0375264e515bb126b329aaea428ccb79854d96cc4e675619ba2cad49d986d2150e5765549b7bee6f3ec66d9eb9a5554fc1786e09a905505b80faeabe21b37b2e54fa2b66962234947239d989d4c600cb1cb2a17064c5177a913afd073c3b1e137ff3162d9dca6643a627b988eb58354ee7d0ba2f7bf391d4da1e2ecec4b064e881d334fe54777f2da27e33becfcd26d8271052ee4aa80c038cb23d9bf34ff2b0ceecb35594321101fb529dea383051e32878fabb197790fdf78dc73139124e633e6ec1cf22a2ebde4584e8529e53b6de5e8cd77174695522cee45fb6343cae7236eecd60426e5c4c82595e16bc50a43b9baaba148b89f5951a3a5894ba467a7dc2e11cff720a57a7b1f19c42db407f147b89042ff6b8d57003cc6e04064a11bf93f01d2f9347e6de6875e9e5ff825b05e77d2787d355598529b13f788e53c5b3534473038921b33fb7ff8e6a1c1b842f1d50b99510d6530f20d5a2a3712e8dc1fcc68e8b2128f27b16939ff9e45aa59886254846e22600b12cf68f693478639d18924fe8bea993a4495b52c880ea0ec466b39802c4cea892fbde6a84ba1aff7f5619472636af5cda7504a7ae2ba595abf8e764d8ae05be5fe1d3fd91e664e3b7e78ac70b9264cd6d7406b57e8e1696532b51e67b8de6064e423e9dfa42492ffd77efc5b83c42e1126ee8342b313acbfe67132541fa34a4bc6257fbcb8ca13aa3fee2ee3e06dc70c0997f2f5387abc78b7316aa72526695706b99d73eced1993c6ef6f30947d030e42b98ce8d1522c361c7189b1d9ad6cf440274520a45c1af85d104929a45636920a396235d38e4929da3349c9c65fc6f4156f0bd7798eddf801ad0050d880a74e51c9fc8c9ffdadec84aee17b53f79512afb034c0e6cebdef173ac9a760cdda57d48bdf8584aa7fcc753c0c221e13e280911f427fa0e5819c870a36f543e84669b43521b2a6dce39926ca2a03c740e7d7e96acbb64b1e955c7d10c75faf92c7f137bf8f14a8d56f0282248a97c206396270b1045d1d4574fc633988903193014f6f84227af90b3535eb43caab0dfb517ecdd788a09ad8518bb4cc4049c05843adfa52fd3b548f642e056cee4180f546aeae2eef5eccd46e8dabe9af9d4335bcf73c4ef5e19f5a09b0bae830e91badbc0e43a1f9d3bc8039f3b0b1793f376d5102e952555cee8d97d69401581652781f153f98ceb2fbe55060145e5839b38db0736817214a18aac41b2c779a799d2989055587c542616541e0c89d1cfe69726a46248d8ceb9e210accf1fe8067e16a55b965c11b8d3bf2e1e40892f82c26a0f3d26a8f41c78b09391cb55fca7736ef0a376aa532ae9a21cb6886eb67d378e500539f399814f923e0433be9608553811e3eb44283937d51d2e33aab705bdabb45dcca8824d2af7a3b9ab049b08f6f8a791f595f64eeb1c22f0a5dbef3cf8ef3cbcadf76534628a65f82243ddd93f956b12820279855bcb03ae18507eab9130c15d0502a374f5bdb0bba043faef4ac99544de1b50cecf65d2a858901e102ef5c73c52982d63e204c354aa10992f5d8dbbf57fa5848d8afe010cd3ce99d7164de1e6288569fd3af67cfaa376ff557bad8e6a71e71bed493c94eec0fcab7eec7f920bf60f126a32c28e20756a497f28bcbd8f7ce2168f3c0ec55ce1f2c00914137c29074bb47d4d628ad2b7016ccd5611bdfc95084c4eff907c9b690b317206e59c01aef5d27db388e377148e2f150f3d2c954b447b13c08e6684767996980d4ab03f9e8fba17b22f02fb0579012c4901786ffd7272d7169285131609501f418eb8aac88a08a2bdd542b1ddbd20a8d0ae410999563d004d602fffb95f10e6712a31f70e2af4037df0a7d2f24c851e7b8dd7ab7ead2aeee9bbd1c8d9232bad5253f4cb56edf52fa82e99620728b0899978e904e82d41d5ffbf13e5c9790b53dd49d1a57c8ce822c19b7a81ff8bed8066687b364c17e3b734a2a8a02b94135c0de164968be31be517a9cc616b880a5beadfd9181aa67efdbf2c559c4000ce592d9c09bca61b0104a471f4608218857657dc2d8cba3ab2eed5041d2cabb3441f412d78353c57e4c283e0f6b552965a270eb63d1b13d25c4bfe349c0945d5918ce91b793b1baad6eb655810a6247f6ec993bbd1f01460a75a8252a684bf4cafaeba37e77aab2a0bd0272498a8a7500ce2889a6ce03ae11145831182f6ec52d8a24870a7d6067208d42bc45fef9f9ac5e395e9b8d214c088daebb9d53f4d8eb96b4bde30484cae1ba0d6be5f687e9269813c62488e620dfb397a31e879e5f78bae55586691762767c0e3bbbf66e8618a657b013d829ae7a2451f74954fe21331146f438319f800fcf186f596775b34753918a7a94b2e24736aa06f6a91ae1d8224bec319203b0e6ae62c353868a0f0ff7084e73d96f3dd16a5fd141509c2a41eb1f56ef157d3766b3a806b7f3ecb880b4214499c6e00ea8c5bb9b0f7a6e01a88490347e68d9656b0ae21599f5398a66b10a5d4a2c2e96f20f371290ca1fcd8839bcf1c7c2f457d2ce3a635df238f1175e85161203394e2284233c02a43a9cf2b4177878cf534aee3e356de4736e4ae86b5c64475462408d4a426f84db462b5a140b8ab2b6098ad98632b146a52d87cef075447b139f913ffb690574ad9f461aa8aa884d944e6954f1c890ee8fe35bc3f3eec97922be37acde97407082d2ba4151e07249cb39c16c7a2c32dadfc79999c54c9da3f5956dcf4033d814955bd55cfbd1a010ded6210c8863d17b076838529393b5ebcb481122d4c9f3e439ed80c3a945195edbc4c4536419b13aba780354adce384d7178fe1bc914db897c2b0a07760bc49cc5080f8c6b622c40ade3551c76ad1382166b6fd3e43387547190e5297118aed7d761f20d44d5bcf42f79cde36fa0efdec4e956679cf744b2eed5c5c4bbbca09d83b9e9b6392e96b1619ed9f608b0c464db3c9c185e8f0c0fd15e4aea8d83e8eceb837d3b1d7f7ba2409acd01afeb837d5930beb144f10626a69eb91ba2d2cae47aabe357cc11ae0264aae77568d6b4cacff5c0de6a14a90ba3721fb521b80d7d4b6c1436f04b87c20f468d0b010b5ab2077a34880efd2a356d643c03985ab3794bb192a04939a308581c7e9021ec1393c919a5d0a91473b027de191b351364296a375122793863e2af07c2d9a0c06e9eff1c673ec529bbcbb5072ebc56a514aaa596ba11a60dca3465c46e3898a65c80121f58472c9eef6aab37edf8822d48763d996208944ddfb01314fd5a38f76db9dab4ffe3b62be794623b8d96aba5479863bd0260d9e20784d04a65fdf6eda571dccfab1392f5423bebd943b6c929719d8f016ecd86f14cc1830125ccd33b7b0ad7cb7f5b653f7010b734fb3a5576c80d7fa32199ee6ff9f961dc5a3f92b1bc496d108e06ceecbede49cd6dcac9342b3a60280837631ba449903fa7205c607aa5481dca98c4b4b583f2df451a7f06e112c5b6259c66c7924078727559fde287aeb9b7c765b4eb62a0c9a1ee723ae79866ea649bb652c4d141f1d52d312cb05c894343f19521ef0b170192d9c8b844bd905ad6963f108d1afe0f2fe0f05e28fecba1fb33eb4f24c480dd71775b444ca6b750f7ca3d5f2d58a5d2ed282130bfe6114e6e27bd56617aaaae14b0933e3e610cc1e406e620702a3f65a818f85fd48ff4aa8767bcd9c88202ef5aaabf46ca23a8c848b63ca585cbcd2de134dfa69a3e9e993efeb4f983518ee39f8720f0f85533549ee45febcd34d903ae93cfbfd4f96ad7f4bf46fd36e196c038d445937ac7947abc4f09035e89e01decc852a8f7b743d740f76bd5d372397a496f545584728a4de2a05aba546b431a75b5a127470c3bd0fab7f29e05772c487670953447ef4ddb61fab6f77d1b1d51ba0dd02826163c0019e1578eef10127435f403a9e3cd2cd4ca4043f63673cd1860086305bbd2ad7b66dbad22e5a6a9794aab58669e5f8383dadd1ac1329796ffa3e645501433ab7b200c889d6fadf8b4b4ac45417b7d941c182f1e4b83e5d2809a1a7d38a33a79e25970ccb5d4c69a796e12f81358918f6dc9092e11c9bb5613636375cb7276d8699b5bdea04f022fbf7e2210cb04e44de1fea85352b09bd388451d360b88a8e6b1a06b1abce7c1a7ca3d6aeb6bd6e9daa758cfe06f77e8dd43995f4c9832c84dc8d1d26cecdac4c36ca4bb1245ae81062eafb822bab03f8284278778f468dbd1e96e40dabdd826d99918f08700e8caff0a7bfa5807d02cfd4704b3aa6edad0bd34f1e7144f38afa6b8cdf626d25b359b7560eb734733cb2584b3c3b53d7797a4b98d5ba993e504fd4136615ff5bc2d7414f73a7a5b9c371ea49dd0d4404856994f8541f194f5391e40830afdf8295a0964a41a11b75774105f269d2a0e1064d4b3d474366a9032eceb01afde78384db9c576e9f5aafd7d0283328b4020f35f2e521e4b2e04674162659ecd59c1f01653a26a57f466620d77ec39ab063db9838db83cac367a6dadfa4b43bdcbad3a6e9bf852bccf1a6fba5c814873938b6519963453047b4f9d5370ebaf1a3ea827cd08708450fb380a0f132cf44fbd5882dc86b3ce76ede9195480e736face5f9074a9f4289adc06fa3bde6fa6ac3a605e8d5437ba7e776e855ab0002c7fa890fc41753487d85c239b9386fe20bf4969258b19230122000c78b20b16a4a6ea9d42daf98028115e947e7d1c5fc0506293283822695c33378de41a1305d331074672f3870c5afdd854c54b4e25aa066d632d3758440be564b55ba738bd47ebf3b651453871c2a56d97a3a6b0d091e74fdad7686cd2439cdf1230063980fb5e3c8e8d6506c79d33db2244526f9da5795b6177275773bf49c15b6fcf54212170f610059ebfa79443042795b4d15b8bff2d9afdb85014a7dbc11e1f3292ba9cb411224888b53d62b426801a962ad05c09776111a023657cc9ce58a563ea47fdd4fd863fcab20ef6796203cbe97eafd26c074e95843dcf6f8d4b4bdee95f5b3b2ea45ee31ab7aa1d275290ef78fc480635ef5a381a1c1b33167a92f2f170639dbdec8747fc2211d2cb27e9774fa21a72e960ee11a3cb57b4a9182a051b827fda096b043061784635f6e16e7d9741186e54dea8a3e880cfead40589b6d7678543772807dcbeac9eb713fac447f513b5d5df6e2f1caa25212d1b291e299dce5b67245634f132c16cfb307850e3f01fa8a285749a7be32762a66dc090045ccfe961da10f2ca78fa88dc20b474bf5d3ecab59f818db05e4c4209564f5c1687921bf7fdab46f9647dab91cebfc4e0a37acd970f5e90b2de0abe105a489466449ad1dba4e2d82d993940bea333f451c1f29d6c223bf2fc43a21bf047dfa4243eaac36328cebd94e09b7260a9b30c950fd9688b9f08301e307083a40bc374ddbf72935a294e04a1d32fd3255aa9af349e7360e06fb02039fd8e7b32a5e21d643a2de16a0d2bc69e8ea2c84ab7a0e642cb20921e12ee6453f14e5cc5b8ddaaafe0c9638ae33bbd78771c043b8dc20de030b1f2b9752001a434745d5a9d1b6598ddd69336cd6bb13913ed0a1b84006021662af708b3dfe63c0a97e42c2bface7a9f18e66c73159ed06662961fd2363b4dfcff02135edd7c36b818348e3b7434329e3ab074540869bc9ea93a433275fdcf643e11f21681be5a78c324ba529fedd3609532fbd68a2819b6f5b2ca03d9d3b857829d2156eb724e4e3d26662c509ea562f4721cc88c2dc7a5a3135ace08986435f2599cbf2f94613d469d4ed617d20908e3ef5f48111923e2be350ba75f1ca78d2e2c751a930d26dc9f2b687cca5e527dd9100129744941fd8d0c0e62836b753ec903328fdf012efdbc6336bab1fd6c996a70074fcdaf94cb93e6a08295659770a461cffcf8907dc87eb78111b772e91293943007b79a97e22bee7b408dbd561f2841cdc0c169eda65f83278de022ab870e38566936cbfb93fbce7ae5fa1d74183af5610702430552ee0900dcc3fe71d5cbed5325ee759f77021cc2861f546029f47559895f4522efb7f2a3f549443e20f62b7054e9a8d1e9e19944e70c8b5d8c744c5a1dffe56cc8e8a2ac37438d26a24cef067ee58bd1d4b36f98842a1e0a7622869cb42f5ea06ef76a19ad753af8bc74f207ff10c8587da29ddac9a455c82836cdfd800c189c8a7c7e47fca0ae30b4f045766222e9eb855a168a3143db3285669be335c63a78c46e85f5edd025275445bfc1be7d77d92521867e404c536307c2954ac050b8db5d08b1459c7bbc97fe22eacbfaca2b566d0ba4dc24e94e93c251f1054bc22c53d520fcef0e4cb6d517f18588abff03fda7ea05e2753c158995608df0bf9688fc58734c8a0e3b1ebfa2a92659a3ddd18b76d17452927a8474261ad101f0fa2260d7ba9c7f303de0a47b40f487b97fb9a4362faef34e9aadbffc298f4b2a6ea6ccf476950e0a853cebae8ea4d6d6e38ba28b9c4481e0e16400a458f19e46d964d1b7110ca952f6ec9d299b7c85a70d1319d9fe9d779973e77be99220c59632ba5b3e6dacaadfaacadb0adae61eb3b74a4a1bddcd56c1f2e729ebd178d91cad419f547a99a6e75fae2525c51f691b07313841a3e3b17d95b9422b810306ad133bbf49247daf7f93595b875b72ba345fc1f6267bf65af83107850a6e0bfd42e24942fe121250286b712dd7ab3cbb995c69646b06b75e6b5d017a98ca4ce885c29d707751b172ab14f12c726da61b44eac849436eacef9a11f7ba522b89cf1f7daa4d7e8570d0114515667604165788866f34d73abd7bde60285fd88f446f2564e6f46ef1d5d9a1235f9f0cada8ea5a3a44f7ea1d2cdbcd57883513b6235275e19f7967769e5d3e0b147dfe79eb83abc6e35e0502f93c293c379e7d8807b17118dac434fbfe0e1a2c36a27a6c05a3398f1c3a0a37f1f097686ea0192c0964f30421cfba8845df5e6746799ffce2aedb0027aedb4b03cf3ddeb9aa8d9ef64e8101b1e3b6ae52059011c2083afd8dad65d38b6815f5b46a352bffc79b58b683de505daf7164da080a551c2b53a515237a0d76bb51cdf7e983cba79fb238b8663d2192dd74c247b74ab4b951a1b1449cd5d1f38b524bae4dad1174c3ca7a99e493a5d7a7072c72b0bf07153583a5baccbbc5aa1edaf7b140afc7c9d14e9568d5997b30780810a518a33a1518379f473b4288d759c33d9b3adae0331ddb534926fba10e921da45f619dea3bcd5f47d8f07c5291aa733d69115c4c4c0bd3df2d4b117203947a4b88a0d8e8f241d8ce8100867a6b4be074840f6388d603b53585c485274f2589cecfb9b1e977bc3906b2e86d1d0366e7546bf22408ba6453ff39f96691bd97d4696f3aaf679a8302bdd994251d56b53cd636fc00d926f2c8a6d3487c90b1926d78322518922eceb036eb40e6b2cfcba9e958458f328c1a22cf1bb07e39d230bd6da7a671f9b543c7e41861d19534636444d44f88e34ebe6fedd7878bfc11c5ee1cb35c04524ef144883c0fe303d7f2a6aedcc1f75e30f43a1ed77fc60de9fc2557e666f701333080b2b1a1258aa0f3c6dc573fd99f350bf5339eb51f76a9a3e53f81e00b6479c5a0c6faed3a64b140663a6be7360b545b0acf5dd4cd96c8c37e063bebe0ee10412ef672867256719033a01166ab6adb72a9d599f0e211569e1531534547cbbbef83917d775592b327125ca202f80ca411856c6afdaab3ecba6c28b520d3ffd313eabe65fb99e238d7224a3f4b1e7414417e3d6d93023e438bf06b3a571037136c7d6f7cb6bf48827b57fff50aeef99ae11f334db7f5bc6dc91b459cce58ee9f7441d2077b0c5bc25421b096c7cada1812d51e35ed93317e1b2bdabd9da75eca750a8e4b8484d5f96645cf02c4ad9d4b4299f18814625625919b288e642f3ed588c1e9a38386e44f3cfe7713d10f1f5be63f5c19117627e966c4d303538d11a64801fe79d5b64925593a0a9679d1c6caf23f02872a2ca5b8c644fefe1653b94bb8731159fafd961be78d812c88506bf1468846497bb37ac7e2eadfc9c68394fc644dfeeabcb490d61c3625add8323280d85436cef7f1318c91e2394bcc0ed7a1b92827e17f1f58e78e82a385836936c80a4356d33043bab873bf7ea207291eb02d53cbc54d4126aec3ca4ba821f15f5ecb05a3c3772c8291afe9cb4dcd1a402df0746c27c6bed61dfd6dea530ab827892982e81545341c7d0244a46916d3b336b1e7111dfdb6e8aa8c23048a593a87cabaf5e5e4b1df4303eb51558fa12ecf379209112c5bf8d5a5057be18721a0f3e70f65af3d56562931e506193796363f63135a556f73c2059c6aa668bd76392a7f0fc256e5d0d8dfec7874e8c64cf59aa0134b4d9f39d06679f877fd5df38a4d0600de5e6989fbd6d3fa7184abab8eaf2f5447dc9e961dbac7578c778d05b694b7301ffbe7a7bf0dcf51217a6ac1b8476863589b0b9e502325a36f0dc1ca3e66cc9a030e1229731320e434063b28a0b1f192d7c6954d0b639b93191e756a1f0306c6dc46aa88dc98573c069f1d27a23449684e3a1f813591cf8ec6dc4cb6283199e71ffbce16262615a265f41ea72c283a83f403d45503b749f03f7fdf5e5213397a01bf37311a18cce3152f02cb934c0d97688b14576323e8edcda137cd113df9abc345dfc6e6ef330419210ea849e4d1e072b6e0604ae2e0d617c443ed63cc9665c274e4d72c09fb32437abdbbcd167862f25de5d9887049e2b0a10488254fec89f6e51c09c7bfb31bb9043acc24ffe65bf6cfbbaa97da4b4794b41649a2479214e102280336d57c520c5f1ef490d3f0f503034e64658e7ed6a84a994b43cf5b9ee0df889047d8e43515d9107b54abdbdca561a9d403a5a846a70c90f766899fb03ae2686217f33b853865f268df0e6bf09ea8fd16b1e0fc42a22b31c6e8a23f8a906198cd63d1577d1f935860576231dd6b685cc3271eccd25cd2ad0f62d465922b8876c20adf3b9f8a9831fade136fcc893a1538c3060e0853e580a9173b2b7d2b0b7c0ff98a649d0edde028b4dee3bc51543bb8bcc46c6121efa6b315da9ba22ce4aa4cd13255777716ecabec82d49fdc4a5ccac30adbec8ffada67dfe797c1fb675e64a32574c6af178b1543515b77af09583ba1cae8577c93bc582bee104cac9ae03545367ddba8a78aee5cd54185ca216c0614d57d1b8b4dfec063f0522fbfd194be67c16c40d9945b10598c63200ee347ff8c2dc80c18d2d3a9467308c9ea96aaf1dac52599538c4e05326f2f51413a458b45271add63cfd4d62aeb6c944db4e452a61b9b94b4529807024454f9cc59a39045bf9b05a66b06aa23fc2282645b8df8a62105b45f32cba4057ffac2bf8d86e3dc3c439d4f67f2e21758b9a41bb70377180f4c0e08fb92548d1b19499faf938627051729bc329386553cb9f26038b6648205937a867d79219ed2643b7ebf84e46fd852207db0417eb5cf4ac30d9191b079afd22daaf2f846a18431d73fd6a29ad3a0200848b2ca50a8ee53dda5ee545d523ec32afb9bea5711c743a16fdb79fd32c56cb69107e679c8a6c59195ee07a5b5315bbc510bff40e24552ff19ee2d1d3db0e3fc08ebc99bce1f78c0a97c1ad7b96590cb38beb51e9e8d921b9601480e98b79b0dd3f23b253dab8604d051fb421dbb3bfecc6b015e96385529f9278645e14abf475c0a6257373a3eadcafea9d46cd7df81c25be8fae65431a65ed541e6c80a6ac31857c44c93e7398b2ef9e8c0a37abb250c1034b2f7967834da33258d7d26b2a8c9975c0b96a78490bdbab5348a6f062dfffa2ed7674a90909b1828a7e460d3c99395cf13bf39731552391bd055acac8a7a6422e176b2961de38c65e4b5a1936c4463e502c6971c54f8deef17542826963bd820531c514deb1cb5389364442f4e61df32910f5defd323e7702277d178c4027f602e3c2082264b64c3360a8c438f9ce177132426ab4e85043689ad383dcad6affb9bf929e2ed212f528d308173a086dd3d239c763353a833ec5b1815c55a97fd61f9fb50db229f9603e1f698e36120faa22b672dd138dd12068f912ffb8dc3119f4f9d53fdd787088538caefc2b237892503fa3179ecf59731dc5c0df8dbb727e2e752cb7049957448a334e212f095bbd5fbe17302075aba3d4c76a99bbcedf6063759b95810ea72d01b42ae277f7069aab77e930e447d4940e4e0fd1e5e2be9fcd906ac9712f3da6855502a58918a1c1b17368e48b777f129a120fd1b6558097ab23775d55750ea1c3ba8e75b98de5480d561942c55ddca97a995d95d9012c4b3e8e1b28bc4c2db644e9a2067e387afc0bdd977064919f7a3953d2bbad23d4ccbe63e9c4fe2955daf8dd2728b3e55384a2261061817cbdabfe361c9d6f13879858ccba992bec0530f679e6ef40569d6882e5214bd501455ddebb02e02a4178fcc9dc6e8eca88e2acd5eda1bac46c02752b713f9f7f09f3b20e1af133142b7fb213d0d10a0f2bf617a0a33f23ff2fbbe828b6669b483954cf66c5e067417f9affebc97721b57bc96015ae22ec154bfea35b82d0bdcc831c6c049f3277df8803be0f135fbc27da6f682b7f5435dc6581cf4880728e17d4f6e7aaaf59935ce7f336ae0a2a4755857e9627416661f9b97cc143d9cb836892f2b427c47c5c6a55923382eb051d96478700f775583bce8593cf888a9da4336a4e1d0f0bfeeac835edfbabd14d48dfdc45438224b88210f46c296005012727e3c9b673f3e75585350fb5619999b1d14c3a478d0eda6c26edde4d180af84e5d739093ab41e62468c6e5d5c62cca0ed214cc54dd033bc97925b697f1dcb188b41f872ce243d741c96264d3c4f0f1da8600dbbf505890430b574646098011a25ac5eb1ec2dd0d0a59a6a9e88b76b5241b3733252b7755ef49060dc778b6daeffa7e8b0a2e68d8e942d4b4d40680db727fbe21e42407b340aba016f3f7b0bf88337be2fcf00c89a99527ac6781a830b20766e3b73f38795faabeb5b5f0593a5c48086f7498e65d3ca3a561451fc5dbf1a7b0311fcfd201ab9fe84833021bd98d821eff9122eed024f5b053ed2e5f5b36ad96164db4ad3747005e1a16dd6ca1b33d7b72ebe7cd33c2c33e4b71ee7fe6752be54128b4113579b3025af9ceb275e9d070605127d657b4f8d95b09eeaa4d52e2830e5ab4eb781cad67cc49d115190dd2e9125aa51c5eba3fee1b6b5293b2636ff4651e83818e8df530f4b9374314fd725943aa54c91f070d7bd7c16962ee68308c9de62f7935351c353706bb04d24186228b883d73391c8346aec837c161f850561e11ede6e9a6bbe0fa70b2c7dc217b5607b11f82fb1e89c20a8e65a7afadd973a5d5a3739397feb55b6f47468e7200977a77407eabe80967f965f6de80f7cc6867a224d2435a8537ce5f8d7b3a835f4cdd279c7d4d0c33f61ed1a459e667e79dcfcde3450d42260d2cf786b24f857e67c2dab82c1ca76ad07cccaea6a3fae15bec2c2d72cd3a8da2922ccc5c6e1e7d7e5d1ab27f2a4d9397963446500fec75b3d2287d41acdbf8fda106fb8f21e8d2ca4a696a20ab5639938faa62d2f027372517ae4f26c06d2f7160a368c4f56d4b449301925e69ec3b752e92947da1129cdb1e7a163f9685f2625cabc31c3cd3ebc99848a03de319287be0e99d9707a9bcd80d60fef2df1053a33b906154378faa95b2cca71f661d04dc9f5592dbf5bf3687e6647532dff9ae7a21f7307a59178831da06d9a5084ce863420e4742719dc4dbc98f8566a47877f07db71baf346621475d54274fcd04a016a2c41cadbac0ba7da328f6286f1d213d1e77d383e7167d9f0f66bd878d3f5a52ba1a9790bf9c9b165b573a89d3527091ed5ef7faba25f78df51199eebefd38e9638545d08e9c2b773eac33e7ae442012a048f57ce42d3e0993b4165c19a43ee7677364b29188dddaee81cabfdf06d8d50421ffd8d79bbbdfbf10bc3bf1480f257ba695e6ecba2f67d33cf0d89c86907420c9021a864ed5ad759d51d4a8d1602776fd8a96c7c33b7b42d6e1ec58480f413ba7e82bc0b1318bc6d21a9dead2fc7a1aeeb05a1133bdff277f82b7620910269ff87e6fdfcb7dbdd3fb643fd2d53540c10c581f24cad3f3bc9d9acd3cabac5764cd68da22f3c06d6ba00ae2f25f966b01f2199a52e78fa8afd65bcaf3ecc6fce3a84edd9a97018380aa4a155be8c7758535bc22765aab34ce3ee6338f669dd1937399889bdfc3bff86a2ed030376cc9b2b0effa81972a385653a634068a20ce5b969064ed5f3f089c990a52f425a93d1f6b9374bfebc91ccc218a69f635ccc89bd76b997b641fcbda584428112e1b5a6bc1cadddcade9f7c8bf48956d883430d78aafb8a515613dc8fb4d7b13c52b1f4af1d87dca7fa54b213aa8eb9147b02b645b89347d0e183b26bd15a9ad037871a65fcc16303d88add60bb78c6915f0e7c986f65beae3963f988dccc483b45e685fec39918337683d54352ef5e624e3fecdc7d200683cbdaed5fff199bc0496e762ce71c0148b94c0aa8c37474a110029969e6438a10d64f093a36d286f68599c4ff4e9380750525edb32bc81aab32daf8244ecb55a3edfe0eadac3bb554b769740c13442fe10e51774976c7716e7a9159b5aa9f31b8896754e2a1007bbb69a0380a6af7b73f5d4548c9abe022209cc020b7afeda17f258a7c2f0887d117e90054fd81758a8bc4585bfa340f24bc6e4e6cfe59afe20d0536350ab4328330b908aaadad5d20887c3ab52c23bf79f617c6d25d69ec5e8f6898cfd53491a3b9bc6b57431f5866f6354b8f91ef71028169b03bc899ff536e6bc6af19c98c8a341ab67595b79af2056e6865fc9f5cc64e4bf8c4d26f8e1761317117198353a1210917085cfcf7b0eb6c20481a2f7cba74e7c1e1d7cad217cc689587bedc8d749475bfb4a80a22f1ad48e70b673ddcdd02a78f2ba5247563596bca12c1b8c72d55bec680fa5d6fcc2fe19c8a0d9836ad90615b7d4688739728909a10c71ed7e8005a726fc92a3af4047bb0eac4baf023f82be9e9eef3e066f31a38b0fe257765ea43b8637b9d5842ffbd17b3487441a34c29439bb98617e6f0536b2277bcdd915c339ecbc9918edbaa7f4c09da706259ef625c5aaf9fd2d1c49f9436257432eb7c902a7f27c9637668253da371fa8db78ee220f47e2aa3710cf8816d978e4d997d0572290d659e3f6dde2bf7572bdd8964c4105636a068b3c83d379d1bf9cbf4b0f6614de38a046409d79e1ab7277176e4cdc372abd57b4a8d573457ef822e862cb63a8645194541ed9e17795387c5272ff66aa371324e0214d4b860715f4e7780bc281674d99550bbb2fe0a7c0edcadec00917be115e387df6ca75d08ef7b3810d055dee702ee9608665592771eb51cf21bdd778901308c30ecf40ffac1bf1e71f5c91b56f7e48b0b6e39802e39b99c69c67ecc829fb9ccc278c85a5a6df5ce485fe448f94fa044a1a60f4b68aa0a0f81165755a720d5fe05577274e9cb692d4a79803096a5b23f10a1bae6faf64f5155f75cae123ee1666d191e6095cf9b7d07ad93175d0565093c9a96189878873d9717106f81f6985f90a8883362972ecaa91900639453e85dbf2866c6e24997aedad345487df0e095d135efd72ee95f3c77209e7082fcd4a712d5084d687c1b43be17d84b6b497954266a4ca5a12f757ba059f264185470f120161bd20c83d14898e54d64fa0d459a1104800b6cfd7a829ee9cebf239f56fe99049266b15f09ea610890590b337b345f1e0a0f6ef0ee657f6139bbf11613eb6bb18c6ed30d46264aea45aec95a642a21415a4fcbe288fd90840234cbb8b3d50b7267b18349439c704435c755490cc4d3b51ab5cc06dfc41ca74a27142f83743d3321de00620d4b41fb92170c0d9b44e8e0405ec71a71226d5d73f95e4783772ea4e3eb914f24a84b6c22e93a91180eecbc73de6a0f458a65a584625e7a0f1deb898bfe321d9dacf5bb4aba8e7244b05cb146ddb0607f683f0e22d15cffd2f2b91acbefaa5f1c671162f5cf0b9aeda80e3f775722ea6d974466964e9472b6b60b35760c64453ce41cd29cf2b834a0af8e644eea534e28e8fbb3865fda924a5e2ffff4e2234b4a2f1bab6d261d537067e8cff09c8123a3c6ee4d90ff7e344de22850a26f6a3cfac1d2a194db58aa0fce98d74c60e48e1226e666a3956ea2fd8ce7736f5a396929305935f55b887c41c6f2ff248dc7e2d3086524c095b9cf07e9aba1c4d7ee2f672ad4ccda69c692f2c6e8612295aae9a3a935d52e2681f74de04fc55095e7b5e2b5bc91c602402650aceb09581d1f8710814c5ad475cf6bacefb78ceea21154a97f8aba26bc617cb3f662aa1ecb77573e3f9a7b078efa172bfc56ef9c7fe927580352d37219debbfb89ce75f4fd1f5a7da38fcd66d3b4bb1f7d8b9e28552b7a866660edcef97a31840137e2fdc36c230912416d6b5e35db79d7b6d7539bef5770b7dca866b3e892ccd6737194622643da307c794a9d2d4627e43e85232a9d4b92da5c3a98501a2617ad716b5558c46b6a4f8ffb08ae5d280045a603ff923d66e58ce4ba7a324f1b25ba6a3b30460ae85dca93082112224b55442cde559f1b496574bbf6979eb60c489c962ccf61c47ae95dbdce4a62970bb06b4d311636a21a180871ff89a455fd7c3c6ea1a1e58c1682d5d8c7c3691052524dfdbb86ccfbfb3ee6cf70aca2c3b26b3e3797a3c69d5fde22d18e69b2db5e9f12f98756dd4856c778d40ddef09c4144b353e23485b1247a6d1d8c6db33208db575f7cb225088ac3d946937489c10a7f9eca58ab7d45105064896f5dd408ac8ed1bc95d23ecdf94d95713843efd57d54429af8ea7c2f47c047e58a05749fe953ccfeebb731c8368cb777db6a2b2c5b23ae54f8b7644ba3c574f8033869d639cec783e6ae6bb4e95e6a9b99c893ba14f501378236eea1ef03135ce0f67bdbacd28a2280d2cb66e32af879837364a3518e5b4c5b5062ffed859d22146d27faef417aa43b6d88957685e4b205913f0a7b1dfef1dd98589770a154d741d81a23c2e643b84429f74e54163f8ce413125b1b321e464c61943e8a72cd474b1db57b3b47d41d42edf6cc0066f3b32bcf455ff029abaafe9f4a428b6ae1c28f7f4123e2122376da80983daa797d57b30b66eb74650e6b9b3d7faeb50178de5a0ee39f1229e82e5fcab193e9a6f149694decde519807dd3479d9dcb741c27bd9d40601d9c519f414a22ebab999c9fafad768135c87edffcb498c461a246326f5f8cef39afe235c81f9deea5b2155de99e5d67987f1864220829f7c9ecdd2db7cd4a79006701c11842304469fff1cad7e9d683a7658f8d3bd64bbd4f8f801ac6ad2eb9f4b9ee5d7bfa87d4c8dacdabf55498616168caf639b00aa30f99805f5174190a6aa3fd9e71cc7aae391a7a7f5eed92be831e810ad48e605acb66130df4cd91324a900282efa72aac3a3d92fae8c322ba71eaba8006d7c69a34dbde982b72a4bd6ebc7a6c222b878ad363cd4c629c30772ec5314140336f6c548e82f77e1f97e7f403e824af20204f0cd9dc5b21ae1fadd4981dfd895f438e81f6cf7f4d64808066531c9ae554e2fd2c1c9c4e37fe0c7b95bbe2def95bb3b41e24e8b307cd93f60d890dac9cf3fd5f167c0b4bcc95feca9f8d47ca71bbaa89555de33fe5437e83de6a18501efe0b4eb7817e91c7706863e5599d4ceff5f94fb4dacdfbce1d5e89b2dbb53293af9a456b0a9c26c2e63f9a220de3f5fbb983375d8b6e135f3ad28657a185edc93349abbecd6e1669c461eca8433ccf4787c338db24e6eafc6a88701b276133b334cec6e81f59318babfcfc95d1eb4ce3c5100fa29b2fe3c0a829404caac9c1107d1e6be9c3e83d8fa516fead7fd14dfb02499710c61a79dfad43e139f512293ee0224e1018191654b87b2f8550350085a8dd0c63438bce809f1a1cfad29f8a00961fafad7895f826eb3c809935b7e8353ba4e710a83990a43a6ffe89c705799d9dcc02e5aac71a663b25ae122971e19ddb50551efd78affbe0d17e1df96bcc7bf2a13ae356ecbf55c8bf92f9d728b71234d5ddc90ab3e4cb2534b021018361667918a28bf20bc8dc850a22e08b63788e381d3810d97f63c9ab9ee2bfb109495ce435636dfbd4676822512cb68f522918161a0ba3c84e484abf8f8dff9cae0b4624ebe2ba05d2e937271a2fa21dc3230529f0e7b1dc9ebe4b4729a1a4a9271da7bb66311b277751841e2ce6c841edad195d6c63f9abd99f00e06caa46ba299e7fd2fe0eb0ee43a26f4e807e11924ffbe9c105cd0c5df8ec7a3e12ed1a71e4ba7ba4e4b5dbab03c22a21b614273ce33d422d5b6c8a96bc4d6a51769558fb934e8ae736002efac80c3cb8bd395e9ee163731cf5527437485a1dc1f4c94ce93b87669622c08a6098b097a96646cb0ffb1b5ebcbbd0ec69e0decdd2c3966151090720ee927eb2b0ce62d6d8a1af17195729f2421df5cd4645afd8f204eec01de3659c4daaa894eaecef3d5c02061d296f27bceac9398b4d9414e1972cd05e61e7edb255f5ac6c4f0f7e71f270bd68936fde66d941d20da31dfeb9a11f9300854d40cdd2c59da835a2c6d09cf1b5791df44d27a8c3945c4fbce815311885dc6bd18c45bc275fb2bda096ac7db13a7358210dbaf02f4be93d6849da63031b3191f2c4aa37e5a1cf969f23c4f0f81c3210b077aa7b374c54e7896d1793988e0afeb3abe56b83739742ac038a6a305bad6887c25d345e0a7b7b375c5cac954319131ff213525b64649fa0c3d0d80c316d0d22d616711a49f66ea21ef5d5ab10afbe61b63a677ca73bb890a4b661dabf2e9c4a9fdc759863974ea6c72f1b210d3b777d5077aa0b116bc6cafa79954ab74e1f7e82a2b53b1d3ab27805392e6de182bfa7652b95d5fe4d63395626acac890f775df21649e5a5f513957fbc33c9e58becaad5631d66de99f1982f7e8cf1325f0554983929bf5b2dee90ece0eb629640d6a23d404473627647194a945372da7c6c116b8dffb44f9c45b38b26be028d7af902bcdd1df8b09521fb895d4dfff11181e6947be1fb5f796dec86b44d296e520b7e68f66ee816cd08a0a27f4e3977e2e35cdfbad7950ed6a716092af00249b6016da14c4ee46edaf5649d3345f2a41e83c2ada74bc9075a3bcabde7c6a13120133fe754a09e234e829cd87012e4f4830b0df2086a79ce460b802917d212568a27d88bb9f75a47bc9524e72a79cbb21cfd87d3cfbef8f2c6dec2ec850dc4daecb3247ac79decbfb5b1bc2497e56efcc3744471a984b7973e94408feb79cde63e688f25f1a4557295ba8953138cdf06d40c2c54bfe96ac97bb8a25f9053b98e79c7b0c569725907ed9f9f91c40c19a0c620d5b03a62e8e9f77cebe0a4784cf77f6c542ef02808dbf5909a862497555b348fa6b6cad4771d901ad9bcc1dbed923abab83a93821223c9354d634021f3e7f014190e6b617653384bcef54ef9777ecadbbe49e9ba6627e9802baddd7314b54d694dd51051ff714a99c171680792bb3dd187e47569aabd5db66463eb50b193cdbd9a8238fc6bfdcc563edbd7cc8906e26fd8ece79855ac7b8b10a6b2706280d1c5ec52121316d68b4f0ddf06104d7f1bd3fe5ce3f7cd510b576da7ef89319e9d176d52a1e8d45bbccec836023ccd7c747bcdbff97973b42fc5bffa4edb21ef8ac411abcd22eeb995f0ffd21162bec62875b05b1121a1c4c7caff4ea8187e05f4be21786bd6925040dd4a7d89d131f0d724daf6a6a91650c9604a0551fc3b6a81f8fd0bd1270cec6af4b87af117163cc9ef4aff7027a40f4ee154021888395fe3cbdf7028f4a10adee135a9faf7bcfe5528a96775a7269687f763a694d843b3b435d98545b8b95f2fa0caf9191559bdf594a92745fc1c99d47cd942e918026dd8bd64525eb42f8219bf5a84ee70de3b2fd669d694670445696dfdb31ff7429b7235c834ac667e50cd99ffa4d79ba4f79effe14b5db26d71d14d8f7a593d790708d300fecbd445f42a08011d8fb328aecf21abcbbd5b7c34c6a8ab79b1bee6e142226deb834de5d21b52a8e99b56b5d431b4648d3da3a6f2abc79ee714637aaf24a8d6bb303945fc1aeb136ad282028838b298ec5a102fc834c167a2e00411f5b07e9a257c9ee5ddb53d5fef23f5610fe89c21184406fd80914472fcf9342aa10fc52f5be08239d441958d3a7bd27118499e3775da0d1e1f79a534ebf1c091b7b40099363bc407d0f805c3e5fbbdd6b2b75eaa9f40f4b972a8db88d40d90562d7cfcc9a355b716beb2a3837028b6158d7cd62a816dffd5dee7b75f7193711093fad1e467ffc3f270d85c7ec73daabb8dc28b304557d4303796a0da72443cf25da7e1ddd5f2ee4f5460585635249aeb7770f67a0cd19c07e0e75e28e91263781dc1e152cf732981ad5d6d529e496bc753a3a8014db1c7e2373328908aaa733b35562204f74596d5a6b022982c9abc09843eb5bbee2bd80ad27e6cc8c97472947b565de32f58a1d3a1cde6538b532879fd5cbd070de5967ec93d1baee0d345bad098ee01bc5890637d176ae22bfd9d4edf3bab09ebcb8b5547310eab88eb30a86757e48241146fb1202cf1feb648bbd2a3df6d6de0f50856982a145166f3d9b87df27154b8969d0a767583b2c1af9af23d635c2819c063a30db7d432a99c297b869cf615222f5d3032538c0815610bb85ff0bedfed2bfbc660897a3478a474c90e7131e1df5f232b16ad30f88f727d7fa757bc22acf27bb1057d4399adaa64c636302e6b5c86c022b6601805abb5880b398de59090b750be884c437fe23f010752ddd4c95335e97d86e41ab0a1bb7d79e3e3a892e886bb19e748ed49904971b8899e470724876600c89ff281fad7e68d4ef636d7ffb9864b01c923e7da349f0a1294ba1ad53949ac566d3f3a212b546387439d1514837c5491be12fda97e0a2346c31f2ffc7ea73d85489ee9305501b1d9989d9a1bab8a80a3b3e398f11016cdd6437dc2784d6e828befe48cbf3afeeca826a574dcf2a78f5945636fb8753ec1f0a59e0c2c6ebfd2693d47985cba44fb82c41f8e8b14a2bf92879d9182114cd926cfc02077048496a4c26326ff434f867877317b08e2857eac841f1a6b270bccac52ec5fe0a16e3c304765db2054223be88d26e376edbcda7ddc1b8158c115d54b7029990dae24f6f2c2420326bdf1e11e4a9a64365983675424d64a08e9c149cc31caa7461779680b2054c48a447d9cb4b91ddcc58bab8c3ebb0403a24e6829098585a8091887af501f432e108f32f2b48f97bcd919cf63b6f059e246c7dda02023a2a97b1f4825895430a9d4a3619998b6d9a0694545bb192fbb79450deb8972b935a91dd95bd8fcb7f6ef4d8f3c7915ce042518ba073e069045b98aecebee9705096e85cc82dbd79ef04d09ebebffa6a2292d8609e7b1e76aca9a1bf5eea1479c76e94b217913dc70137abc806ea3b5c72de77a95d24e767c141f0086a4ffffed021184d76d2358359f772b495afbc597527d23944a995758445170e47db73ea127bf878d5fa5614049d3bd9cb5f506c85f0b1e029ff989108c3086288bb7e64fcfdbf8e342fe7e07f87abf2217d70fe50dc6ac3ec8b88a735918d0fb0892cb289239349ca49adc66367263d3787fcfe5ac92b0fdf7cb0c571cbb567468a59c3b7e1179acc329bb3feb5f15f8ff3bc27147dc13e439fd76e4e645163b88796b5998fa49eb1d36a8653c9ba8ff58294b52341cf9692de1f1b0dea53d5e15e3d7ff24f41213ace687537d87933a4d0e909065d0a2fb3937169ede7417607e03cdb95046485c460c3af440e36400cb4ce1712662215ad8ca23e912b4d523328c142276d4bfcfe9ebe9d044ed74216bf7cce0f4c64a6b776c84bd99f9a5f1e22095a31894933b4e2ef3c22c071ebec0ac771fc7fb44d0e6d55e951e6d6e148090ea12d3eda4cd27b5662b8e98b8a3ad43512b881e021bd1f98dc66f815b5f61faf1783e8bfef27cfb5c8a2ea96cee360d6f2037056341c2bd643ddc6d6a11b1ad5696f40698052593f609e3e30a43e63b41a024ab274d9bd497d105dd6cfd4511f6ee52f0c09e0761cc95502edef89045189de9641df5aa16ec31fb4916ed684d4d8602f94e60a65e0bb0b025d604522e8bcbd3a1826afb2f5c9b138a16ba6181af6829aa32160c0d3640468f2462fbaddf6f14ac09f103f2f5c5bfc399416814bd1afbb211511865fc4db1c47e28a673c80797caa47a8bc46880b8faa94904b49b3829c6ee409dc37bdb6bb02ebb9447cfef5848c1afca6d8ad6dc7c0b56bf8af6559f2bc6f1f8bb073987efb1abc1dff3bdb7977e5e72289ccbc94617d995b00ebeabef1177b184dd55d45347dab462b2a737a52bbcc370204b11d3b1073bc0706f24af56f2cf4d811076aa8e25ecd0bd098ac338b6d13867a001488e2da01a928cab22ae9b270e8bb8aee7db0fb258d96d63bead6a0df0b5216f0238d927fe0c132af6b81f38b7f66688def89b0dc344b4f4645b61f3892b248cbcda6131e94e49db9a5d78b4cdc75c2c74dd31da13ea36376b2e545497feb3a283dc489c3dc753d6f3dd72294c6c8a76ccc89d89c616469707c7e3b158d2a936465ba9735c0c86a1bc381f9cc3cc2c0efac39082f841e750345d6acdc832e1958e03390c2a6ed1d5e7531ec7649218c995eb54884240ed7a42a1fb6cdacbb50b638599d16becaed0d81330e22d3e589e745bba903d3588b6e15ab1a7cb23cfb7b88082e830a793f05dc97dbd2e198d7dbf63c1baf38110f6a51b7f925f1921bc4dfe0e050ca60fda376de211de9eb3900732c9af19c156d1aac25acc24081a41b3070ae821c9d0d7f5cdeddef98b6416ddeb6335eed492fd0e13b038ffa6464a825f5aa8c06d2bf6a0be7c304cb4bd6693439f660bf23051a3af23f81e0259a923f0807e0aa7b9c0a7e26c0f733483e1f696f4304a7e27656805494e0f2e8fe2546b7d6cd35bfc568deed2ae6824ce408471c071030e17afad408fb9c0720d8dc072efb477b25749856fb9b276d8f2c275812268792df63593b7da2f5c3602e899860a7d2bb300e06ed3fb75d64a56b171b0143db5f27707b96c9376d877a61a609ab68ee8756050dbfe0ab86756521d8a638f1b5cae8bb225ba28586daef5213e733c9a9dc4b23f52531aca6b5b8fbf4e4e1bc4fd3138516803d704c0b44babd65a8cbb555488f089198f15cfe8a95a220bd079ee8e557dac7aa97b398fffad8a006df0c7a04b61d1ef1649141d53f18209c0425a07c30ecce4697115ac96181ed8e578af964dc1ed0b5a96658fa2e4d80894ee8eaf8d175b7ffb8e1552199f0102ebd9f821a5dff4f47eea395a1e9d2ca972d49e66186bf77eda1b75c68df1cab1b938cc4e5d22d0978cae92279f70c7710f7d776f3767cf0dcc0aa7ae028d961dad02313136f64ea3a97d7e9f901f009bafcbb4fd3b7a776e80a82c759c6ab27a9d1d2413fa2e90d3f86e7fa777b9c3106fc996d4c89b025e81fd0b594a807b0ee74ee499cbb3625a0902fda6b9e4789b7da8bd4e7d3794be40ea7ce98ae4c150c443bf8d884326e8fc293f10a43b55a56c00831b730824ae09d6be2a20491ea2f8a979d3966d1649d480b8ccf246e8ca6f280565faf97107ef085d6fead29e60e7fc0d0970af0b7825b55b25df813e85823bc4f6c0074751f23b9a592c2a10c83efa190a2b128f1de514f35ef85899e3136a425b9cb421d70228c1db1e15c707a579e13da69acdfd83ab5bae3a71688acdff76eb9956e9bb815dabe160045ff9ef4f6eab656993d64a28b260ce445df095e7842363822453aadeb5686e44af4446a99543555431d5d8cd38f2afe1f2212561166472164fc16c74a52b5f1930d7984886f165d0232a0b6a52a517196b17343e359a4dae4a25418e748643c401940a7153bddcefff7f2ce395076dacf9a004cceb708122260abc306bba3bd8fd1c95b5692f57606fe3b20ee392a5ba3ed8b417c3d83cebefe7b27fdf93e777e32b7b0a4337913478df0b3d6b43c9dc4d49300e91c0bd20e176e7141e171f1f76e90b4405e67658bfca4e4e1b16563e7db1f5734d09b390d56c7c8002738bb6bf3ecb779f7e4034f41d52cf77f3dab28949c35bb48bc96baba4fd94bae6d068c820141877a0a7b0c593db19d0bb25f3b4574501eeeb8f65325fa04c8cb46176647fee33b22b18510441c6228d681afc4ff3e0b8ec0646973f9335643bcfe2015d87a4294570dbd26d6a89ed50c153f4f22ebd5e1b99de55f86070ae8d2f7fa1a3879761d65ff0cd69ebf9e46037a6b0ebf2893580f1f1df0842005bbe7331939fcb0aac25997d11ed009fdf61ad7854f4883d5776d3ee5bfccb5ada883317ee41075b8cbf090c8ad53f29863c8cd9b043a6dfc20844f143f29a1da0b0154611de50d5b9ab05af9092f3dce80be1ea9a39483e012ffee2bf4736bdb335957ad3c105c78543aa9e061ebbe233a2c56804c7d01d4c1997ddf95133bc15927dcccfae46bfb3e49adc4040b6682549e2ec6b5d050fcb3423031137660e1fa01325374012397170fdf2ab738caa93fbc3b9e983aeb5825532dec5d029fa7fb602638f2079494e726c4b850eb88ab37ce6048559df4d45a91150270ddda3a594bf638cf6e1217ead3a98ee8eb59d51d6705b1b9dde619137baf365c1ed787fa356427b8805d703a74f7f8288e78029be062f478acd14d9e837b3f9fd30aa9dc96d9b613c20c201d2b2686a606677a288360bc13ffadaf28373c9eb07bc48c8cd37045e377ac075b775d14bbdf80619b303e30456b850d9bfe955f4e419a33a834e3297c8111b31de82c1069c3ece4f84a8a7d3aae0be26639722fce7fad83de64d6d771d9a2c23d74ee995d21a3fe1f185b46a41381d623296de59ecfda8bfd42639e38123088c197f1f13c16c32e6be2fd8ef73cc1cc2e1d21178446fe69595f8b10e11953bc44d06cbe5ebf62fbbf4058142c41b4b6d5eb84ae66aee15ebf3e43a1ac9086845fd13dc90fdefe6870b1ff1b0992df7f8bc83eed46b5bd8c7a3f131f20bd061a3e2b0ccbf7f55dbe87389d4f13bb37baa19af0575d77fcb64c4e3ad8005c9e7db499e9f647d58b40a04994bfaadb077dd4ae0027920443a8295ae83ebfa80ef07d196bbd83aeeb752bed0ba4b816a0f8677e6bd5c2b7aedd29c5831efc03227763ea126c3d979bf7c1809247a86daec7dbcbfaefa89121a1a9a2d1788a80c010f305756c071e967ec1d2cf6b688eeab47089eea1c57b174cbd15abc2f502e33519ab2c4af11b7cc83a9e9d370f87f45b4de29dfa65974064bf39961a175998c69453bb7c79e356f45ffc8101a00c4de85bd36563b2296c273011f419f9023b2df9b558aed2dc431d5419961cee3cae5bcbdb6a3d26ed9d1f8325e72f65a3a4c65299dbe1ab204fc75bf55364882740f729f3b69ce4e10c2d28aece090391a6d4753b78bf37c9e11bc137357e7f2b670e3f7df3f7e5972fa69f68aed9585ad3a26ea3b54151aab22b2ca6426d88faa1f71f8936976ccf97649971aa39af5afdcb44947f85ccfab0674ea109d363ee2ba0108b3e37c11e487ca563b49be5984622a2d667a10e12cf9fd5640e466df5ae7ff1921082155065f2761e841fb252ef116f76dfe630e93dfb3f5dca003fe74b71a85cba268c6d0dbdb8807fc43c178f3e1a10e7c26b96a35cbb15c8973157e3e3e6601b18b06ff3c930263d628291c8b8d171e37a48d09ed90f239b2369ee552a0efb5bf6797956e1050bb0974f9ba4d5d0f82b8afadb71b55c77db9bcf9471c1c7dca88e8e75dff4f708110ebdb2a363530ab6899296e90c3b509bc4c3e5e378c9735b555ad1ff2ae4c8e4cdc24d44134cb5c88409f734acf885f6a70f27638d1fb0b564a048cecf7deeceabc17227b334432cd4624e38d7e5a7b50dd2267cf7455d8ac8e9c3ecb387eb773f0074d60a115cc79e63868bdbd5e1ed4ea0e490a2486ed1cae965eab7ae722e21332c2ec74b22758eba3925dcef7a32e4b9d7c83a4e9ee24654be3c44a6b78b7cbc0eecb95800c281c1a77c3e5dee535e8e31b725846fbd59e8a824926f1a1c8e1414703ec1677ad8b9ae9ca7658d62676653aef2727b6f50eba9a15ba9cb6c86dd9c339d72580343f52eb6779b28204aa35cdd884987678b2f22f25315eecbd8381f30fc7207ebef6485eb15a0f69210cabfa59f0e06000a1c45c51fc78d57b1728758620761359a54fa1f7532d64cf927c980e61b6622915207ad6a65452e7ae02fa5b94ef7c9f96deffe1041038f78d70f7159e0d49d08c6069ff46c58af8fdabb2dacc872c1da190f33e5e0c14f7720290f7b37ea658c7f30d95c5553bd9d4c97278e65d9f9ad416ebf67a9ed8a58a3c0c867ffc3ad07f086dcc6a43390caba477ff8201ec31b8e2ea02347c327da9816a9bdad1515f4f87c835722ade83ea953f7e80527949776b417adf93c908b8dacdf868d1c195c7aca0ff7fa73b94a879c0aabfeb9d94929b18f240ba9747aacdf1f19028696167e57daaa8fdf0a879a0fa29704f1b39976046fff62bf12f475a0d20083e39fcb146397b6c195366e4238c2c22a42a6510d65c45fd25df33a7c4b8dfb97ff33858ac9f384652ffe783e913811f637e073e044be3172d56878372d9b95179b42f40ec6aa9e1bc42f7c0a7b73ffb8f0bf9fbcf490d89ba1db79f25043c6355fe10acdacd8889364ef982695ec33a6df33ecc386aba4c7971bc59b09f38894ad1e4d9b39e29b92f5f2b3afbe10454b1be7096698e6912d47c5e86b507832e65f6cb32332802117324e721314a4c7bb9d0525e6a01aecbd8afd34affa8dd1b529ab51bce53100682539a97ab53db9371499a9e6d2f6db27f759f344072ab81ffea1590c03c96d44ab19514a8fa1746755c2ed46b2a3654916e12055fbcf184a103c7498de0a1ed93451e5a4c02fcceced90327910251198e89c596ebf89b5c80e2bbf7a85d8906adf793f47ecfb2377c78367620e30e111768de59ae72bb4971bfae9e5d1a656555f170035c6fc575d37ddcc1130e32fbcdf9fe46b5f895a383a87a5bbb240cc94418bf2b1a6857e4e2b8a7877e2c39fd4259d30b1ffa064369e7b273f219611f7f80b126825937cd437619a77dd44a07dabf2985e5b2d795627a4217b2fdbf97149e77bfc48ffedecbd0e4fcca78eb096cffc3b617883fc19595f99b6ca435bbf212818bdbfa876154e7b3d0b253d0c8da48b273319ca366e057ab5c555575dd7f532c97dc1592a1f96394769f05de968b0d3d3aa43662afe66e477f73dd3a9832d4847b63b0645e5233bc4027daafa3e7ece9e25c967c0d9fb805ef50fe77d174db724cb5346a4562c6ac30addc8eb3133950112820ebdae62deb1748f7769fe072916e3c30de0a7aab0c5fe37c8392d4728d929f51be65041b032b4947677007061269ca342dff61215eb0c3e5c1ddf2c1aaadc4da2e88271d5dcda174b618d6cfb18b452ab630343a0e34157dca92dab7830250c4fa6a9f44c3ee4145a7a114dce44df551b8e470004e09e10ee8238ba7d77425d3976ff52bc2a6262bb461fb0f59222978b16757bf32e57349f807f95fd6ca4303be1d4bd0f0c054e0956658ec9c78a97d55311ebf7becd95d5dace677e7310935e1468d77a1e2d7c07147d9bc438695d4dfbe7a0be57c42ce1906a7079dc60c61e3471bec85cc8bcd7d34a15a3f27182a444be8046bb085ea1dcc834f5b91240be02dcb9155379b0a32bdd67ecae0b905745c2c8cccac4017f1a015f02664a7eae3c1eaa06ca787334218b8f4353f70232d6f991b3a78398f4225ad30ffd7927187458a4adab5e70d5ce4792a2ce1e15fbb2eecc6f081edfd10afd5759104b7620b5773572b6ae541b6f399bf8fc4943472995857a7326d00590d4e9a63236d77925918306362c9fdcd68ad2ceb0a0a7fa210b44d95bd000d7a23f7c534996d9a31bc874856001c0558dc59a32c023cbcb36fb2a7337cf989b2ade740c5fcbd5f6bd1f7f7fe3c6d7681140b513f61ed0f0558e398b58ebdb1039451dd29a9741e558aeb883ed8682b0e94446c54463e2452c5a58c0327f46bc68e92848e34cae10dec783b4e4d02a9730ee00e61ba139ecc74406b4d45abfd7c079a48e4d0f4f0f1792b3739957fb0bff5566ac27d945691cf2f62dab579c0be1fe5b08aa14d13d3d9406388af57ca417911132215c13ce93edc13e9305b67e82e5ce73d0475f3ec28b2f59cfa3554cc05101bebdd215952152dc3a4e40a67c8920967051f2ea38452e78397797b7516507cd1bcc8836d12cd9c5f47e6207c365991c8cb924c66197cecb0fa5db21a2f09d490c046118801a554d5fe3a8cfa299eba2ef328ac40e212986a3f543b2bc48f19f942164351326deb02baea8064af80c9bc4083f997dc5ec8a15d333fb02c852165aceeae9698f9efa3cd514459ed6f2edb9e90175c70ebec17a408a8cf987374e17e26e480071f03195d7626a866d9c8e6332fd18f9ce40cd79f1af5d801a040823ca6d198422d0eb628d8aa6d0d6ba50e385013aaece81af3d7330c3e31863d759ca04925ef60e83031850a54a5f64773bec7f9e3628386a2e9f6ceec672fa936e496403467b33f13660f6ea707377c415afc3b1f6fe37a79338dccf981df009beceda3792687069744a92d7f75d7df41e3cf6c7899eb6540ab7a6dba66c7f309b5d67389f97393e94bd9a1b15b4184a8cd73bdde67b1ceba2530a7c4721f383e7979af77796ecc300556a29008dec777e4ef32cd236342b1c4dac99aab6c3faac30beafc449fdfc3a9b006440477d52fa5e7c3e2612b6f198e360ead5a9e22c4bf9652958808f8f57b7248e4739b67f2fab33138c227b6da47e6f03e9c825a1de102e84992713cd3c7d5c9135a2070a4d5d6947084fda450a28dcc9d0c89e170423b05e62cef7a0a3ea38418ae5f82b4cc8a85fa8de05922aab9be4de958411e52a31ab13db645b27f2d43dcbdce2f10d3ac72544746edc457213801430503aeae4d4f55c1e5a1cb73e599504be2877da11bda92e80228b44e2b707ec12dfee5c2844f21c2d982fa0eafe465f66fa48d41b011032df36e5e655a5c33b5da3f6be277b200f8e50da12f3be7c22d0534a011d2a8a45bede30fda4ba7dd2c7b7cd0d5a03f59ac8404245185a6ce2e9fc95df35e1fd7e556bc68ef5918f179f1e246b5cbe62262c39f8e0fd6a05ffe14b1bbc5e14b1cabd49c4f15272c4299b4ea4cbcb41d751d97b16e9c2ec675bc708c99e4d23c7ce0331fa94e32238a5a28e018902e6d27962e056b03f98a2aa4043a9deebfd6ce907888976bc4fe053ba24d1a0ccada2a5ea9bc1628b7707092be3d89fa82f1335d0a4cf6b73c5b2543ad5e6339d71cae40158dac4317f44a0adaa26637578b1a4cee7ac77abf3a3578fb32addefea53eead760050099a8340a0e8cc50d14baf3d8bf7b7c54933be7bea76bc54587da3067f9cc88696a1bb6f88f886541e6f79243617bc4ab03f34e2b3a04ff790725c8aa5244f715564bfd92340084be6b088db74633841e44256119de00e494f921ed927a1705eb94ed3edc54ca5c7aacd123f5f2492225ed16c3f0dff65b7312170c46cce3fb70d2f3dc72d3292ebea9b6f2619751aaf3a99b50098d1dcec74ec003e44394f3f80cbfedc0a12113592c4972fdf6fdcd466ef923558ad72c3e47c5a10c7bc9de9454542914bb52180aae7a3c8a528f3a668fbc338af4d8acb4505803c59c70e5f3098dedc1db356d5b82bb92794f31162f0c0dfb6ae15b556edec50a255ac15e7fe590f9855e8a0d6009d913361deff27d2a3f680e34d4c77c77cd0e332719c24f481d3d2cd39cec7f8978b3821577d865e4e34043cfbe059b10d626fcab43d06abd9385110fe69c175f10b93d293e2615b585f3eebcf587ed21be90144b31efe00d73f61a72d445df5cef6e23b5244b38209af4e1375e3931e764d494f25e1c5bd6598f94acd25d09035f798ea4a359e3aeb15709d129d78c679c4ec23fa0c755bba3c2da7b144f19d4481645a34b063da958ad920d6e1ce5b1e6a4fb4fb87cfc2b4a79ebc35dd1f2d27a9dc00e6661389a5f2b31d5ed637971b7e7a0ca7e9916285ea9ec402fd3e71f0815e706c1caeba4cffef0bd159a2444532e9cf0af3df068068f7266a9485ea60326eed72c727f891ca1828067e651964fcc865089809269f2e713ef2ec8a8891ae01584ea07d1fe3adfc39ee27accb3d149525745f29a0682f30601b6aff6e1d680cd51e8a304750c890f70c10c7035d310da93a833d91f59a4283e51a1107558de3e83130a3ef1478527aba658bc9f3142aa374350f39096395f65d4117e490f6ee3b96f332b3e45a1babf92b21ba3bf8f6e8709893d0c46284a2523f9a46f5f692a2b30b9f11f22c5d42fcd21a67e0b36f8a6dfda1d80418f0da9bde59eb8c25cca17f3fd7341923309f065ae53bec3852f1c88da201b0a8a83862d5c2549fdb5df8e37084db9674ed356196e91ada5594a4b5c99d607813fddecb24b4cb546fede6ea5d0f682037138e63996f0071d00f73f87ace5b9bb81b5f56717b14985a2842f7cbe3c030a7d9d04f93064b06ab1475dc396b373b7e08967da4c5b394a2ef1d9311d05e99c62d59aaf751a06100ccdd6aebab46832ab428460001ef6404512074b2547cd7e1aff2fd745ec7c8233ab1965c62ee532730d38ee30fcf3ae07b7e1082eff4f49887fa97ca9d49d29cdcd5d85ec6ce69ab4e600a11c7fa43d7ab0bb38dda90f1534d806407a6fa8b9fcacec5f1fe5fd1784524a341d4d3341682f680acc497c6036a497b017a434e125e896859b8b2219f3f7828babe6e778d02b787d5fbeb3a14bcb7ce6127d70dfb9b736f6adab5726249d8e30fd599b6cd30d1d043d588f03ef030ff57f4a6cd6294d7a0a5915a911afcbed6e6ede8fd0c00bc73fb941e7bf3f2069a8f757c79edb52bbcbcfede685ba9b0baa127826fdf213194532cd7c8230acb704ca7491ef399161295589da65e1621e8f52feb08f5b11ae0a376c0f341d24e5a0c2f0eae6c9345869b691fe9a2c1e5e6a367323d4462fe742ecfe990041035a757d101c511e3a90988e9c022dbc158b4cbbb10d155fa50ca4e013ef24f31a88c69e0f5453707515bc6c556f24533025cecd6d4786c70d1ce9b01230357413fe8a20fb800c21b43b4f79855310539343bf896e2f085a96be5151edadd1b8c32242d485a7a57b2b1a85f70e872b7831cb813974d7cf42485698ea80c6a6f6d0c1d176587eae6df30ed6670f91dfc07f8216c3c11fb0c5a4455f545ae4f3dda9697480d803bef9e39952164f55777bfb873761c2c8478e76ea94aa1428c6a9d8204de7f2d34dec91ecb771b63e6ed937387463deccc0b5e1d8fbdf322e7d1e8d60166c08135f3c7f00159374f5a58e558f72a8baeaa33b052fda072ced4d39884d1918d3d804c7fcfeb7f6cafd0ca212f5b6773d5465517da404f3d6fa07d7e47a315ccceb3ee1867ba9ec81ba679f4dde8952b6326e65344003f262f14ddd6a44b8e2372c1bd1404c4ac6eaf728913b8cc69cfd65c8df6c0d3eadff255caf4d6241684adecdc9a20fb641e16848436d1bef379cf1d124b9b0d212a63753d90112967123a7628723f9b980d6e7f86f149fdb5dcdb24f4da85852ae104fa0f3a19e2ac8adeba3fbf4f207af5b0b1a52bbbccb6dceb7dd454e14daecfab3041301560c148aa0b8c6f71628e7d2a04a17673d3ff6dd48feb83a0017d81d21ccaf5a43705ff9fca7c2841691a5ba382ab83db65e7b6b752a1e47d46e5539ed4f74c3e603175ac37f2415939ae0373b51f88f12318f4286f7fa5f0f17443931d5e24b37a07b96f4adc035f66e5b5b3f97aebc8d82c7b91e27f19c41bf7bcb8aa476773cea4ed654171d5d63938679e63c9adbdb9efe4e7a3880f497d9f9177d37c3bb7f5f3cec8d7c89f3a3fee23e7ce09ae8778169dd37fb01c56ef03b125a11fa0ab87361d4cf1ca061723f2f638c4ec8e741bd1c139da9ec42b4b6757847e374a8cfcb44d67799c509caf3c19d32562ee48a2cdf32f6cb69d3d8fd3ac277b2241d8ba1e91aa571fc8319e353018a647e75e0b9152e2710d7f26e2f7170e390e62882c4bd013275af52b78b27e85e9b512d9a08285beaf17b1505dd8e2553a08ea02284b327dab56c5931b0fe9347b4f2fb06165b083c46edb613fbde839bcceb335896094353fec7f3a637fdd037825dc11d6a82192fac0d01d6210e548d06e62cb3984f9d26d451fc88a681eaddda5f3340cfb8bff5ef1fbb4dfc53b342813492ec5ce3976f864da782b6f36766109ae2adbec1cea0fbd7eca2d78d6376a7b9d630f10492f950819ab96f289522079576b76676b6800f25bd96684ae92d53266b85e1909ddb6ffa05ec7b3923c9d2c6a12724a4cfb4f57a7aec56173e0b7181561bfe246faf934115ffaecaba0939f5efe2d36aaebbe772f44f477be729ca06290dcf9cee78e6aa91182c3e35f32ad499a9b56506d65f11373b31dd381b0dd9e25b74d03e5c3b318c5f4c98ce7f5630efb273b0308c328682311bf7520bd200c2be26b27447e14f2446281d48957e87940ae2cab8d16e76e24b8a86e3d87d05806f3ddb6ebd66947465f6a4814376355d3e9b2223a44d21cb25107cd3e18ded4ec4bbed1a87b99c95ad61a088394bc4606d6528f6d87c737fda76660886d14356efab230e532d66a0ed07b09b886fffffab26e70f5e12fd754e305e485cdfe48936578c004f7de6221c10178c9635a1e2fa2fa38b9e6e780b585b4887980d18fe960acffcdc1392ae8c5e58542c724b500903d8560128b35f0ded4402759c4412ab814f40993e8c5625a16aa7bce650af66dc5a7ebbb3277c5abff6eb4e7544fbb3bf851689aa23e1e980ed4ae1cf996b2fc99b91622d5942474e135c0f88aa3b0dde0e439fe839fc5d14a2359cdac72c59d713c37ba22926810b701934c9e0aa74a27631e32834e7b87a2d55fa957e7d4287efcb58ce4b7af92ea3d36fbb5e40d384eedd2374dbaf6fa9db28770d4c3205f177cf82e7e6927609743a859b4275a845f5df71dd3d78630ce89efe40cb3222fc8c2704f77391713e4511e2aee9f7b86f44b5b06425119d9a7de2c65011e1ce7a85d5563c5b7735f50308b4915423eac65dc292812b2eb629d14a1c35aa0e033782259922f58dcf9c2d153ef5dab7f975b6f8b07d566340a0e9704e9d59c04b66a9f86ff03d24136610ab14add8a8fa4536798385b61cb6ba090326a4d31cce70be1b4fc6137f50bd8ce653b93f7e75a0b0dd494be64455f56177029a94d2a2ff53334da4c5c4887b351c1eb274d2e1b62b629544c827c1c71e07de5c74429d3d7d0be4e8099f74ddbaae98922b928421fa1581a95ce595f8ee09371aece5f34c0229d3940144f9e8d8ff9dff3e1d9db667d9907cd9c384c325765c9300e0629996f7c7fcd20e23462a243bead344419cf920236bf9c3cceb8b19716908f340cb97ecfd10f0f7223053a16c26cc7a497a0487ebf89c447f782ee40cbbe15dab122dc5ea0241d3ec28219d5d187f9d50d9dccdb69e5579ef576a44fe2baab2576b9c4d4a51abe13a8f818ecfe4d21e51cda8fc42850f10d741bdb2a55700407986e0d1b31f24e2d6cbed942405faf874ba4515bf62d90c79764dbeff4e52c53bf70b4bc6ce47b189629067b2c4a7b37b837c08009a215a5b078a0ca76b4ca6a2cdbeb27050dddb50f55a6c018c4c1d8eb848b91da943f7f5c311eef87a16c8a8601e5862b466d967750599eca46fa041ef4159c2ad52572be6b1ba5cee1a8a89c4618ecb66ffb9d5d7404833efe04d57c8604c9dd01c8574e2a9f52fa6be98c01f96752973e691ce8b5a371617e1c0a8cac12a0076666f477885ec0ac069d465789a41f60fb2109916c05f77ada19f93e8c059078099f7de28eefaa9f3d4e3e340e86b685ca838e2231e61c2227ec20fec313be1d0f7b188fb1ea62ae90dce2e6f12bd0a207fa53a2c2f8c99963fd8abaac14be16f29b88425703d46f8c159771fd9c008c023ede0a52e1629e47f430b4791c15597027cacceecd30a2a4bea43613ddc5d9a9394ba456700c8498fd055a68e42a82ecccd070d3db45fae6849023a6ea2bf11c76be264fa69aa23bac60c14b3674a1906c566c6fe00fde22563a4ee25ceb85bb639a4eef5d29acc8e3b1793fe15c0fbe9e04e171e46b6506b44394c23fe05f82acfccc502357ea4a7ecef31456db8ef4f95b0a4061710becf5206161958305b98ccd18ef49a66b49d27f048ed3d6c031da17fe81ce5b2298a5faddc6276d4e8f4800227821f42f94dcb6ce0f3b239083a2ed3f276bc9fe0889b4ba71087662ce4fdecd454809e45193bef2496167e03b2352a583c1380fa234c3de63e58f6b51bf1d073c5a6d712ed2a75889fce5eca1fe47cf608abfea4ea9bba12a1735b9c31713970a989a4fe5f66c938c579787e2a5960511b7c60df99620c8fbb9aac1696134838e3541d0763226c7ff298a1a7d42ed0bbac42ad084fa986a9538ebfad5b3c5f233898af529375ebe99365f7d2f9a927bf7654568e54a93f18d086d4816d117368fa98eda1a8a9fd513d5057dc4a3fe3fe862b5268d588eebf1276c0144c1b9866eecc6c62503fe3a25b75eefc380dd4a2ec01744151fd4b5d3c476ccf3d72b2ff9630b3bea95ce65496e60c4a8d45e252d9ab979828c384a9c6e175792e169a85c56839a0b7e08d0820026c9941388bdb0f7b78474314a51b8c998bfde659ce312b6009a7a0d67f3a844b7097c8af0d4eb5dc58c8d37ccee3c850eefa27aaeab9cad31fa900617ad25ec5505387d729dc447753c361005fe2818f32f85241563f1236f00bf4b13a32f6daf7be4a81efadccc5dd7b9ed2dc285f9ae62549afe1fadb3b90d1f0edc093be78096a5f7fc9c9c4df89a23eb8dbe6901769d1c31ebf54c0bb1fa12c4bcd6d583b1fbb7ad7cff7ec60e88fb209566ed6ff964f5d0f6374928df73aacf4c96e62470a3bcc3cc76f2ef9d5aa46089b1bfd4a4f59fee4e807231b4044eb54fd3eb3599df4263ed472f60610ffa4ea3b7350ca933bdfe6e924a97a91efc3802d929b165d56c30b21f68233c9b39ddedb477614438ebf4ec4869f801ab0ea9a3a3b1354a16af0af2f844634fa2d5d64c9ff8a879afb88fd84db1562d6641c552d4877b3a88ac0b2b319b40811686b0b01a296c7ed700b1ae9641e3dac7decaeac16a9a34c0c8756ee97c34e4ed807f68ffbbe9759d935a6dfa39893854762cf2d9102dba8c0634fec7b16aed2ca3f755879b6e21efba57525789d9e2b1ccee1e99c5172fe90bbc3b4420196650b0743934db8c5c32d6632aaeff36a041be6f68c63e3260f9430627cf28f9ca8f8ae2bb671a6d8dff2ee25ee9bb8f9500537b81b3f5ec435dd9fcf0ae710d7a5625f436837f4b0b5e4f42119d7abc60f35b658fe41dd9dc9e03e74ccf19550f3a2dd15f5fc2ca4b790f341f230fff4721412f6fdeaa7542fce19cfc8b74c79d5520a4e5f0579eb5108e4f0b3a948684f59df3ef4ecc48b1a57547e82c4b589046f040585d44c3183ea955e4ff78c2c560199af38e0fad48e5a022448a0c99231f26371e30d1ad3b8e295234f927e2324b02b1540530e5c9891d5187cff0ca547b60f9cf83e22ef84c6d41a6d46c13f9818cda4b0b8f0baef6521e4c90bed246d8499f3a5f1f66d736a835d34155dffbfe09d2d9631d8f0beee75ca81f58ed7a40b867a2845449cf123169b2e00b9e6447760e28fb8398f28d8e32a316b0a92a7ccce46fdb1feb2719a6b311840b04cf44771eba79b5d4301a960772292de0d330100ec1e6376e2f9cf93e4ab3fdd15ff54c47ad93d46ea468862d5fe5492cffaadf90835f4e877d31d98f1763f2e7f59c074273b8c02b87fd3febbee7abb36bf0dc7d6d348256967463b42dd35fa5d7c683df1dce375c870893d02a8ae7d51359cfbe1af2b231bda92dfc6b437a429835ea3e62b492f6e9442cfb77a2095eed269e22419578dd73c60b680cd881f00b51d00b9e4086f75fbf7a7391d48480f4a51af35b9b92e235ff912c877b41f53f441dacc183cf33df104fb49713ced2f1adfbb166ee02d3dc741eadba688eac6401a5c28d89b404089a1713bf042aca31bd6e5b041a6d4c409e1bb5701fc3e8c3ce8ab8e0ca30962f66f778fdadc55faab11173af22b51b1c657fd9080a2be3760687720bc43fde36e115b2f35276a71228c5d4c59e45155db2943f49c8b92db8ab06faef03e44f511dddc0ea705bdf32b1cda7cd8680fcc7f0a493239df3b9533d76b42eacaf9f394fac7e41b0ca4b76e3df3e17ccd57bd576b9de1857002d398d078012b933bd9d3977b8ac24e9b65e5ffdf6a50c3b2a6e0c17ad0f219b16e2798865d1d5f613cbe14d4de7a49cc96f564f9009bef81e1194de71d38c5bdae1553b2be3f98a7e9f8e796e878d6eea45b44b9c90ead43ed65ff3f321bc8b99841b41fe807b630b874edd189f9aec22d8f06c39f9de830704e1f58766c6765cf30a27c149cac2d6ff9b18e27affa49a3a9cd1650ea6be3d6aebeaf9918954f24cc93b9ec13d4d386a6959bb3fee9dbf98112c0e46bdbb4dbb894b5212fe6ce5df5ced77da941d3ccea1f1fdc286435b95c9ef05953371ea90500db59c78c2e87c8611708fb848204071ff941f9468dc6290beb69d14e748c08268945fc5f1ca6fe9cfb96ef4f874a3a2c4c308ab210361a5df4c0790b370da7eb7672bd83e4487d30fd558e55d026f5d1616a7590efbbdba13b1b1a0fde6f13d84841f085da1175b77972456b9d74eecaf3d60825147a5a3cc4d79588d17f39086125f0c12c67e259eefb6fbe1c75fdc9281017642154ff4e4696ac5204374af70538dd9aafae31929b877906b93fbbdc9b33f6dbadbc1f92219734b2fcb6767d96ce9653dcd19ea4139fb6acf012d418e1ef3b765327b780589028264bb2a665b8138175b968c42eddb62cccc53143854aebf0677ce59a3d9dbfab6b91df2add9263c283973e1287fa829a9c9fe7679b140ea1b4da47008166a5d2d04672a417a3bed24c23e066b27691ce6daabc1b45abab9315e20c8a440a8b228baae1401ca6446f3bdb4d8d6659d9ebc8c4cee35df36c1a54cc8da813cd0780100d9a3c680116096674f8e47b60b51a2608ecb195578a6cf2c2b4aafcdf74d745d30d732db71e77d2852b55935450848b7d81cdee63e2a7eb8a07c35ccfdf74b389bcdddee90bc5edfb17a7f403ef5b48dc5f964f494718ac1dc92750ff9874005f5c183a2ebbd025c643df177621593fe31323ea71816cb02bdd736b0f14c88bc29438274dd3c344f46a7dae1f09b49ed542fb8dff1ef041f28f70e1d97c6a2ba215a5cd72d7f8afae2cd864f91f3bdb6232a72f8afd9fc01b61ca2b142657979e26a0df268e002b1043e92a4237044474fb28040495bd08f364b80e718133e0f856a3746c148961e842bf3df69c145d4f725162a88b3e3de9f94c59ff1ac39db3ec563d4f6acfc92e195485a828404d2d069f75ef72ace04785d3216f77ec0b22193b753bfef3b68c289e80f58689fd3bd9b7e799092b4e6d515ef53fdf59d89f430970ea7713769867430371a4be8ec0e011fda63b58bd56438c29ba013d97e39c312ad5c0f518a8fd527c14d86f1090ac6f02584ec115beeb78001c71d7b71338c82374f097f235708ccf332a9927fb1ace2f154d5e0fa91b2d10b58faeacd6cddcd92d72123f16dd4b62f7fe7573826dc633805d5cff83ac84c13830ff65505a1e08c9a49708c63c1cb47ea7c6ef754f5036d75a3cc379a59fac337b49dc1cb4ba44ee08c8440f4b21548b0f8bda74cb89c3e7d1f3c5490868e5aeca10351482e3fada88f83c6d7fc640283a061f802cd2feb81b60deb462bfa750926efd29e4e75e18aa847faefde877c17e7a10b7272141fe23d0c777e3cb071f34a3feb176f1b060b55710c532309be29c2d8f77640047827a4a404b103546c9ef595f7e55d95e01022bf3980c75356b9f5575d5e479bbdd5d85b30b87e54a0941c595b381f53443375f3ed5a28de9d664fbe6e57eeca3d3329e0f132b4eb0edaf9dd9e4268cef49def39291e193cd0490e18db1a984df4fb270ae0e72071ac38f98d768c0b1952e00c85df2b0fa8360c0fec12dd6f90bd63c7d1eb85c0da5504f30fb163c4facc4b18fe570050ac608152e4a22ad80c11fe777bc25ee7c1b7387452b3e62aaacfee46404026fe9f3d22cb173980c768ea8a23592a80bb8ff164e0533ea0a87043218e683322da3483c4df93951a6c13bcb384fa6da2edb14ddefffeb5101c949a0fb8df3c8298bb51716d9a85bdd5f693b967f2a9a73481471ffcf577590bde9666169d1a5301f28366d9a76a794a9c55fb21cb5f83d1e3529f3a24a616164d4370a71d7a322ce166ee2916418f70bb5c00cabf79b525b56b59e5243f6110ff8761ea21a51a087748e3210b59dd29b1d2a69ff94ac953fe22600251d49180f6e61f31b5666bdb32b37e6e529d143af483fc63ad7dc7a95c5109705052c5691461233249988e51508622ee23e835a5a3c1e9dcf1082c27e6ff7fec7d6636ab856e5e7162c89d052a2d301e9ca5199a0cba2cdfa3010819dba0f0f7c893cf7e29dbe587f6df46f31488191534420414f83deef070b884fccd67697d3cb5a8ecc22d01b114224ce6e0c86e6f934379eb035e25857be11cca77e9b4b9464092a0b569746e97030efc5133f2e1b9e83ba17ad3ac3bef7c3437349019e5b905be5e4c2a05921d13dab276a80aa1dd59c7bf7954db84ada31d849b845a4b0100c4d77857942aa7813405f61d1c9f6cf016208064cf3a4bedc4485e2d5687812dfa3c5d2eb1b0cdb91fff53d63e8acb3365f1bdf5fcabb110e1c0114814aea7edd66c6e855f1d0c651626350d9838671e348698d8ad8da0390db6bcd53bc8277b1da339e69d722a00356c6c770b36afeea399c6830300b799608ab34d891f172f4958ed26bcc7f36cdcbb9a37af5b3850dbb85bb05c3587c402acd25ee6d1e7d14796ea46b92eed6fc506ad1f343c4a92750c0125be76f6f73c0c915c7c9b861d15cbaaf9b9de4c4c6c9203efb5775d7fdef7ec081c6879f880ec4535c56cb47ce1f75531dfe5333fb7d021c2aa28158824650e2d2aa74cbf00053f30c30f2f49ddcf9e70fb28e8c3bc4086958b108d0f3f4a1e2992fe5945545fef0a8a715cc2e68bab14685c96d73864c6b7311a402aea36b0a988581d4323842759b87c5ee2036aad7d80a04abbb418a618daf6bfa55c37254ec26beaf2fb0c80f1d7ba9262a64cb37783ed8144ee58369b8f283f746d682dfa5d83d768263f6b618d2647a324ab5a696108e8782a976e09da11e39eddc35517aeb670a45c3742c32ef593ec53454c4f0b4564a43ba45884b3dec1a41922a295a72d17352629c34a893ff1e6a9b40ef8bd0e31bdfe858e9307e59ac73ab8a246c66c1fc71a67fdb5cd430b1703d0e279aacc14b2fc859ff4fdf1526fa507e1c5c8819b185a9a49d7fcfbd7b847dd69a2b74fa8fe7df47b9f3ccdfd6d48b3919e2970e11480c19e207086467ec05087ba10b50ce896220616e17d7b9490e9c882b21623020c9b09f43a229fba2050e69d17548ec51fac67dd06e1c9120e4a5e69d5b57d914ceb8ea3ea585b5ec020940460bb0acfe00aece36bbb35dd9e998218853080a5859608e994b31e3e9a7d5d1115575c3a2c2574d2e703f8b7b95edec328c5868fafda2c68f87d4af4845b0ff8a08235d563dc8ba8ca5d4890ceee06431e4e52bf4521e694ed73af8327877033bc83a8d63ede62f54cd47e2e9357ce2567557e758a718fa3c93bd74f37a25ee0b3ac77aa57fc872b001ab8a9142292df4cbcc8f202976f373e6f767c0c50a8dbc945d7a4061c0f98bf6a3d6fc066c9b91964e5d87a6e3267021b0a22d010a89260948bf63fe35bef0f5f123e14a249b3677c2b73f97562b4da86c090246870ebcc62ed24c1472876153118c3dce69a626c9c7a70d9803369a101c5e872371a79cb0dd2faaa996d0353c540f44f3e693a0e0d3003707d39566e611d5c3e68f0eadc84f2e126a33edcf98f8b3c906a62f6258a5df3d9d489dbb47ee89b9f4be6fc3a9ad118dad699660254d26f9a4399b5b2d952aa9f97b4d127849363d8cd98d26b8cab8901c508aac1103b74d5753aeb389156a5c7ce742e7cc2608c4a45cca218c1f27dcb67501f1ff2d2f3872bd6428cda3222d4c339e368032a8325c7c2de233db69520da3006085f3ffd070e6de1d8a1b7590e26a42bb334f5c4d7da478dc9ed30a800f4791f90e7ab9bec358fa9188b018710928bf36885970d6f0c037fdee59cfd8e98919469a956ffe4339ae81294b5d7d1619de7dbc4a6d27f46fdbd4cc17b1f79a35fc5137b875fd30d7373ac4405dc13770f116a6c2cfdc94eafea0049a0d976977079d4b979123b803108f2f3d5227700176491aa88e933a65eaae9cfa8b967d8465a47e6f0f0de0368869e314ec7cd0cf5d39e46e195703ac8bf33f94e6d3edf73f053281bcd8101164de567396035ad682967fde8e46be43ce1311b1e83d7e367f41d9bbf52794f9226ceefd6d6f5b32008491c2b1a1ff3aabef51e4cb33deaf1cb744ef5a537ebab8a40ef746ac3f1f3cac815dd52c998c7b7ae7303cbd681597b1d4c9494be9fb6b7b9c216aa4d663c90ce5ee2fb76a3fe9e35e68afb67e3fe20e92ae0bbb6a5198da3f81efd85530a34487731772788b22179d0c50445e812c38bdf74f71b9f98f5ebe2cfcd27ff0d59c0a3acde67b279c2fd19453feaba9c108ffbc1d062c309214ee7ad287b62989ff57774ddaa0bc1d1d607d574ac5912c392a25136a78a88928c2cdbdf37fdc97057745b612753183dcb488e6a26206fe963bad4b59e68daeaecc452e36e80c7b2a967740d1dca683d767924523d0ffd94fc033fb66efb4a89ca983bd72c9c70db6c7a5d4e4561ce9167dcd15648c212ef909d1f6d698d5a2d97b6b2c0d71a276564e3bf083309cde47380cf162a932d8a7b40b79ec1044f454fe3fa947e7cd9aa08114bda06c75683fadcfe11ca2f43f585f8ea2979f8f4695cfc2370035116f33e6ec0e7db99dc477ff87a98bb50e572b3c881a91954bb3b33d1ebe86935067cfb9b58d44db709a63ab641ab64c1ee8a022d3b2673605571bce77449d121a0c22de4ae0078499af6780affa6d760f360f2908bcc4b6b11d9aca0321eded2ced1b51f5a906354eb8c7302f47f2b9bb959c3598f92104fde6a0d42da499be991c67520643c1666e7b448620bf69a8345bf7ecccafa8c8f294fe933ce37b64efa70879058e4921528a9b024e65dbc7d510dd225591ed9f7ad29bab0f304c8b8d613e7d7965e619bbcbe31ab6f3eca3a1ba32e98892b79ad993278844a4988162e871367ea535aebeffecbd92c4490469f019908a47a54c7aeb9d9c4e8ad666f2ffdc6f5426f88b84a9d6cd5b975d901a4d9dff0f304fa074c778c2462d5ad62464809b5cb48e61c3f0449cee40abb94455a11f37021956f9699b7eedd7a5d6e505f7a3ac9fccd8b90330c70eaf2a3595ed6f3442d5f09ae21745233db58ea1602e93d2dd099ff140b23b11d00a557642047366d394e922773aaa0ffaf88c7483ca248830ad45e439a2d4d200ad9b11e3f0262fe79b0b05325b310c64a02c78b2df16b5a49b2b02b78444cc823b3ff2473f90371e266f8d86b629aca5eba5e8e8d44fd76492413f14b8052c9d27dea05e234154bdd85b50875cbccc74cf543275cdaa8d9720f483cc7b646b017fce1ca1c0e403a8e0e7dcb6676528d5004f746cc32652a5dc9550f40d6b1d149591ccffb7e88cd02a1f1433fa02d5ed182aea5153c48415f1c5f40402d78d975642dd2abeea898ef5936fa14ab128e82d60ec7dcecb3b6a7e28ee25980234794dbf1193e5fdd5a2d27ba56265c495abcd8de0f2adbf2639c33c3e7514322debba074eb26b52be3a31679cd44ce262acffdd461692507d1866e153445724dd8d3d869f3b6a6f9341c53514b24c7409a6e03cc3391bc5612d05a687845d8e3d69958f2960e655200ec9ace2d2469e1d3a9a0d1e2aa4b8642b7dd7c15a4392c260929887c5ba20c94896ed9aa8237725cccff205b23f8a10b21a57255a4ed93f990b74f1d03cef0b458a32b83cfa4c17c46bcf4590c58dc6ec34ab02a08e56c7d0867b3a134a96e3f890e040368d958064ba96dbc1395f1d436061b9710d396b196f4ec0f856434ad8e86f981c0fa062114d8b0ea9dabc53848d16e1445efa799d685b3a1d25b1acc4fe2039bb2ed8e43380d71dcd81e9d4d9926a7a2db059729e2e362e76d19e9a9be7688e30a13d1b8704faee7599c6cd58604436c7f8cbaf6464f4009ff9cfd4e3cbb21369e67d246dfe6b4619f947478af13bf5604780b22addf88854b617b8563fb11e54ab48c9d6443cdfd4df5a8b633ae34bf892f374dbfbb33b332c51a56294d762dadb3b7545e67b72f808d269ed2282e37584dc90f944da952e8b9bf9e100302765fd6b111b1c1a2f28e1478b3f9cfc042224faf0a286d076206feeb230f408d3ff2231f23265468a778887a0ee838caa495621178030e8e9716a87f8dd40a802ff3da583d6156dea2e811d2e476b004ae3895239d786e2b2c3a9e239113024095841d42560c2b92aba332bea609f781ef107d75614d8d8ebbc8b5f939dad45b7709d3a9520dbb75c44888f3f17344e8fc1706cb65b91401c521ec4f827e1b3fb0bd12416d56d933a934b1a4fde37085b9940a8e52a416e910cfd787380932d689776da97467b3a145359cb4e4cc826b7332b3854f159d21a93699cd25408a62d3d505623265dca8a43b33f3cf29b3af1caad57882a58a9ae2c375aefecf629a24860cbcfab6bdedb6e93c54403fdf4c50fd4865f4f72b6b9567b8ca7525ff11235f6b2c7afd2cf96ce28a0561927e5cf02299774a3d3c7f302f37d5151d3facd0e7b3c9b6e814cb221ecd1ab3128efaf89fd24d36e58248ecf24ffaa0b251fc6c6eba26b2c7be861b53b308884f510f355d16c616263c295473f4143262767614c1b901ef39af95948800ab800e2b3f4224b86be54ffa53c19fb9bd29c0300e4cd33c587e8592a3f858f68bccf823b67e601d34fd277e4d46d2c2502217abaf934aab1e89f2b9c621cc251a01a5918e600e27c522ac1856e6b6b5fb17a7556bce0d78f3c06b8633fddb06c6ec8be2ba92c042c88666e07366856cc1cad2e2ad2d36736b924d4321563fac05cc648b5af8e299a60320bd075d97c7ff5d9eeb96e3bf0dcb2d945df7c9999ed34d62e4e3eb4819e07b7709fa2053a1da8a8a79925eca825e10b381177f8bb3d1bb9fd3e7f0e1d2bb5e09249deb5a747d67d1e4a519b3df7c06323a3932e19ae47d4f7de8c88b9c8ade717fee7b96089ad95ca13eefefd2be45c06fc60df20f7aa892fffafe1162278c35a17204d44174bdf8e964db8c076dad3af9cc756d184ac6296ac532749dcda24db49dc9b7a3e421f1d960fe641445bfcae1846a442f259925ed2db2916699df42c5cbd6433b20195cebfc2908357a80b5bef34be15601c8cd7a1df7fd3118756ef506773b3d9d9db97f4572659aefc988212e13b4973fc8dcdf24a731d5306f7d1a7fa86cff43b5b2a1257a21ce34385324258ef3a414e5c2aa7ec088c8e3daa597069399d047f13cc0069d473939cee738bca3021bcf7e76273635041acfcc55e285142eb2d6e135a0cbe623bce2e9073979a7359716257ad35450855e00fef7a1467701dff54886a9d2d0089a831f2f75591f6b2f7109f2f44940bf9d0320818001d6e36c2b9c9f2d91fa0d8c26c5661a7a240a252c097a12d0182cdc7c44e729f2d78876f590aaa7589c498bb47295a2e93079939615a5e662c15f37cef8733e9ddff3308e38129f7455e52c86ac05027a7eba9e30340a4a29508f0e0e3cc43cd2db2f95b2d03ae815654c5968bcfe3ea99a9b93eab09da0e75ad373fa5b0f69ea0a9b1d94f2b1d1920ba8e871e70dda2e32dee8dc59cc228b14f735d8a79cda186ff9d07d16b23f218b81991b06a9f2c956a3d852e6d3943a1fb166837c68377574125c3797b07666abb8158c232a07294f7e275ac734b914498f9d80b979763f71e68f88cbfa046a47beaeba08db6d944337ac6e7795e750c465a4e1940889acde961c71252afdca8e68cd3fe1be5a8839f9efc741df8892185c3e4a45b78b542a5eebeb9ab62afd4d2285d6d4f4f2abed48a10cbf390e0665275d0e83f5405f4629d617357c157534c738c38a87766a3def25e02d6a8ea4c9fc8dde8ede11cc1f305da2e0ba6f612ae273f16a91ea5e2414ecba6fd357eec0da38fa1e8735008a82ab86e3b4bc2660eb4e739957eb2017f4b233ba095cd9e72a3ba9b3c85593de1e8e4581b352003874c5bcf6f1411f79df0ca3800932618e6dc3e905b644df52a9a99f783c25a0574b1b563f285561b4242e6f701d8724ded9fba3f2e06c1df5926bdb391a55d3408fac33a39c174cee723d142949c76445c8619bed261a7d4840d0e7b768e5b656a7ddd37ce443bde17345b9af0a5e79a1f65837ce9e65d77809dc65ee536c61e89f1021ae6a651676b140249a7d44e5bbe37ae9aa211247f0c9d3122ff0c65d882c6f4957f10411c290a7b7fcf436ff148892164e69e111063e6ff95796f2a08acc7d53036b54315933bca6eafbab1b03fdcadf171ebf2d55ab753c1fb63b2d1c34a18c3e37b4c9ce11fef9f4f1f75b20e28e2a0966596d3eb5f00b939a3860175bad147d6916a9cd4b8b098795a16b5c9482525d34d0b1f3f01437cd3d28faf54abbe9a558b89288488bf17f6dbe1d33be41b4a707a1892070f76d46c4b87187380fac651162db061544c7087de131ac32801c1c52c2c251555bb6aa539a1985ca076dbad9d33306b9067c1b7d26fb06c4d2da4f69a745d9dc1b20d2b0b17be50f83a57f6edec5aa54dce0cd80330bb5a3c9121bbdb67de9e92bb8eac50f26febcdfeff70a1aa217b35373ae64094a10cebbe75e84117ad09cb697253eb06017d7180ba2806ca4a30b7c21beb66106c87f0fc39c1ebf4a09e6ef3800a5df126a207046bfecb88b5887a7f13395532968b2557636e801d903ba393655086cc4f7a93b5d77587495123acc97e994a2d896bf3db870b67f6f95a321e7f2edb7894663df72ad3737e845bc760590e25bf62df105a1b5cd1ee8c13c0b1e2854fa71fc239d6854a79be8ab52af976e896efa8345c1605bdcae67c5daab5a48499ab5e9587b03843e212efdc7bc271499345b5e457026c71d1519c0ed6ebbe885c066e986c7f2a3f22ac6c40e9db6abe2911e24dbaa49f697e889466753e995ae02bf99136865785d8bb3b535fef0c1792f646b067ae037f767c5ad318b7cd5d8dbbd02bb8374e056408582a852ec447fccd3cd61eae73fce4e4ce9236281d2ff88025fca0be7a7865b6bf6b937cff35d694cc36797532a7e95d76c7e4c9a8c4d13edc33b37537d3201957f3a4fdc9a561f0fa8d2284db8edb999c57126c3ed6e88f262cc3a87ccdece74eb2dede9fd747ab832c67b6e4220100b9a13894349fabac51068de39d7d188e9629190c9e40b1fd669dc99c46f6079d3ab2dc6fad2a8b699a11e15cee9feff355c04ac64de9723bd74b639686784eafd6a94807456ca3a4524fddf3d91eef4a0779574b9bdc7b2bdc23d31b2102361789d92a3a2321d1207b7d45b1de7de15393bb7828a4b3e49493ff3956bf45d14179a54afb5901ab8c1b3e8c9dd19c520009b21e73366cdc91ebae165e4cd2dab0809fde330e1d87e638e52d0fbd0bb9c0a1aa1f4f0b22af91d4d1bdda3724d4b86870fa4d8b55c3644fa90a77920f07f43f6fc1f6185f18a9557af9e1f2d8a64d9bed2998462961e7dcb9367ced93626044c66c0747a749d5f581a6fb31ae51165c0b1f7c9e02c0178c78dec189a4be47b0c823693f3855ce393f27a969af31f55b7469b18d371bee0f7e36bbeb74d895792944f782019e115a4a38ff919a639b626815bf44cbc2b59acdac6ab21d7d3ce9ce257da87e8ac8e6900c9e7a653a84f1020ae40878b730d016bc7a25269fb6c5cdefc6b45c8d7a1591372328958fa2e4ce13f328fa1bb9b8aad2cdc54ccb987b9238a5ed6b3d4b932967908b7fb50f7f9c5062e4c5f11608c828223cfd384187b4eec8c1dfde23038700ba289f55e9a1468c45dacc2681d60f0379bfd4a14a6fae585b8843de0aa1af90b8998a54bcc99cd23b048feef8bb39ff09783900ab9405e249a8fff72bb8ddcf05159c34162c7a47c71ba9f97724e254573c13947388cd4f5a37e9e5d824f23855b580578536d3d21262803a4c4d7aee3f91c733f79589235fe6811c92548a60b7a5081fa7912906caa0dd2c4bd17f07cb432a882a2cb332fd5038c4d4a70a7b9b59fd6187c31ff550b727e40e6820b148d5fa2693ff7e335b1cc4e31a0b6f1d45214f3c51f150551a75682634f51bdd45502d76ffacbbf0bde0755e487f57ea8d276eb7071fe1a384e9faa3971b7417bf049b537898ab84b21929f0f5ec091af7aa282de534d37a964ab136a917b11bf25fd065a9ba23ec260205ef79aba9ff331f174a3fea36e72cc92af9a8ada21ea5727374b3bf2cdf6d46addcefc3ad3a0a975892c25047e21ec9adfa862545c9eae0cb477c8795aa154b67a8937dd5cf1c8404b2ddedd62f4dd3eaa4a7772906ff3d9533a974babd95ff0b6587705f68e07575d975218780ceb401b28cd28928f645c78fc1297ee91d7a8637d4aea0a0018e4d1f54a8a5e65bb372db2a2a7f0d2273ca2ab91d296eab96d65712cd384ee8316f6f32acd71735bd6921a5f283526a9cb6defcc2aa6ce8719fc0a95e5053b30383521eb0439b9251dff1247ea5b4939bd94842508e3a85f46f7c03ab8857ff5bc188120907cbfb1fd4b1d74260393c59c6e332163e66c1ef1e174fe7fb1fde9ab3d294a70cbcd51c36b40d709040af2c17109af713fac3e03aff0e1b817c5edbd76183fcac871ce511f38b0f74a135693c3eccfdd696913502f6cda61d0dfb2a60118c2bd93a2641a317c972bcbe3d944a19355244492bdd0aa4dc6810a07ebb0abe36e1d2c6c750d2dbb3c117d7fd7220deeafff735f9039e0af4e416cdbcf5b321ef1df9eb0abbed0f6eb39dfda721583aa86fccb290b4588fde80c6f576ab062158529eb733ef6475e41f0d6ad99239c9dc0aec5e3d2e89b138b837afc25c522023eb69a7fe8fe28070e331d98cfa7978fb6b239cd9424e016e4f779eae4e3801360c42ff3304ee2ed1d510d73c6708fce6bc5c3debee7c96b57c6986bc94e249ed2ed0ca388b838608c03dd9e74c3abf908e3e8f37a49cb7b3b8f80c273651062dd074acc131fce11c54ce27345cb9394a8b5251c7dd73ed30d4f4bf916cd52ab305767bba46ac767f94709e0f23d86f92ec2648d54e00396382058b56b8462f7b5c9f9d460677ee78a85dfc480ec2ca426239f643a4e59537a93887ebae86dde69ca3e3b2bc7a1c51559d26081aa56d6869148f3d4f04597f579feda9d489308d12aa9af088d6ef25f3ff6920962aa1098104790e12cead79b104816df29d583252e4715f0be5d8fd8ccc472af599af30b9c0887cd3f89f579538d82793fca3469ed656ae44e9ecab6405a62f54fc757c1c5043af6ffc52aeb883f25950777f1e01cfa8050b1ee921ca6a214b3977d5e01f1ec1549a4a9eea0da59af522d6cfe5a02ec42fb552e5a860fd14551ab5dc9a8f30d2afc47ab367791db9b52281bdd1542d96492475dca6933edbacc91116996cf69c0044780649da016414b558ea63fd634315ae86e65a5b795736c778e860e7b6175955d74376c3bcebc6e693506067534b046f325979679ec6851e64a93fa7559b0e06eb7cf3c74d1a3dfe36303b697e2195d56cc28c3b5e3271810ee696a86c7d5ef6c1e16853b6abd98b46db870e0259af19bb0ad9df15fc51c5bcf4b49e42c4f6f9e92f84f3adf8e0e74d6f49a7dac19c9bad9e7e3b3da951ef234531268dcac8064eaf9da9dc15358ead351a27ec3e44d2579c59f8df7108d27bf965573d57c02c53899ef63d770561613dda0c4cf28f3312688d68a918663cf060566f8c090be7692db888db47c6df5ace186ee71fa9ad74cd44f88bd70cefc06ba4e5c8c541672cd7a56f5770bd9176e3be54ba5c34791fd2665e0d1d394e8bb9347d817e5d80804bf776068bedff87340936d977b35f1f0de16946001378df56a501438e52cb1c790f48b84cf7a5d82acc5f9bbb05554829b5281f299e357be425f92fc7f1097be17703b75798c954368ce36b5b0df7fb1d4ab08a36a2bd85646172a7dc54277cf1442266e11b533a30b4d572c385b6f1ae5fbc736f14839c09d89e293f08e96a5eae290c05297d606b61822623f7317132ed48e08707f2dda299e0d2ec78bb0048d6048339a0102baf352b114ef9478df83a3bf9d9a5336e3d781f75f73ed917ded234c42b78bcc8d53b9b18a938e61467512d90ab655ea19e7c2119de7ae6dcb9af6e1d5ce0c4d84c1edc854ed9344fc6edcc415b14a3813a3495b882d2d9f85f5f42f41706dbd42decbb2e0f2ccfbb48f5ec70ad2153b306f96341a5a4fbe9cd9213de32b6006717a6918ca8e05f255ef84f7514e988deb320c1a9d5e6b7f5ec0b192ba1037e166be8e483dbe7ee115579c0ee74682b696ffe1ce8db067e30135910f4ebb1365d6fa79b22165ba92b01de2b5c535ea1e398d558756480c7186840a6f681a29349a1043746d38d33c72da262d32787cd39ec48398ca8534c736d6760b84fa75ec8e588e8632ec5ac9bab796593494a2857dc2cf1fc0b1b0f9454d101829fd54f64ba605b2c9aaa1df617fb6da86abf4ea809a534e1a652ff9e506c91ee9b426b24abcb56e0dbe25f5c491391fb6f741345960092e1817579b9fa1ed11b6467dadfcadec6bf7e59eab906ee2ff07fa32f7563923f142c451482894168c834de04b72e2a279c5d3edbde7775b40751aaf08e45f6dae1fb37bceccdd5007ad942bc81f645f2582dac0479ae638342c3ba39521d68d62b8970c6dc8b8076eb0399cb38efd3f135943169bfc1ecdc21b11fbd532d5bf79a86f511634b1e884f99c9e3f83b5b31121014ee0d369639751bf1698a20aef147b57178231d340564dc267f6c26db09b8e0c0628c0db4ba5faa5f699f9b3f8530abd5577339f33bfd1daaca763323cf3b601e75efa56a31f5fce90047fc03e4696b1bc33643f5330affd96ffae5768d30bd6f26b0da9c998c74b98a0ebefd31f4895be68ad1c56ef149deee2427ec2c235237c491236d20f0dd86a1896cb310807d39ec1fb7b443cbc62ba5adb582bf77e41fa786b9409b38fc114c096f7e6d8538e220d5184f57e42c00aa04c9dc84ef1b6489cbf6c4ce8393df65e74037e6c6335494af7da3ee4407ada07e143adfeabf36e3d606dc87390a2e3d81c0ae8520a2e5a3124713449fce0631469cfec24fdf50ffd095fc49cdcb93da72464c9c01d3be4e3e0291e2e859c1dc8872bba8a32db4242942e0515b4db51d510bc5ebb803785e9689603f1dcbfa68a4d4ee1f50f4e39e190ae66594d914aa8e09eafd0a7b863792785f0d77a5b08ab1e6f4a8196627a56aaf9025456ce9beb078fb589106baf6d655b575b3fbbd3a38a4b3ca32726d8e747b78d4610faacb9f8cebf29ca3a3d6d9a00ee42c0d7d59ac54f11335e0696ded50bbf3ddad88045fd99ee24fd15a6425d95615030298dca2e03d6a0a1a17e55d8f45e34180c3f37d77db2d4c93d724faff52ad53428e62966ac677b084b4b94e238125ed67738d43b3d5f5d010826f74079ccfd655340c98435057ce9e728255dd0541c5fd05b34b6be1e75942c8e58d3c11bba339ec5d2fb8c7729be96719ae46ed3d6d3777007dc9750954de1ca1fa59e4c2aed1c961e79a316d008e2137b4254537ca1dbcf6512b282c271809fcbfdbc6b3e8aae9292d7bb191a974759196a9d9d4bd71535d28c9cf62f95ff00bc2d372c23c5c2525376f632316ff24d8dbfaa72aa54a73334cc498e663260052b9265e8b11bb2952d0d6c249c5c66a87e0e5de6aa7e75c4023693a7ddf5bb14af2714a3dd0c6417475d8e27fe4fbf74ef0d59f0958234c6fdb1d013347858c611a6540b1f78e945759c491a1fac80979006f2a3b0897ce0f4f8609b38867b9936bc616a629c2831797cfae27ea14d6a702f88ce55c25a2c46fb2693ee187994f7ab57a747dd5f1452e9d63264c7422bd61cff15c46af6b106b38e167a3333b60890852677fac56875bac3d6f38e2358b16b58c32aa0370846cfc2e43e27de65f8ca5af1de170e47afd87c25071f67b061491a0d27f7d1a67660f69a518c62dcdf2a7db32ef2b3fdce82d41f27a8852ed9170b7f146c77fbfbc45b7ff428dd575984cf80dfe80d6cb3a066492e5b87c114f4e2b6a3c736557497924d474ae1c325981f937a739860176cb5b89e6b38f92da7ec5ed213d875a450497aa0694fd31fa9d2d5db2f4159056a2c2f419f91892e8279290a51721d4417c8eabefcdf07f8073d3e93b1f3a5a112adcc2491f41a333dfb4f2ea40a7301c971814eb1361cf98a360ec419e13a01ab513c1cce8e935478a48607ae731f0fac21e02537376e72a8e4bf206bcbf19d731ae5596d334955a1e76e99484c2ef2fd9c1e684ae5bc868d94acb148c7ab530d08ce9779928ada2c84ef84aa9c92a7f6841cc7205eca4067f06d20f1d9c4b0c9937beb0521c1d30214875efd1945030b73af4137b5942c3ccf3527b2735f70c8f1f051e398838a4fcdde02d98a9e00e806a3cfabf94c83befe829e998a05b306d07f1365fe0ced5fbd3cf27fa3dcacf4014560f345c62b9b9a30e3dc9ea6130679aad359d5496199922fb3d9c0ad8c6fa9cb47222eb0b1bfbb190d1a51a39c6115796aebf876f9b54f6809481db37d4e32e3fcdfc7bc7b5c4a901f1a7001a338fffcac1bfadc6f519e17e5c0b9faffd4d065031617c84188b702255bdabf1ede4c2438cd1e97d130391191cf6149c12aad8b46f7cd7251f5857a9d57f21fe3608e19f64ae556d38d370cf3a9549611f85c622e34aaadebeded0d24b2593f9759c5d8788133453504e6d03185d474357670e1a70100c6f0d4a4c7d8a416c41f7b0db060e0789e02bcb7f0a6cc73f3ff480056565b333f9ccc9cb59661b724c69b2f001da071617c5088bfc79f2d2af2f8372e177ae0230ec0a2e447a88e362be508d4d96fef7952f2ac5b55b7314ae881dfe57051db603722f66fc74c4d4e91f8167782d42c4ed09ceabf0ae6e48c2c8a0efb2c1e4866e014b8b3a1f78d6216ba3190b9ac71216282afab9ba868cd98f44e37e926b465dae414a3d33f183befa7444a0a3f1fa814ac20fddd948e5890251d5e244adef01e31e3903fd8fdfdb66cfb851e4f75ffddba838835884586fb0c845b8595b69aa40e0e45153b704f65c6e1aab58dd8428beb0efd335ad3b64cc5ce7aec98fbb70f134c9c482c16b178fd12af37426c482453702f97df1f161bfe502fbf14e0c7b6b028de173b5851da5f6eefe4dc61144b9e610efeb4a1a3180c48fa205bff9d6f689eb4b69b2ee4dbb354891e0698e1da1c7340b71243ae6bae468ecaa6be9321943b8f2ad63796d5bf2f62d6bccee47a47ef3543947b9a018e826d27ef5b747e210af411252a2da5f47108568c9fef7e5fd6d0b6ac88c8a5f8f161ebe16a36f6c559350175449e0ca5f683d8f18a0dcbe5299ebf11bdec2d1e3ad8b08c6097c3c0e1df9f4ba5e8cc30ab42359d79101c9795d99d6faf4d1f065aa6e47ced9267a4cfae7334dc1ccf9bafe69ccf53a15ee0e710f6a1214ad3254d1cdd0c8df317ae80c050f3cdfaec1564738732e767fb4992e3607f0df83402060926e801435c758a30e89cfa5a51a4840cc734097d9eb63faa177cb0a6e15e61b15b8a876ed32672fa749c0d08399847e005d03fef12b9891614c43442eda25b43f900546e748f0f3cdb6476cfae64e285bfbb04d6f85c626cad2a6dfc9d282ada0310e888b27ca6c51cfe4a747168605287b12d396e33868fae277b0c764103f76a49134a6cd5841b3ffed9ee7cb12869e80239c09dfc8758b69e1ae60ab45046a933c0bb077c206d73ca96cfb4d6dd1c6f4c9e97b3ced38af3d7164321eadceb50035eb6272eae10fbef90acfb2cf4039ec846e7c1cb6f7b4d797f85609d627f10be2783d2b6fbb4cdeac7b9fc7fd963dcbc50d462a78b41cc3d25da77a2a0ead2e432e6bc05d4e213acb3e6b8210f06fb05b8ec9ef2b36fddf58dd2ddc8401ead6f0f0c5fd9cd6702e580ff91f905d7230545f606ca4d2aacad56140e03d9eeadef6af97c44b686dec7653ee718a2f6b2e55042941596109f6e8a65eb6eeb9033b1968311b8b81b896c4a6cc182889606fbe87c7b2d643dc0e1ecf2c84f1047b9b0e3bcd6a869e90d44e2167c0919994fd21c25ae5a965931f340392fdde3ecd1afbc3f1a8074598d4d6904e868a06180752a5f76284bf1780cfa5aa189174bab265667697111a1f4537a37ae14cc0f536c32187720de9e802f79b5e2acbe013d0767eedb142e2f7a69ffe5cd8f9606ec2bbde6249dc07b92a93f8ef144e14addcb976e8cf4eadec3cd0df649489973467a04ee3691c681677dda35479d53481a3ea8696b21c5bf1b46e759efc1a3a10e7584e13d01e1b6f0317cf31286d9b71274f618d6583f31d3d4c59efe2ab7ff8bd1cb8d98e7ca7f6a80f7bc726bdcfecb831043ea479b1fd2e246c317292023f635b07aca5e4c57b0c32be1a753870dcb76f7e61d5eef56f9a8638a59fda501c2364c045e54888e97a0a74bfc9698e317b36e764dd37cfd56e17e5b5db800eb5a4fee587bd435af420500417f55eb8690dcb7d8c2397b345be74cbf8662db9bc5b4af8898607392071a86a9980561a05c9f1d7f693ae32698083a66bd568974ebce4804b49c71fa28456a38fff57f6048f2adc5fbfa396d4379e8e9a42f7bb1ef04dfcfdfa5a1ff8a4239a919347331964622bfa47c9b0ac237a3d938235521a451883cfe4a02f1ae06aab8778ab9ec24fb5ff7720591a01f77928a41b1e1e4dfaf3a74a9ec5ef2627e4441707a89ca0b519931b287f5e0eb6e633cff9420c2516df144e7ec2f4dc8628696d832116e9e5257b29ffdab47af27ca3f461affcb9e30c7a3cdc17aff6eacdfc9ecfeb6890e66727318ffa60fb0df7fa790ea4fc96d7626394f01d8d321c196e00344bc68ab69ecce1e6c2d3d28140dfe0d2ca9bc6cbec79cb2b3d9bb084836b1eda1644f6c7e14c34c53adbac187ed95ed4ddb89242ecfa1aeb4b74d5d6db1cc5cd3f01cfe93979089d87a507e2887c6192dfa8ec29673e1cc1b51f478627d6b66895b2676036ae95c92afbd2c0ed043ad4db49b4a1e673b95b2fabbdd96546ede2981dd96df4c1252632251590c3e4511c97c1d8ebb28d9c4f4ff5dc3f21f3d946e5a7f99b02c2ffcf9d9fa91f43bddde02ab8a9d7257e691885641180e782143e151d8644acb31fa6b13273711d8996b91b689df72724e02af834f68d32df6022ede8e9c20b67adc7a3f5ade9a85c587e5c3f23fc669cdefe700b59956894d01c1931dae407b8cd645515e2eb75f4da1ebf7ad6235687b1eb129868cfbda951b1c93f521af6644298ab144c5cf4922134836cc5fa7de0e5b5f68087af1e49f8c900cd93940944810c6b8ed80127b5bbb48bf0bc6caca6f01674d19dc8470a8b48fe973709fbc2fe6550afefae146360a3871a940a3ce58f8ddf3d39f875108619c86fc30fd59d9a7f641acb98e4be8a48d97e85ec23ed026bc0e54e5b3e785c28f8e2e58f81e0665fe21289b82b850c92ab6ccb22e79393616a0fcd5a2d02bd1c350570173a730cd5c34cb57dc096b3103e5b1d03ab671a015463a05d33aeaca01e1f4a6ff734dfe60d5208f6446d9aa658752346ce127f143c5e1f136c5183293fd13fff4d3c7194f5bb5cd56e0770ab37891e5274075bc16c1e4195b4a092c3abaf3bb32a169691fd7360f316f8c5143ea68ad62e67e3489b1ef538eaf9ac1339654cdacb0620dc28e9a8218b41e2a60c8cf0e9bb29727011dc7af0253721d777560c8479b20f0d7424cc661aa25bbf9e0e49f17414dc1a4587185b284b3137c28afdb17802ae714f513c175b59bb7192750cd8625ec674adc1e97b4ae5fe4146a2e58574edd05e3d10ae99bcfcf497816d5d220c1c7a61ec6b1b4e6af56498f81db4ab698d823a00d2d05e3aa4f6368c1f71b7c9c3cdf9f1c992321b44590db219c34bccd17025b1c048714d03ddbbc02ca71649799650a928048c42fd4db57ce98f90c18dde2197d0499a93e9e43adb52b8edd06430878ee9759b76932f5fb8655e4cf71c46b92ec8e090a4c6aabef61dd05f88be3c712378e90747c8861d34aa3c68d5b62db66b94cfd420d5049a31cf2a866e79584a747ffe5f0692c29a388a6261574f817a83f89425217fb949779e02be72080736a5482e1d574ec7686b647090d41fe9a1bfd77b3fa5237fedd4a2e8da47d9717f8e7b59561e4c228fa2d0af374c707276b3669216aabb66b860c2339d217466c63d0e44311bc34b1c12f233b0c0b8c2043d68db64f2cbd26f39d82762cd492015f5607af7f3eec714e6925e2b9508633d51478cd983f77a67ae88e7eb436186978a4094fad2f50e947b48807e91565dbfeea95db1f0fb94f0ed5b8d0b1e1a3f96141e58f32ea1d6db6d10dfe3996c8f22af315794ae77a57103f6f197a532b546e5a5a8233b7885df78db5edcc23579da2ea7dc88a92328c805661aa3d96803bc4a749a307300b91a86870fd5d50a7b1acc35cd4e931b4bf2cf32e7982799e0149a6c73a79f860184fe46db8cfe111c8f6dec6f7a7e734b5cbe8cd67679d33c75f00946aca864a9b1602119de5d0b36666f1b9a63e69bfda615a1803083081a5eb5b5824122ff178248a3f5039c8592515e0fe45b09270aac28ffb363f2113cc04c72142e34e81478bdb937c29ac310a9b0c7edac13801731a17dfad971fd638187a23ef77259ef82a4135a6362b502e52a3d1279e7faf5d6fa8a430b3f7c50a82157d2a7d5840e929cab5f89e65d2df3fc1dd0da31cd019fad315694af84bf902a6aa5f9d9d89618d50c5b4104497185940f3efa82fd2d8b55e89833cd000d9373dcff573ed6951f12e987ed3560455018bd40121dfd269dabe0af17f5ef7f5c56d4aa0fb915e39657592f6d30605a8077786601d2bc80e24445737fa399f34ecf7c41214258197346cbbabb9087b8dfc66ee1c5c76580c4c4aba57b2cf0944105f3371659dcd1634f8f3655b36604da2f098efc4dce5157816f9116b796b9c866658a1c468ab4684585e46259b672df3bc5d1bdbc5c52ba558a6044eda435b14c406ed4690a85cacbf65ec63e5afd298dd736f2a4b7e750fc775839769b080862b63f46d0a45610250eea5e3c24ee821d3ed45f70e8a774b95de031a4364c0275f4d5d70bc43cc4b56689c375da5b7fe0367633d2f7992c092c227e4ab196d6fa01ef319c4407cdfca0b31b5c7e5e64c52afb7d08e511a015616e5536bc37bf4bbbd2cd3279f15d281d9e8b420b0adcecad275414c820c0eabb121dd32e703de19ffae39be0e08fe164e8e6f9e829e54ccb1ab51711cc38b4d9e24b8911edd01277cc3384a94042d7a7385ce21da37c52512f7df1778fd5ae004b3d72efb62f9430892524c5cf40e3ba0c33da8ca4d4f98cccc4e833cf5ff57b93924ad3f256d279c44984bdc7509cddf2d617c86c909409876b991fe1a76dc7a6e04fa12ce869726d61b47e34ef450776f92ae39ee62439000b7b81d2b8c5fd600e53bd034c5581765fc709d481cd73b218b7e2da38e1f9bbcb5bea9709206d60f1d1572ed17c4570fb73ae0464feb556e16c4ba736d305e7d9ef6a7ea3ba74e415114394323b99cc41c87cc349b9ddb26391f7e8bdac0c06228780d91f37b90af2b3f23948fd0663b4df3157de8a73b63dd5b63e026a15401caed1956f15377fbb44b541f8031fcbfa47b340fce66cc77d0df92bb69e7ca5d5586ca1c525ea7ed026f3ecd6426418a0823494b19eb6e82b97652284c18785ef4e423002d2fd7d354959f461203249b27d4c6a36fe8f5cc82e38e9491636d49e86e8de06101dda1de5e010f6388da478c3dc0bd34e8244a289bd493e84a5417b4d0fe64574d282a6dc7091eb305d66fc79997fca696c690fa92ab8a07e31fc35f72646efb76e02c5f15816a009946a3a5ffccf00ee1636fbaef9f04e4da2a32cd8f5f3149db9e6a9213d32901de5cc5780068b65550a15171848cf51fbb7ecfb7c96d25f21d80c6765c97ba2f187e8b1edbfd92fd9235c898e47677007ee1e848f18081d754b73747208e63e34264cbfcdb27fecec8604a4bbc054af0b8700b9c5eb18617dd969a65fd3425ffe953fb0ce5fc5270b8727bdcf3a412d8f6af70dd4c35dbea296c286f131e33a4409d4a0a730bdd29f68816a6f91ce27dedac922cae0fed9ffb97f2bb25712b67613d5e9c84c7821c7360ba259bd5d133d7fb6a28bb5c5ddd0bfd5324fca46fd97bf6c3877d463b7afd6af1de9d165cda6534a3a0c401a977a1e3eda27daafc257c8470255a37ab0ad00a79ede062564d669475c788e26da332594877c9535ce297d3d6c03259cde67d8edd9b76f21f896d5a06a23669654972a1d6e7656c0b210e98857663efd04af49617c1a3037bf9b1cb57dcfffc79cc010c909837f035cc2e7735984a81f63acdb828a1afe81db14d1de0b890fca4ef6d49fb6482444ade6e2cc799e64068767245bf23a2aba87b52a20fde762d5aec30769581fcbe49eb984871bd7b77a106e9e6ab4506e7273921a722479a157e29bde37e6b89ba3a36420a751ea2cd08304d8057e840cd1c13b2c452ce79d96e6fd9afd6c3eeb4073fc5eb04ca15075fa265c3c7fe68e74112061b8c1eb9c13dda8ac3b3d3fde5ee5ca1b5cb6e6e400409e2d4de831dfa562d727164973809204f2d367ccfe85445bd36d2d06dcad4a40a59be6ae61731ca1418fbcedcf3173fd18a211cccb792a8a252d498e1910f5d768e0e0d5ec7beebdab53a78babe7ffaf71e98524081d925af3ac8789988d795694be98c0c5f380c5b83c32d7ee05d8ee58ac14620c90f968b80e307c3fd7fd453792df83c3cf7874d1fb8402acfdd9900d48fd2461bf0f0facbfe4b2c83cd7070402c1149c1367771cae5fef813a595d9a567d6908fabb9a74720cff204c4494b5c07f8356c81f52cb46f4575118cc88820ba9f29f9559cd97ace881a73f54a5978657565f393bff363180c25e8336a2c2bc4484ee68c700f96bf7d48e809b09b7181d50f7eb6e641046d2bdfd99fada31d0175690f2378a187e8ef7a09be6ede6cb84d6ee79ccf94941193f8bae6a830937b701c8b91430d59d364ca38547446811b722bc1e9cde5c2b9c390375111d2f8a9589657051b37c064d505a569a5d0dbbe6b418b5b99682a1b5b3c4f3a8337570d4127a71c26a006861ec9877df88e6971e1fa5171270132217b395929eeb15bcd5bc9bf8cc93c9a7b1e6b19a3782389fed3c93e6b28d4f88efc80c3e1191783cf8b90ccf0da8ce207f691a2cc10a418f53cdc6bab9ab6da251600e7108fc68a9ec7fe235353a521bfa78c5ac3baa94710f7794791af8d0d57b5a1c1ac0c206a6d558cdece93698a15b56a50d2b73ccfb0e5a993128a826d38b2000083908a7bf1fb871d70362f1a924ca329d1bceec1eef74f91229819f287084ab1f76de837301fd35f3e63fe15872034d44ad3a137f1e057d903d45621195d977207df39b646fc73a5f6ccb20b2faaf1e8a15c64d2beabe148bfa396f7a950ac86fb8fce42bd545ec733f51e9fcfb50285b0e2f25c830710f1155b84420a8505e7a29dd0a0e6fcdf80890e02f0333d47cfbeabd0dbf7c4561a246e2e9aef8ca0aaff91ae144296c63ad2dfefee227c63ff3c16192307be76f37633fb08340e675eae525b135843d8751076808980a2f9281a030a52fbf493d0d2ab19b363fe33b8e7014effe72e2ec2a6cb6010c7b0e29c205bbe795def635f68c06f29d680f20f903fff5a54b6629efa05f96f4ee5a22b0a9f5f95c73648b73f16671e1a996c0cf75809286c6ab8aa790d43f01c8321f1c0f14955dc7b7ef37022eea8723bfe30de6f48db131c86f8ed7df9015f91d522eb028a8dd26b1e7aced7a1850202273476d0e0e365fe8f24a1778bdf81508d4f0d799de40b7a4bec926edf80af027cf9cf32579f5dcae7f494cc8a1b094124a6ef892537aebdbc5f63489b74846018c3315de12240b12ceec6f8ce1522911588f4c54ae469373f7fb41e28f139253f8ed95043e834f4b8886f6c0e19d26770b6823ac319b65ba03966b797a5e79471fbf1aa5f1d73e3fbb12659bf30f6b93b509c2e47394b8f45f8eef4c22c1e223766dc17d878af878700bb8888922d376e8f7ef51c507e69bddf5ce03c07f48ec7ad2f43412af2812283a64a5e0819d591cb8d87c8e2ace2dae3f3b236c69d1c0d8249f4da991d0081b53f42bb87be7c15364e181d24cdbd1f7d446f01865b9031577a8537123d6d74304c6f3377f0cfb657f131395add9cb09aa89baac47d16a6afd736db71b94efea1a25813ca5ca7f17b38de66420c258c08b72883e07b60714b348ab5ed515696812dd16b5e1e4b3c0b93b9365beb705aee7b22b4c45d0e83f822d4a7f031f7370864dbef2d8f4adce90aa72a26f636d9d39ccdee1575909fe404e5b3d234be05c6ddd149f80c4d8713f32cd82ed7909bada68261ae82417e9d21f66b0e9f0eda74e6120e99e06a4ee53606d310d81318d8e1c0b4514e979cc4ae52b6f2ffe96f3c7fd28c3c6ee1c475528f8f2390d73b394ad10ddd8bce419440943a6447f818373e2f3f0b007b3c9971737d21795f69fa01ca3a70233204b12a13c7e9c485c3950e51c032cfd677fa50aa21ed3195c6d5aaebdeaecbe19cc770556811651b246c141e748485db2522e3f44924aebbf561b23a225e25c4da4a75fa8dc7bb00ce169552d40173b26048b9a8d7a589d1e5900ccf04ecdce7c521ac80dc44a7582d29106949ed68add287e767d268bfa5a395e2b86b7bd3007e5f0dd5ff8991cb25bc46e6ae5c0c8f8302199aafc464b15758d3f8574d424d381fd143acba9c4e04a686e7b575c110c67aaf861f5c310aa81aeb2a1a95f98375b72ef4ab125327c552bdfb86efc36cc25bd187018990e1562c124f9aff82859e6d3aacf227f8af4b82fbd0a676cc5d3e18b55c1c5676342d4366b2a7297ca7fb65170ef363d6e164ca0a26902fa3cc2c21e79e428c3e201eba27ebd4ad4b80809d2d4c73cde1253c7426e2fe2315adc75939ea3fc83834393a1b15be647ed512b51cfa2c07d874638c0e5da0e277418b5a047ff2cf790cc020ad7a9ec3c914a7bcde60e2719e0266dc64a668ad6bb9b2243072a859125eee14adfaa5e1f502d55d700b3ad2bcc0aeb0b4ecb949b0430150038394b9b66a2e0cc80eea846d55e082b141fba01b0f1efe228b6cfd46bbba83ff450a252b7f75fdfdd1d27d6783d02e8a662452f1787593d12736e503811942945cc92bf47927aad881ab50a270672b044149d2921b1804f0126b011c4b1961d143af129e5b343d65bf0b04834747f37cc367ab980d7d9eaf685756fed5cd511ea7b74df2be4d862591af0745012bb03eb202406a3f47880490fb120bfc57ff89f1c26199f8fd91f19265b375b51a147e9d2cb98ba23dde50f6bc1ed07ff820d7d1464fba880a2828d4ceae5e36d934599b5206270b6ce5ebcf7eea7a2178b089ef79e358636227534a650858076fb3de7f3d129abeb1d0cdc4395df9693e771627dc192d50add41a60cecf2d9800d66ddf8aa4eee9aea5c0b21a0eec15b18022649cf29c1f4eb14385ddf0e257cc47e349bc73ac886012fca8d79577e7e935e9c83c95f9c58c3a9b1caaa4a1f67f650a177f1f3c0ccab6be89e80063fe901ffb8bb8e2d36797cf51dea5ac9cf7cb1feae79fc060ef4f13f756de98d3dae9765dfe13b237bec0bf18b2a7f505a45f17de0f5d84c06f06ef1191dff4155a0aa3da7ca10866762fa85a3c11b1fc31fd71e89c6da9278eb4523c82498c88e847dd7996e279357360bb36179e8c5b45c3a96d31e4b8ccdd423b8bf2baeabdfa1a9602e4a1c7e180411a2f1bdfc100a4d40dcb95472727e257d369182eb97db4a6554e9cedb690574b90a99d82bbe9166e1a305c91769296022d5ebff8be0061912a657a6e9e5ac1495b166463e828f5641e7975751ce68d237c04cec386402110d72902d35f94346db801dee6a9e76f097a9f23f314c78b7b4d44161e0d634f38fb181a12f70ed1ef530298bf2a280e2ccde91984ac6e6612bcd82258fbdbba9b0ef0c3db970c9cf46fff3b5e669b71c06911749ac247a81ff86db036958e7c10fedccb139db8340882f624c7469c425980fe6e6c7771669c916c2211513d129bbabc66c15e9d36a53a80663f03b893b79601e4cac44424a14eae77f3bc86270bbd8ec74c2f33ab09bf23661c675d9a25a79b7dd1136dbce48d960962b83db7dbbbc1c7686523712f09046e6948b1ae2a6c3f6c593f68a6e0ff010737ecd6f2a2f7f6491578efdba4bba5b3ed751c3d39700c15f6ed831c575c3da115cdd6a0d49f388391db51f1065ff18339df2420297107fc7a53263cdfd363f61f17de003bde548d5b37b941245b1dd6529a72b3d0c6cae9da2e80192d85787e20e6e256df855cc3f65ed70afb65f3522fe07baf6375bcf194a89c8b54eed5b5ccac905b4a53cd799e78685a6d75f5e47b60a3f1c55f20b5838ef0a5a624e2f854fb93493962731c5d67bd124ea4f0a15b3c5f5896721bc9d692f594fe558ee948f823d31fe1bae410600089d31507ee3b8946fc501ede66492fdd92dbc0480c07817ae240627465e23de9c91a60884e5f5fb19c1e5fbfbfaf649ffd49c5a9a57d8b99ce429423a5fdbbe9209c83028c368f9d6ded13bcc05cf519ff4fe30013766d582636e06681560c39307cb5cbbe3847a347a78c381e9d43cf457eb73842a37ab713ba23458f811672ba7e5277f5e8f831751baa498df02670b22af3f90f30caa00cdd91dae1cbd155151a5f6c8ae8445ba59a547413c8be96d4c26c51a5e4d5bc827543306e68e42b99c130d4a5ff59cd9cf56be8413b7ea60c5f3b04ce576903a3f606c97f0b76fd4b4eba2195150ec3d206bf237404d533105be7774a36344f9211d2b679fb3f32a032568e346d20d4c55e566f78f59a9497a3a27f4f711e6795f79d0266e5fb19d090dbce9b36f57131df9dd12abdf80155aa089973ad60edc047e7fc62163ae8c8f58bfeb0f8d887e992f755aff4f224e0142af8ca0675bd1b9aa7856317ebb909374f7c66c46657c7caf8a0a965f1e09181622392a824d5e5ad0a83d3f46e6f12a75425215867156abeab3a498e31e0a8b2d77fbed3b92149d7c3ed4ceb9432040a5d396f3ede012ca5736512645a0492d2fc70171f99bdd81bc4d74a94d221f4a31ac5bf941152f315bcab8d667a8c9e8478389280fe269794f3d4adeeef65fcd9825640b17c599aea5110b03b127f1afab9fe07d755e40bb7acd943d58246559f234413c9f553b7d7676723b9aeb394ca4c7cc2dd517cb74c0299b601f5ee62b8a94ad3edfac5898fa748627567bae797e1782bf85e666a2e34c419df9e9c3c9084322ac1314833d2513306c87322d722c84f589f1540f688c4bfce4154bef2cea22ce1528fb6d5bb24a15830b64b8e4311a73c5be24bfd5dda9a2b8bd93d45b85b1551543dd576cc0df352ee59b74ea8e92963d3832e61e7f4f4a8ad9f5d94f8587b706a38e2ba573682783f2f281e1e2ed4801a572d48b66741cdebe00e6d1778547086c165a0b4a24a6b46f136ca477984e25b2ae1f0a9dd802c0aff851794bcf78df266d277bc412e27cd51be7bcc38bc5618c6541c5c9a6fcf8321f0cf265925396045f03b136dbb240b8aebb5c64ec6603fa18aadfa8fbbc3bf6ac59a19e34ebc450adde8ec8ef773c5fc8ccec67f6fee2d4348929368fbd1cb0a096d184d8e904ebe185f393e137b971443e76cd7bbe1ac40580a786ca60b9a8fe8e877f6322460734fc92857eeffff3a2d2dad2ed5db27e23b6de1768a35b59da98ae5b45ecee233ecd52ac1f727b0826033b1680fd1dbe9ec71cf5beb2ffa09f1ba8ed0a32b037851b88b5a05a5e5e21de7c1351ce4e1e4a9f002589057e41e58342e444198ffb97b56269cd696467958f5e3bede5b11825e9767bcae80952cc9815ef65dc4674a4acd23ff502a3b8a028935e2d65ccf2fd4793e9ba3908bd2775f47acaa2501ba1ce75035f64a71f868e13cc1db73d638aabbeebf44eba6bf2c0397afa62eb89456edeb572b8fdd64d24a88d6d6dd1d82673b512dc216393c01165e87b73d858c9d512440aae5dae7ddf63dde2947e09ff50ef9247921b44d721e022c57cab29f676a49a956e00117bc9a86460c060b88b8f1f9b1201c2082fefa39ab2a5fec009ec4f5c3507fc8015c415148b2b3712a35a327131fe2138c1583cd29218ae1d3cfa12d30275e5726e2acc29f70919f5abb10f424f7e06e01719695f82f0cdc70830b3fd514a8713e02bb10de8bd20b5d2720048ba311c8d4efa7c052725d60a1b80ba7c3c51bb98726d3b96f97ff2fcf2421e1085b0fee5aa236a524edde21e6a9e5faf279630a702d639fd465d91a4b2de4852b406315d64388a8dfd91fd521ce8711ae7586e417e4c08506c91adc61dd3faa838929be4eec17945b8c4e978fe891b386d93d5073b9efbbf39576b18c7972041ca49139ed97f539ecc50e1ba17470034b448c57b9c626cb39f72d8036c58025d5b3f34e3d398232d425721722d0c3b47cf9c585d55dfcdd70680dedb5d17c8bfe565944c0f92542a1770ebf272143644b9c243de5cd25d42af90454573671f4e81c6fb998ac02a2ff981b908e5a3c69dff5a1eb9ebff05a31f44b316388bc0214cd6ce37b851115aae12979c82efc1d286b5cfb3a69f5a5ddd14bc697e6ed58e5cacba10978c186628e2e1115d9f35f800bfc71b66551c7afae0b8cc163d2eef06d96b08f411288e9340605c595c347a237ffc222d01e66605c0e545294092a33070faf9ec2a5aaff94932002276eef0277f96ab14848ad9f3ab170fe3d02769d5938def50ac06888029c9076facb2b2f5680447035e06ebc2d6d61f4f6bfee89fb66427ec1fe01ff82d547759f8b5d1ceb90cd8602812e809ec820bb68243cf91ef4f85f93a9fa9bd76cef32f7367b6961d222121ff7a6468ef2a82b3fdea1ab65021c96a60743d73b9fbf1d4cae87fbac84380e5fa817a25c9b1193deab0b0f5361e9f9a27a904d613efd3616c2aa6d7e8f3dfa476e0e66e5b89ec3c458e50b3b9ded4e3eedd3f8965ccab070fec19a6463a8e111e51819ccde527c57972b76077b75d86d19696d33e46ec425ab3485a94d9f887aaeb41e9f9a58bcab9ce8096361e375e01bf0b5a9fb391dc7f647d4c25721a265a1ad5051ebd7ebc965c736e1e1a752ad64fdef6f7ec94f84ec481fb77dd34c9d956ae3b0d2a34b4b5e51424b3eba832d17c5099ee2b431decdd54d6287e03ec988919c424513c1de80f82dd9e66bc9d5b24dc0d58942f49b76b93280073ddacbbeeeb41c35a342da7d40b6a573691e6f5ae44be47e2d9486d2df8e17db7408e3bb7116b2289549d53f5fdec016bdb656c7fe8490a16ea02b986ceeab744b74ec96fbaaf4b5f3acdd2eda40e770cda82b3370b511cd983d594592cfceceaeb164ab95709f6797d24b3077f165e5cfce174d05c9767043f0ec9094587aa815742ad38b6a0150930d5ac9715b401f11caaa68ae114d0374317bc854d981b41ad6669e4980631139d225fea41b973df7572f894529ecdf830ad3248b4962374df4c228485cf4a4db3673ca5de55cf25770bd9fef1f12c1a60ecabb4fd9a28964386164054c77955303686c997c0c25736f0eb6ad80e91650f9f91d1e387479435ef027307708f1ef4453ffb1c430c2b9486dd4ca2307d83d847cb9e75ec4d477c6219417d194ffb9f907655e191c1dbedefc8db9fe3def2237072a0d3e611daf8dfba452b43b0552694dce81707aa8b784345311e38fc079ddb03d745288dcf78d4c9a0b72c967978ea325631a7c7ed4b36d385cbed9545667b5d22ab9309bac0c46f0b574ed359a507b5ebaa9bb2f6ad3e95675abc4ac8ba9320dfa151335df10623b58dc3e85a63519d4caad6e29f7998cab6789afe279f7a748e09a7240b143df83b3b91be80a0e378b99c9542771a032c33710cbf9c665df7a0f2e2f6c4fb62f82f4ca178926c203de6a549511203703cc31622edcdad6f76cd8787f567d025f3bf4b3130457b5a61e9841f3e88a74fb87044a0cd3d06a4fa804454f0e6f140c866c6b1c04317c8f52cba3b31721ca90e18e8b89e7b13fa65c39c1ddcf23cde3ca37dd85afe129d6f3599ee8e702a4873dc653112a20d071009dfcbfbb70f804a6f3c82e5cfeb438a84f4fccd96523710ada7b2d0ab62a80cccf58b6d38a278545696c5a85d0ec3bdcf8defbd583a89e070527d599073f5222c973ea6d4a859ba59629f7bd20d2267a95662690ad7d5a92244c26574ec4b530649d37e8f6d684ad6d7bd22b1e07c1c2072ce513208fb0987108bf85dade47344a110a4b6214637c8f13f01c5828b4731ca52f9b72b5f33d0f83413a5ef7618ef52c212db4e6fd40b604c8c37e79a8bf7db09ac04427621b4f363c3eb98533f4a427b35c8ef66dff131c89ff13ae4fbd9fec0ed4fcac4d32b56f59a7218fb11fa8b96639a1fe525b5756a1e264d4c89c3f7b90839e6f976532f4e1c0efdc4b45cfa98da7fc05dc455c19631b171b4fa1915921e31b0953f558930d7b86cbf5c000957dd389963b124cf306a5de6eaf87474f5b930f5e562524e457927b710024133a0636973486da0c1eb881ea616fbd578d0970c55e763c72c499edce5b46eecbed5a29c511a47dc25a42fdcc9ea37d72e41603ae77dd14b01f3c58d4649466c1a65d0987f81c944ac8781a5a2e718fd26509b935e6d56a9967fae1b899bc033e1a36fe5ce6db21f62a6cb309b25936c210b2de62c7a59a4ca973871b26077a9568793c0a6ef5bcf56d5cafe726cf916a1b1e02b956d5f4e4aae28d24a7896495795b00f5bc035ef60b95e066952384e5c44124e08616b05505c6efffa8136dc191f6eb8191ec26b56d894e44a70cf103212609c66770c40c1c631e8dbe2218a209664b09936ac703dbb4e182d17a7d4613a524a1eed23b28a55e02207de1a6b3350d9e8883dc8520eb5e79aede13f3f5649fc94666ca14cfa5b3a29fd0ca9726f55a9a259b1e1e85fd190206fe7c5a6d221f5508b8345a3c3474c567b95643baedc589fc682def65eaf562fd90d1de2fd001436307a3caa76fa4d66f6078e0482934dbfc5af9aa01d7117435658d55d3fd5759cf087661b934dacb15428d9a0607a55a30917b0907deffeddcc5ecee7713da63558a0002f91afae2f7ac3a4cf8991cc437f658c9207294b2430e54fbc883363b336c29bd942789a9db7c1589ed775bc961ce499583b0d06ef78fb0f1e594cc36791ea5fcb733cc9cf88fdc1413838b5ca30ccdc34f1282c4b83cfdb14512e5835add6a042bbfdefd1654f615b957afa79562b469f59509ab629e9627b793fce10e169733fad13cf3063571ab5458d7487bd71e6f24cb061fdafa81ea1a6fdf0585807625f0789cecf9dd45adf3088e3db92642ce2e5b25cd5f8bb2962eaa48abc2466e9656bc11e6733bf1ff8f4486c98fa97f5758260a3f5d6e380e4cbe494e85b1148f6e56db93c8f95af67893cd5d1328f14db3aca239c70db1572f3f80d304dff580575c44df20abb66db9b0930cd1d939b8c54d7d6431690b8881a532652e45c124295de5915f12a2a8b9513aa612e4f07566419f3128cefaf9e93f695740294c9a79c150a05cea228677d492590b42fbfdc11e322b706299e26522778508d9312e6d842fd535549f9fd3a664b00952416eed729cff084cfe744f52f18ad0d430a68d2620979c6c71f58d43d31230b894e0db3b745ce06847579efeebbe83f53f538823250bf323731c7d7bdc3484aec1dbb650e70f23328cca7358565b7600818a503201dd4edb32f2321f625b01ba7a8b805d1c61f1dc9908143a4ad78a21bdd4dfc1204f917e37102bff8f76c7ee2aee526b369728445bb5fc791a1fcdb10d1894dc9cde44924cc65a225bfd23ac4a9cfbb58c1405d98c6b54fc733c00521b88d1d3dac1645b8e8492ea404e7aee46b85e0fe81992fd8f09948577f80b9efe8182c4b005dd0c378f3f8402ac4221f689641a930754a6e938a57d22edb2154077bef35ce617ce5c11cc2691b5e868425d5bdfede23c07207f4376d29d046ae8ed35f4786e725fd0c1d7af58738ffc5fc185c09f2d189c163c2e3f4300d07a16f6912fbdf8d25a821d413267eaaae097d8d43f27a51269033b27ea5e1672290694ff027f66a0b5c620b6991d47d060e7d635fcd711586e93e17e9f2fe38c22f0fb3794171078ba4d8543c2c5cf7b9a0c4bc9c4c1172943b06132625689df377c31de9abd98b9258f329210b9a835bff0b2e92884e41589d89078c1a8ae96f39ef630cfb75ac003b08ab5303b9c9a915f635063e53cf2e620e5654f0f5eff556a741c7abef90a6a36fcde62571f0904d4766fbafd5229eb1d3ac13d0fd53f8e0feacb055f5abd40d0c37d347de96aecc1f75845ae7ba33328555bff3aa822351a5e4a4d6e1e973ccbbb157bb15e9183cc307f6cffb2bdd929dcf3b593a8ea1027dcbf6fef11b77d5da8f0313e5b4ab6fcf887c77fc1de0d2f9d806a8d505bf50291ca2a5bdc5dd040c6ce55f69428cc4197aef90862825bfba340cb45699c525427a87622b1c0d0e93809f383da76974e7dc6511bc0908f4a2545cdfd13e16f730bfff968f02a4671c6cb4c63941a1e6355ffd68ea16abb5d7a04ad07207c888b4361c9817e5494b354d31c0383c5564b132bc1a632df689d23b2733cc5d5b8397abfa4f5ce9524c0112f5e35060ccccb3900f74bfc37a6fedd4a48bc85c9cedae716d3d5fbb5eb19e2f05e19f7ede7577a0fea298d0a742db282abaec9aed86fc2577d5c5bc21e1c8286fb0dfbb82b6b04b4444ec40b7fd0baf3e34e5d82f13f3151bd504486bca9e41a6392e4bd0090493e643de700266894e21da9f67d9bb47e886d9a8d67cbe8da54e8f4c4734e0ec5f8920f8aa0f93806ef9df40a6588aa2d176cd87c2bd3a368958b7c08bf9069b40ea252c503ddda494c3b84b9ebeb413881bb733f42a6c1dbd2d6c7f2f08015d3725f61b9605f42c6060bfb39742bdcfff6ab3a3fb329c8725edf8dcb3c0c32bd5954c74839495b406aeccc03128e60ea4faa7f837746daa156c4a67dee14d5c9b24c2efec6e1842e588ec43f8c5af57095c43b99f3d99cbdefd2b3b21dd1595e50b26cfda8d0b3386c51325b50bdd7cbbd697229e646f56bf6388af59d5d3adff6435958e54f0ac55586a6bb4e845f28e34f242a4953894e58ac5340917cc37f40fb3c597aec41a7135c45df2b7168272af479b1db59fdc450ed2254d130ba3d0d8232780569b90d27e14da11a87dee7ce2468f10e870d4a15ba7b6d397c92a9eb7add6b6d46f3555d813cf72eee695182a866a03252775b09f7ba993a24ab109845f8ae38297702e686542ae0ea103aecf36087113ebaf15c12d182b1ca1d21033fb38744cbcb0664e190b9b98f1c7ad5278c5043914c2484d788487205590fe182e493836842f7eeb95a8e5e09dab1a4d6957d98ebd1505560c8a0d963a36e534408f4a5a719ea704b9e687690526d7b725d6a36359fd37456d53ce5c3193fe2d44a383262f63d88f29ae779cf124227cbdb88caad2534f0e0e8e8cc4d93f96907d2ca747bbf0d79bd24f678089d8c180ef69d416be09a3304bf3769574a22e990514280eae69c6e848a7007349c76be57aec1f55e519af8857bbc07394288e819d9e8240b8bcf11137796bce057d1d02b0d3f5a60ac61c140c9679335aff265b9dcb84ef7fd0a369bd00574f82a4ea123e74074fd7aa002fd96734ca41c0c735b95d12d404df6c0ef01b5da9285a96d01fae40759abc9fbc39d607474728a27fec6027f7fa53faf2381f60e27f845f666a573fa6be9a626d81d3ba2178a81f1fc755dc95523254c105e079dcc5c38bbeb15fdf59879bef33676fb6bbd76bf9f6b97b8aef00303502fcc4fbdb9625258ba837c7b534cde55ddccfec4fe1d0a65515ba82600b5ac20dd784d42d6aac777160581eb06a440c99193fd030d9ae1209d556a9c9c3baaee30a6873a0e15f60a838b9cc519c38dd9cbd2fea57413e061db47d76d0d2b9104f87386afaf9c09678bf7ee8b6fddc95f26d4229d68c573baa3e584cbdc54b755b374a0692ab0f010d093aebd16c68405fe2388a79395c171923a3905a9b3cfd8339694170fb42ecf56d7036efec37777f6fcff8cff65c66dab95ce3be701a092ab76a6f27c1e70aa6e580c82a0e7fba1b5916efdff2ee8bee5630ce1c976c626254e3d3e3bea0b9c9ce52af033e855681b0913e56f7716f1643d92c46a969288780fbf3bf190df1cb0311bbb0861d6f991bbf4106297da87724eff680a1f01eae690deaaf18507c34c9d8790f06c5a9f5b1f93eb96daccb7532e867a6e3476bbdac14a04651e17cd3576fbcc6ba6c42ddc66cb4db130c481167df235a669ac6ee528129366d1dc4ef38ed40e9759b6c30f5141103861e6030b346a86c3c7afe232bb773b8c6a60a3509d5af5779ac2f26af6c66676defada0e000913e6926189ac8bb944e0901c893496d0cda6f032cc1d8e37a4c9b586eb5366282fa2b9b27ce6ca8b3d7783ef9edf568657a76aa3589af31b3e19902d2976a5d13345c0d8186053c5c93c15e5c2ffec4d8571e9d78b2d58216453ddda600fad2b637a1236edcc40362cb87d588f44160d45ace81abe64d7b77733b0fb044a357efa9c459f15b1dcd9dbba96db9f84117c23f0dd702a9ff25740a2e0c3d6e79156835f8b1e672312547e5195bc0121588965e7feb92596c6932e9188c0201770598b53fd750a4000add624924cae14ed541f70c0f50f88a13fb25efeed24ca3080c5eeeaa26d722c88ef12794b5a84bbc38a57516bc2590f4a3761bebd21982642a7c8ba82ff70ad5939af1934079fd0034931f37b3d940a0c120df8741d0df9e37570cb8ecb0932ef5a84916a8393861fced9e203b16033563799a5c290c272d7d40432ce6f17fe5144e74e65a23c0dcd7fd297777f6b0d6446aa7ba2b94f8460c9c55461642e1b77db8dbc755725177f5844c87c570a7e76810d82fcf76feccf83ec3604143dd276ee924bbe54cd8b6cbc45c03cd71fc71b0b614d7753d6af7344c0866438d8143746494384ba057fe6f855416880cfcc61a5422df28ce2d2175ffce74227e886182980ffeeb6f4d7d75ce10392db28127964ba3c6c2e466cfce45542d534956374cc323250827821bd85a4f6ee90bff386d42730a633f4686d1f56514f8fc047205895247eecb7d2037b73b10a3c720c4bfb410f93fb21bf739aa2f706dfe92c3188085491c816cdb41860fd1ede25a5e018d67900e7eff697b8ad66986e16ce3628ea250ab512b8bef5a7e9941f3534fe2f28c2bd56454d05da338e7eaf6d4692ccc6833b735ac640991625825a070f14e67403782ccd573deacc7cfbda15fb70cdac8eaa511711590f54faa3edd951e1f88a8c9449f4f88182227940fe0e0c32fe605440b7f2a8c346237e414a53a765d45c9b86ba3f673a810b36b067271640de674e826fbbf6938a9ac4b2fd5e6e77c72176211da5d2449c07e2fca8015b9b0812068f59b39dc74d2737dc70bdfcd57ffed145338f4ddf6c63b453242622378b34504383753d168577c8d30b95d97f918c11bf1ba36c0f157cbfede83e779dfdbc61a507383b9cb4ecb69eea1fb19b4cfeac1cda9005268e37320524c91151746b9f07e1d943340c111ca2e58bcd7d6cbe24ad53219de51e473b3520f467549fad47bb8ae146b73fb66c7eee9a2681212bc154017cdcc4c7cb70c33a1d7252ea79c7567382711f275444f45a17477df7f24cdf1594686408a4e6c1acb110c8afe0e714a169005ba12e04eb226d964500d81345dd18db5d0b2d987d0081efe8073a88fa8b85936231dadf1659069b88a02994ba1d15fb11a1111c1bde9045d2e0076fb4ebf01648f534d5cfa5846047f06407371df2ae269f05b74e5f6ad3aee24fff177a0d4844d1b2792c90ffa690490461b7b42c51909ac894b5bbb8e49ab60db57c58675f26fee9357923af668ea8513e4dcb2189b1ae924be055a8bd2dcaab84a9022d6a91c32059ed603cc11c13d77068d2236df5cf26dea63816496bd34c65da9e6425033e76388dc5bdc03e1a22436438b6ae7a47ecd250e48d7dab71d72dfe10226ff82722df8996466c2c1c61d72f448f895b3aac32b9e125cab47ad3a293dedc604e56f75f2c1533c64e3b34c23c4d5519dc41045fd7d272134f7dd14b1f7e690cd36608db51db247031e2844582f9a0af1e41053fe9cee01b3902d7ebb8ce214e6ddd66acf94a3dec688e402ac774cb2373da784fd1cab82a49138bb45e9cb280dea85041d5501957a3d669d1db0163b1445475506cd02eae2eee66c55f52a5c43bea798a8be34873c88b7b1efb99f7c9b15037c43e31d9f42ac63d2c160333993852208b05b0c0ea52a3d25bc3f7d1bc3c4b33df81c7a228a3eb0de46d7370c53d5b31ae2cf174a9f93b57386187b647b2b2196e4f42584dc3aaf43eccda9f7c7ae711ceacb15f0930a5983099fd9a8a8a47f079cf6afc01a6ae55572e1c90baafff18c1cd782301e0f49a911bf7f76960a22235d12484141685eaf291777327011acb02ef351aa9c3ad3da238fe205b1da20f3a8999e958f0f5a6e1997ca9451f5c09029d03abe04ec81d00c88e902b9ada7ff4fa74e7ba490e8e5c5ced6d3f1f61ddbd46eb8ab387846d514c24ad1f212eee213d71cdb66c4ec53f9ef87082425a04a9f233bca8c96ab5ba95ed0cfd9b6e2547652e2d54e8852b45230315cfd05deb2e53b9b7892736f7381132c0fc5d3cbaa68d64a8bf0620187f36218eeb11b0b0fdba0533da70718561d47e8c51048d37b7ca8f81f8efbc96d58181502b1cbbe8d1bb4b5ebe6c9d458b4a181af44e86f0838e4e5879d620ee3b915b3f82c7d164131f577120653d3c2fa76f7b835b854bb202a5cc49ad78dd03c5b471a8ff53905a0fb5d3f23b1d133d7daa51458fa33fa2212a3a89b0a901646a03c5e5f55b2b6962bf090fbefda133a723738760b916d922470651436fd399f6621876733bb1c35284cda9c3d529aaf8b1c12ef1d8488a76f678dd231e1dd721ae36cd561314cc19d1d39f8ec6ee25878ae13a824b5d270928d6ce2dc4f0029a47fadf5a4061cb424b373f4b4de69f63c78f1862c9ffbda47241b0570ccdd3452b2f913837799e47b1163a49db66bc6d6648654f31343dcb4d613a7015241c6edd7ca63c317eddd3fdb4d18e4eaa669a11543dfeb1873b27576e6f9fe3926bd61e8b0f0573abc0f808cb4c8a8e29a9ce0b651704f7eaf571e9ead9bccda22ebba7117fe379702056a49faf6fbff4a3d5d24a4357f06296218ddca73dbdef296cd4be6cd079aa5adea4ce4e1ee63f62cfa22a65146ecfb8f8c4556bb417aa9b0b9cd79b9a03b32fd502d2031e50331375dc9ad443dd2df905e896e0fb115500e808f94479ed9d44eb9857b2b5223205cc42f01ed8f9025cfc42150b6907f763a600fdd238f17795ea572d8aedaa82fe039feb6798df63edf15a44328153f4be47ed9034159609e4723b74eaa73f94c15ad0db377594bdc748f46e07b98bd1db59a64ae0e36e017bf66efd77a2ce05103d3ddd67565054158f3b18f23f005f7bc5a5ae956f778454a02c9ade8ae4590874399d5a3f590d754a2f34ac79b3aa6b382162b0d072ff8176b2799b6991eb808b9706a6654cd95a9e5d0fae956214fb13340c82e13ebe30c5bd8ab76fa554d9c0cc85c37e588ab402ac2859e75acb2ae46ca0973204d28a760bedb393e69811732bb060b12eb0a3d79cd23576f6e65b331e01f554505400d5a712eaeb1cac5be845d1debb2a13447cfc2f15b8a61890fdf4c300843ee0c630cdd1fbfae88aceeb7b64fb6638bdc9ea2c80e53e78700c3cc1e585519f1d8113f6f9b68e3e08c506691a0c69029864ddb78fa1cee907b49fd1bb743ee05e1c62b35020ef28562bf2410dd852cdbcc519c6610556cd22b5279cb18f0150b433005d94ea39353891c9a97f24c4343d51f8e5bd3bbb038a1d3573bcb706f218cc7cb29d47b27e6d65e5839483d779b2f29c4db6654d08641210ccaebad2f4a630dc3e49ff93b5cd9a31d17ffb88155231b1fcae2012964f4abb34349224349fc0de7048cfe5b0b1fbb3173ac865c072976944486b2b790d86fec9e15d4a774a38e283e77de61c86e71016e85d47c6614f3095d36997629fd16b904016fa802376d045172831ab29e4bf74ab028587fec9a654faaaea3d3349538820d3eadfcb26c24b9ebdc977c4eea15ba6192677dcb857b78694e96c26a2ced146523a276b8b3ff99eac6b4cb6e4d0bb758127c518790b26b5cb8ce1d702ec47022cb7913d3375ef3c9e0de4883484669414ed974c1487998fb37fc15d3f4fd7b66a937f6b2b7cd49e13adfdb8647f7a6af5dc68ff4c4c8e1ae77fc7a075f6d78f28f54596ada7fbc054a0c8bd35e3eac9e3381bde2068154aa0d9143d5d1ce0917be11d874c47b99f25e020923750d8552cbdb330815b777dd1d81bbbe7281229496b495df01c58bcf896534b66fcc2c89477ccffa95ce8c43a364ee34efaac7c8edecee78876bd8d224275481cbd3ceb40400b17fce8691aa74bbc546f05217d21fc8d0f606ce43ddcd1427457c03628a58e04e47219de5ed42cfd752f66ba38e53639af22d3b36c18a8a7d24c8f93816ba71acc01a234c8a3bb93de3679f6234bae6780aa3bdb584db13ff9ab15f9f8e4078861c27e79eb3cfc0532c7451cd7c1622adec9493c1bfc1358fd8e4952ccd023545a8551cacfdf72db5400597a5e906e2e56a7a1ed1521be35aa9a457e82017e7575cf47e7c572d9f5088ef84eaa419bbc4c1223aed29e24302f0102d8bfdf27312bc31f1d25ab48b13c2474e0c86411de3ed7073603f280c2d2e7325fcb93ed6325b339455b8181eefb2760a9996dba9d3b8a08868311a534933d0f73f74be945bc0c935694dbf31b319d0b54d2ff28a0a51644fded7b61927a93223f049f0317ffbb4176375119d7fbf7df47cff1da68af356d990321c3cb77953cdb0ab8b5c9e5bf1a2cc9a3dc95f7e9edb15985850546c798b0132be917b013b00a00a907a3a4efa6e509aaef2ca8729e660db4d224dc77d32f6d1f4fac05bb936be247bde2386d0e9412e32d63f764fc55f25d67ec61a71d2dc8441ebe4dbb92015598cc55152870d740cbbf4826ada330a1b98d198d9de0c63b7225026f05e49e0ab1dc9df313952af1ad4a14c1b043f6983fb6e7d064bda30bd0d26b3aaa0fcc83c21abae2310ad564beecbffe7d21d68c293fa9688cdf567373602e11cc34c987d64811195e81e14fab6bdcbceabb069170342f62dafc42c560c348957149b0ea5d4bbbcd58430f3449ef1cfb9351a98d0f38b852a42593dc83bc2ae016fdda3c81bc8f11da5815460c2392caef2597e1813724de8f68cddf10f2885302c6560a3ddd5f2c3aff72d0b6cd1c70df3f83eabe7e4b79d1c4d254c4f0128f00d0994423d9fc2fc29d4f06f79e7adc2705c97113ed2efc643cd5e3ab9523287fc88d3e32e40f38118d7774d89375273c2938731c692160a01b70a0c1199377d68a8a407bbfac8f66476e4b7861f9aeb41bd42b64516d36542ac029835712dea4ec3311bd39e8d4be003b7b0e5c28ae43759e5c31492f0e425d215c9a24cdfca790d705e5098f506c9a6ede08806bc433fb18d35233c7d0a2a31aa1d749deafecbe9117661543ce6bd8ba7538286851b703122677d4827ed208c7822dc4c5f847a1f7be5c3c18fd5971e5d08aadb434c5ecf759387cdd66df986ce999551b60be2b73ca1635ce87704f2336f7149d348975f0f7ad3c7ce9debd9248755c779401db10012e6a391ea78c94ec3f993a4428a2141dfa278c169298d8545357dca7a681e16ea112d84d685965a80abf2de7ef83f942510643e564319a61196e2d5d209310def2cffb3acae42a96ca9080cf3a65f36c55a7de531d419cc6eddddb0f8dbf42a0227a9ea2a3c7676bbcee9c874c233e34f43b8f64d8c4bdf3534c1b0b5fce602c4a07c81336570544446fad87ca5cd8f4ef51bdacb876b317d26ce92f6760914ed7a921bdbc4fd153c59ffabc48b656a62444857932d9755c91a07c7ceb94f1c4865cb2ca7d38b9fd2c6396c0175d9e0caec761c8a3b19efa126bbf6a3c92536b84ea3cf674e49386b842d2b747649ff11f7d582e65e2378a6d1e5a2a31c90a3ac055047f3ce4a5a5092bc52334263219643345158f191aa05cf72a898af2e5b28ad9b2c97f8951fd760dd215d4ea95ca5afcb9f8a5ed3aa8fa46e7debc13338aa1bc05c441f9bec94b16fe1aae509b0d59f9d381072f13b781503d8b5f813f37a0a7e6604a255ab2d8556eda7e93911e2d4cd7381600af8f2d503e631d573a19158b005976d695946941a5e66560213608ea15fec51eac739981fbc2e9e17ce8e8fbd66560cf553a7210d93d2df27f255cbdcea8623699e0ebb406aa137445226f944e67d654774eca25f7228a809b60220ed4d3ba5aaf4ca5da9700a2ae8e8bfe9bb0c932425270499f8354110cadf8ead547d7094ef880bfe00d8aed8c77e1235bfdef8798eda2d1450f8d469f1e174e5a3ace844ee338b3cc473c5c16d5e18c1b96ffea1dc5e400de06be903593b0333d168c33973a9d870d53346ff5b4bdeeee742bcbcd6d683b199d9fb5a9fb2ea1883c9d9abcdca647d09941ea6cb54744e34b732e4ca6762235e75a536d597ee2cefdc7ebc15d73f357e435cf0abffc95c5c6cdef9c5f2f830850dbe07608b99299c545f7f315b8dee70ad4d4b9cf1a319dba358cfd7ef52aace7235fa9a6ea68b22f0c83dd91c8d92da103dca8c7c679038bdc3e5da7db0d2bf0e2ee3948bf8d1ff49f1ffb57c6cd4376fe2fd47bfa42c88e301bccd89d1d2ef9ea78177060e20c5d1d79d458342aed91a70993fe7553fa3b449de2bacea1ab20a23f23a45a4171fb60a6f9c9ffb406aa5d5664c74ebfe991f195f9cf453beef253f11b25ff91b0c75906015a2b326a7ae3a42cbb44329a6ac106fef68120a2d6d155a2ea8ebc6ae78e78fb0d7f6c2814123814afe89f805eddee29fb17dc035269d6f590acb7cd36fe0102010ce399e50fd2792eb23d85044a33872a4569a0fa0a5b77c29f86c1c44ad6b927d6fc991b2562eacf7fde88a01f4819c35b0de934a66c4e1f2721181e1a490f72488d5122baea900a5b414f3f0555fd00f696255f5a46ee6751f2494f05ff325421803d7c2d6d9395da7493de69e8ed242fd6496602491b53e5f7ce353ce8770fe47cbb310c6fd67f4d701567935ece3898c34f54da6a1b69d0565b2cf6bcae9459a79f7b00f8409928e04f7a5561acbacac0475f67df3e27aa339358969c6d103e87ce5887371fa0efe651bf32b6bcec06a2bfd2ba764d1b01a0506a0f83bb71590826a81fe674e48a43ea890c05ae2071add1d322f1eb4081532482184941a365a5a974bd73ea0bf3707b70a770983c18af3623ec2cf80c07fdc7eb348f5f5d80bc6f446fce06e5bf22cdac7221b19b797064d53b0da75fdbb9c3fb1811f36a4af3c24e9db6ca56aa7db21030e2bfb481995ccbbc03b13acf80e9672c1fa3ae03d67fa00475bdc87e88294601f789cc0f073199a996b1e33ec11e7b58b6831ba4e700405d56bf438a0247307c88c1cde16da7fef9162104e3248e97488a3810faa12c206090ef69f9ebf6b6db078ee3db1e1fa94c63e774e685860f60d194cd0285f6d1bcfc46c4d83e1e12df2735c361c89d591a91e5581411e52d1b1c54d940171378d5fd3eb15d7965cd770538d7af7921f807224026749994a7b574fab657a8e4f891e3f478b539a5d5f501194ffaf343b6a635291f1610b104773b2ac816ad04d9791157e5c1cf3dbd8d50a779d56ef81fd68a321cce3cfd8dfd5532ae943b9161112331d270e4be597050112c33f9568561191554b565b4f95b4de55f4575f3cd4af2d061c4d497f91022fee93adb9e4a03f7d968dedeb0e043c6fd0e3fb97c867cef769e71e24ad2c0ba1ab5a100ed1952e665c393705fbb80198bec3355cc84999619b89cd1fe3ce03470baae488db15a8ccd2f09ae0cb50c1c1f78daa6adb0d6cb446e7133df3d395b478210003b93d1af6c567e528b6f15f0fa6a638e8017be710ac3459fadbe4d82934f9969ab69d74485e7b065c0f80241b09cae0ec2f47790c932034e1757936703edc52b724bac4597d279f07421fd07c1ec3fdffa6204aa6955af08bdde2a0c89593ee3036751b74b2637647ff5135582e072241e01f157e4abec0cdec85d1cdf2858db9f04690ad7e63015b6f17a73f45d93d9b52d5f3eed839d15102d998bf9fa0de87379feb06897710587a269511e5dba630da5153bf30203f84cb199195a167180531a59c1286f0518dd393d12b53e461afba3ca6b349c602a2594c27767b2580e2b13ebb3801be8c8b0a8433817477e5d09734c5d31d3b0eb34a1bcb31c72d18c1f4e50a3e0ad8480f918158f01b0361058b41eabfb6b5a3ff91403b1dc2ebe00fb4e01d2ada8cc5aa6941b5721b343e0100b005acc22e1d7efc9e06cac65cbec2d1d6f497b84cf4bba1a916347d706175d167d054a9cc21212d9c8892c4727169dcab11ca0df6ba64666150bbe28c8c97bc5cc8b4f7509b43147b1791c4d1f9ef24c71f598b7b12733681696ded78c0ca75d7e6fdcfbe33ac4a0e1a7fad58ea6028ffafcc43d452bae6d71c252677823c1f013bd582401d887d0cca7b208483029b77b78343b032562278bd79938bf15e08d9cbd28177777f6f4f0d989705cf4286299df22cd69c73ed3df0ccd94eadc3538dbd5fb83999b93058a24b5b1595fa9296ef613a61feb4ebc32779ca10abd2fd3d7c3796fc5038077a652845a790fd2387b01d6b1d75de7f75f76607ba87ada054359eb6ec9cc4ca94ed93b27ea2d3e71b5a9df0f4b401d895cf9a1cbebbf43679a7d68f3fff9d0c2b3052749a4c613f28f58caa7f548f1a1061cc4cf3b540ceb1306128c366ad500c84c452f41bd58060ef1d2ad3de3b2a9f3874c6d3dfcc479dd7f83bcce429c77ef9e5ecef0c46595c5601df3a238ffcab5ef74996d0b65e99e38b44d0f64d0991e02719eba661a379edde560542a9f8649a7495a14d95af58697dd25ee1fd0af9b03952d6ea8ee6cf29a85edecd357cbc81aec5ba20fcb682176aa75812fc1b475b23529e1ce8280fe269a692cdf03607b2efdee527aa929ac28e43469df87686454bf7d2d1c0dba0942cb222da73b5da6ce87b6ba9906a3d8c56529e6939c35db4495f2bfc6e26e6dfa7fbc6e06e313db75b5ab08fbe5d6755d9ccb9472b493ce5db54d1b1b93fce6ecd7126e5d549c863669f9822794d1eb041a47906b38bd22387747b11a66347adcde722989c49d0f8b5142fb475d2ccc6a4df911485d648337e92d9a1ccbe1984dae5258178424634df2f158cfd777830f113206884b2aebb4b191571dd6777fb2cc8e37a88ec9f0845e0888869a945a3791267059e47a8a038dc18d7029e31f1eb8e940f8659c14f44d4f8bc2ae651deb74778c764358c547dd04bf35e8df53fc579b2221a48bdf6e105fa96571a9c60ecfd1062161d2730b68a625b0bc1156050285aee12fb4717c64a75c606b319e7fcafe90cb58cc133ce7299f286f87ce0018deba1b938b884dcbb997b6dda2f0025a2e5f086c2d857a5b9dc7a9411a3463aeb5337b7579da832778f210ecacd5cddc7c445a8444271983f4f4559fb90e8a8fdb05728dcce135312c7f08667bcbf270bb3fc8534389053adf436531811e4d10a654b55a66cafcada967cb8628844e953f33a6ea389c2c4105200c8ca893fa911bb47b08e9fb1e41aa3acee6e44181763159e0ef3f6868c3a838c895629ddd6cd53a79898a2a69bdf2aab0e5bbbc400de5d8da642871011d943014de63728d04cfa9e9e1b79a3403ab8a84ca7f9f31544d6eede2bb5d08ff3fb27b1babb4b45f008e16054570c3ba4000b0add0b3ecd434a5f1e62fd375428214e1965a9893f7ac3068acface85d85f6446584ef4b8d3834353a393c9d18599c3fe7700923b4162e045e97cc09c2de31793a4451185ce52e775d5335f0eb3ff8d2348935a68f3250279e4e203021940681f73061d299a0c357b8459793d33dafe8e2cd52b23f6c0307103f63ef20bd10f334c6159822e88dd7c09f4d0bb86453fa1b97b92ca9a3ab69ebce92aef3d99591979597eb3e8478e14ef221b9b293501a026417d1b7a2702393fffc810e8a270d41262478bf8c6a543267b8c900e3b84d535773a0dff6f1eb3fddc54b13ef465395fa1bb728edc2699c73bbfd09c8cb7921dad90ed73a1ee3dc5b1e09aab2dbe5eacd4a9e3467113daab29358ebfa8621055c83025462f25566bd34aa745963bf45f1aa6bf41da21e3d6559f0736477a18ea20adb783bc9760f8a23955b2a2015723ae6fef5a632d957187fd1e2f485d0399c3af78c0ce6301916c488ce56f5e857fba2fd0d3ef6ccfba0d8aca33618d875e5e32c62f671365af846f2edc508e7539a3646ce5174cbc334d54e3292080a03b32386eb47454b791eaf30fd990a1227c343906e24df8295caf11f36627de7e2ce4c8cd1c1061d329123ca04b5cf60ea9de0d531e47d2b7ba200edb1b17fb0d2e3457b9437b45893a3640c9766e9bf3f967b73680d55dd395b88963bb7357c0227b7adaa7b63627a247de7aae1585d74781bcf616ff6f597c91eeefd6439a29354500a28e1a80bd729dde163db11d362c3aa52df423c83a32928874e6736d1c82b6e840d62666275f2aa65dba653cfc93b07d5ddca37b660ded783cef4e646ca921924e93761b7856e96964ba06ff0e822c8df0649be27309682266f61026fadefb286f05b8bac6b7d0553736088badef4008e972a5f7e72ff60beb2581eb5213512ba3127efa7b8a40ce0013993bdb47a73e847fa956f7d0f16b645e6ca172a0b88b2d572407d8a9a870bb519859cc487ddfc169bba105a32827b0aa41bc1769b2a0baeaf941f318e2274b61900671e5ba977923e5caa74e956eea37d41083b63bf4241fd1e6a4f25f87e18dbb33cec59885623e130b30b46a1cb926120fe705d5eca25ecbd1af2607d7d07a269db6c49b645659e9819f3e80b1a85ea74d0a0c65856b195c2f966ea7ee0e6441e8d605583ff1b954e5851c9bddbd396db45035bc90429bf86524cfee66af5f425698013b4d0e1a7a914dc7c52ec58d4d6b8c96d9c71b6d4f4046e12a301d04c559d04dd836c2a108dadef0e25e2835d7203e3e1248f9c654b1c743f5a3c8eb3dfe31069f9c287f5da07f35b56e8ffd21d46476ce2a92ea2b2ef0a57948aff09f5e2ca3b34092f93ebc5ee5891f4dbf84eda4baa7415f5679b27c9e3c7617f451c11fb972499ece1597ecf75896294af94d92b4aaaaa777a785352d1d3525e535e98284a995c84f1b0c73326504e2c21b36f7f606dd624deb55e0289962a1dbc37eb6189e80e255c12e8539401eaa64c07bbb5d4607fbd988f187573df4e59bdc19b91ef9be5cea0b72357a9a621a9ca64be192f00f77f452b1c9cfea76b8682979f2dcc0e57c9d92b14cd36983f277585778f128c0349c3cf59466a1d217db81b00adcc47771474a91374e0c868d663c3679ce03639747b32a03fd0f7258597084a6ed873fbf17d84cf182e41f073dde5178ae71c9fdb8690f5072d3757f827617dced9c8cbba7b5dc42e610f126e908e5000b35b15691b7e043325877ae7cfdfd80a09ffccf219a16d6b2bd56ff297b3a9fc92a7b4f34d9b3dd7322609d114a57f17af86d94faa3f1f570c7ae819f01a497d4a153baafe99c79deadf5fb002ce80ebe79143b86f67915ad3e09b96112c5433dbd6982d07b8107fd25b7d032b2a0d8878263ce3d468e7939872a27b6071e44f593c2933f70115bde44300ad92ebcda7d07256c6c32556b464fcaf41cfd0fd88b380a8d28727528e1dd1ace2537000801efbd335e16d284aeeb1e835eff65cbaed6b01934690bc2a67678bb8f81979f9cba538923b5e80dccdce6dc61e9a9c9846757efbf4ef82fa78068c871b99765c43e73f82deab2887f2e3ea97850a173b810be89a82a0abdb974873e68f804ed6c58a3a4005a7df67c4ba2dc356a70464e40a6e6a71de4e7163f64bd59bb089dbcb06b9e37ee5c8c3ea39a9da3e577b2dc9a1c18ac0849ffccddfb73f60aaa5ce3d1a94f8e1113745ae0edf293160e7783c50dc29de5558b55d537a9a742fced78064f54f05fb101f24a89759285f6b4fef2f616b34292a89496c920d295e12bc616c304b5672b4a4794fb293f30cf3c3e28902553fdcd9a6b3ca3911ce78c47907bc96cc65e7412b3fe1bc9b1e7fa376afac2b45ff3921c325b5d2d183ce516cca364db9ed916f895c19398caa1886ad87e5ee649a82ca5d34adc7a50f4489c113e7893620a19f7e53269933833932f2a3675744eaf6818b5e5a9f5724d518f9c9daebf4e572fd649e1f0b67c5efd8936d0d7052615f5cfec8e6018d34e2ef36c000766f8bfe726d80334b9313492cf5ca85d6d4fad7b0d195e3f6058ea7019aa4179d544eb634e704433ceca92d4f19eadae177b3102893040a8daeede0dcdc3be579e5d4fc94e302772593c5c41448decdab3623581889bbc75a66246dccedbd30c89d8d922d25cf0edfc4a0c073dcb17df09d1d1a056782082f3814dc70ab46d5819bf5ca7b85fd233e75671c98a38028532cb552731bbcb316192f3358fe96c8bd7d6b5eba9ce982f14697a6f4bd13c20ed37fb735d4bb21fabef307939b858ac230b65dc45556e5d9c8ea5888c64c14da3b902f7f542caf53568bf0c769da465e1cc892b1c27abc2443dee7cb172ab176dd653b80658794893fbdd6d64454c1bd9b81b247c91761c5780a97a7b3c6f4e3d0942e9ac65045bc9b758ab54b5fdefef12036d48333b684313a6f8f9c9f9e5f0fee4ef077bcc2631ddae79a49044a06fff53dc949d84688c4c859b547bf7fcadf629aa75b4b5ad888534e65048ebfcc0cc079aba12aaf01b296dc4192992a65f0f1e271f7db10dbef37a170df3c7de32388763aba88e7680bc34f6a010d4d082bbcc4f3147e654e691c4194a8cfc78669e06285061dfebdf30e0c6bdbb340bfca3fd3cc77455d8acd1eb1cf99fb03d7be920f506b1b7a067e6f812ac2c03bc547a3c8105b7996fe2bd503bf8243cbc4140f0f84db486dd085c8eaa847cfb6d713669f7f8f1836fda7b95144b06cede86cdcaf87ef88f01a24d4306f52efab692582b25e92073fa0898db22bc507c8b90756481ebe996268d2b05172a52d84538d36063115a8cde3b1afc239430e9216bfe7166d56ca6c253de9eddb83e4c8b2926e42803b3766e562014106269869fa37cfbfa12dc269782816fb186516715bd0bb54fdb4b9b49cb9f3291b6d64b3ab9b4299302d2ebe1c36db55469b08727cae3435cf38c1eb741fefbf7c18ef45e4f9cdcdced10493945a904015e746306263605b0756a02025b9f315ca8efc990db69879f8e29270630c7ff693cad5d7385fb6550a1f2482723e2ab98988e7f69addfced2a281faf067e7c10e0f95b33517d3e3fbc0ce6c81b4acde8d2563149507d84693417e7bc82733811ec860f257a3515a45a282e92ef1fa554b793718a000a5a3de2f6754ee5a727e58ee73cbaadbba53574d70b829f5d0ccbbfb5bf9b400960241e13614e2a8a7b0623a870a8a936194a53e9dbbd99a34ece9fd3f39718f06cfaf8aca92c5350bc947a74b14f0373745c21e35b0a0f8f3c36f4830c2f58f4145b3d3e0c184f04d34432ccf8eee1857381cef23be5d91f3faf35b1ca83014bf9f8b3732d7ba4182b8f438cd5d9a9c5a54020688de4ee7c6b3aa273aa8634887dddd0240279637f37e524d2d3d9225be8d5e15defa641bae5f9493ba36f54a2e351dbde686c6f5f938805acc745fd5b660a9760534be6fbb4d6e4cfbbccc2cbebe03c6c4720fb09c57ebc6f55a143b320f0af85a335bcfd9923b4e2ea299a0a64f9f4216cc15601ef3ccdca7f1e9a5007bedab6bcdb0f3ecc8276db2446fba8825ef098b6834d003cf194e12c9749be19bfb3174abf12af27994a6f78d6eea2c9e7338341d91992fdaf25774e33fefd7890a63e3d4c6c264a7c2b604bca623a7096fbd222e8acd33f918ca26a7597905be3e4ded0e3c84908b9701c90b74824947a88951e6a5e0fc5904bc1faa5834162bcf49a312ca31f334f4e83603f60855910698b3000f6d88f5eebbe84cb2077effc19f4b5d9daf64120b741b8ba1a6c6939d5ff4727422ee2f939ddd38dbfa4fdfc7affb49a899c7a63d33614a8b3293b9455194ff72e809a7fcbd2f013445058b24697417817eb4ad3b50fe4b859b6d070bd2d148efd115bc7e8a7b09c0e57f582f3b48c41c40ef19fded5c5ccb78092b445376f26ee5097c5fd2cd19d03d5dd8eaa967c60e6dcd5e9925db9cf7aa4c99facd9ff6ce91163e4ccea6b3ebefaf1f76240d724c8c0453077161e08bbc61ea8c53bdb158c3a15d5bdb99f50100fbfdb88c9f2040e1bd4ff1022d1c07d2bad0b25844c003c55c4f54e7d5663e9f69d1265fd7c34088113d3c01afecfb026c9f98edb86a081d15eab3f69dac6af92f8a0d1d588ae35d98c4e7fd67349f1eca1d1d8da4b6f46de4dcc36597dba047b6bc8e62ca8d3dd3ce96edf8c1abd507225b8c3c3e51dfe78c7e1e425f065bbcd1ac36b82bd0bdc262e5604cd89bdac48ed864a50dff5e73da5c56148d5dba88b8a217953830330272910c757dca1376225a6b2437d6445f442f55125c73c8ae8b590b890b281c003adf3a9464d7c9dc94cc09a338fd21efe4979632ae30abf134de0e20dfc6769efcae4b96ece4318c137a3b0dc50a67b3f72efe79dc1d0e562a14394d671be5299e7ea2652f9e27810f4a41fa48715bc91aaf3efdaaa2577c51284e07f300128e2efdd39a900e37a001805cc08c2a054c3218242f778abf04c4362e08942ce551cc2aef4d2ba34dd10fa699141f4013ef3826de6af081fba7f212c217b91252a63b1145a210e91293ad2430e5795f2d688cca562b537a5837a2614d4182ef46ea27f33bd0c1f4bf9898ddcdbfcc700bf5420e09e9470587f1397b6e73ab4f3710f8d37cd704215d008966c6f1ae248c15c55c739dc78cbf51217910a2727b44a5806ff2aff05c59627240dd8250d1c76125ee330333f96f7b56ae2014a7c50d895a302af44a2f658bf1774d950dc2c5b4e68f29131ef9c96ffd8f1af33d901e0a6b1e3cbcaaec7661a48156a4644860fb6e94ea576e80f95b54266a534ac7b005e488677df6f75f61df391ae306b6186b6594c8929494d2577ad30b22d023ae82eb27b04637589c11f5ac5034f811180f3a44be7d01d55d2c504e9d2c19302e8465cd040beb71046be26e3edf4e288105abf8dc41a8bd41c2629771eba82b0930fbc5871e3152a927560c2ad3921bfcb22bc84aeadb756c9be8cd95453be05b8eda251158177de2b70c1c7d63b3dc71d88531400e1065aec6709d75f25d78ee6ab339ee4bd636ad07fa52b680b24d8884c455090b19a8beda6b8b72561d9be2273cffdb9c9ffe8c680d247abf644d69fce1caaad7a3aea454bf69e6a9bb18f7eea9234c79481a9d25a7fc5f78ab2c01cbed3883cf0d06cec230e96844fcbb9d67e64f5e3b6d03e46bbddcbd81c26ba96874fad2f2de0a70a8312b1e679a78d140935ef1111e1b91c8a92bd9c903118cf898df8c255480a23e775e15dc7d3a5e6f6eeaf538e9eeb3e0060a9b75549e9d0a5637a06eff84a7bc122dff2e645c8ff847dcdac47c1611e6a9f4a348d317d89a1ab53e24bc0fc6c4c9ebec70382fd9815f4f63489c24899433edf4c6f99ee6b5bfc563af114e97c531d4da1771f50e586eaabb9932ed70874b3dca52219329aac564c1e3bf5f86c2e468f18e65f4e69728c8d223eb3956e81a7e8bf1df5cba58b82eb88326577d6840a03e05e1d2ae186b340aabc5f9cf0ac1b2d507e25e581e298316a4b2f9417fa77afbbb94c56538dac2518382d31769a417a47bf0454c6890124b1ecb6af156bad575cc15ee4efa132010b208f04b9b374d9bc17208d3b4fb7744d1ffcb91c33cbf0042cc7a70138fbfacf12986d6c529dc35a8531e8319a63a5f21ab998ed446b77ac769f799245f82a21c86e55c86574d3bbdbbe00b7056dd6022f39fb2f17b312e3fafd24ec0c211df28586196753c56af9fe01c25720b8d1ef4968ae29a842079ff362aa0a7a28e6bbe80d44c30152b319382ae4b46c28f22ce6c0cd20fda20e51e7dd44bf72371275cff0126c1665dd7916911666fcba0293159b3c54115e3dbb6fe501a9a0e6d7d33e992bcd02689093eec85a066194e4d323da1cb11d8f31d778c3d463bc427d2ce7aef36606bcdcb33ed4b0b407a11796f01c18969af46b96d3144f849b7272a7623aa2b4ac222fbe1953fdd6f0df12b61899861a91d28eba1dbe13d84ddbaa473b56a615af763746f1f43fd47d5795f84c4148713fc04cabfc65f97f0e63cab51e297312a1c39326d82a054892a5beec069c3bc0ad28747cbadc811c177bf121ca55abcd15996da18f9c5f8965b3eeeaf17da6bcb4975027665abeb92597d798630ea2ed4e7689a51927e9991ec67ff8a57b845a89013249318d91da89602b97ff4660110d90f97a38e3cfe1ee16192b797f916e3db85ddcd30cac3191198c70557e1f6b67d8cf66355dfc96af3501cb83ae55fa93871af2e30b87667063c5c56d3d7836226a47587381290a1587d96821bb51eb16f5bc4fffa467ea8bb5c60362ed0e97f69535c17975c406550584de2b725b2669bd891fad2cf3ea1af4999faaa5d71bb1ecb9679b98c27cbdba402681300ba7a1e9ec4bd730499ab353d89579188373602a68974b7c57dfc02724fb46d618b7ba8b6a3a40b68283759c5a1997e030568e891ed37cb088612a97476643c5be15c9e776854665ccb43a9f35f2d0d77d2cfb570a4b55bda2115d05c2d0416685a78675deb02a5448bce563f86fdfc903e321c0d15474fd18d1b6689f7142e87acf52dbeed6b3ba78c1e386586b9113fce21764c5c2cd362699abda824edd0fd8b99b3657498ceddef6c5096bf6b3b5fa0387c83e752c4704180a178e56468e6a5d45eedde645c7df51e825d03c13f87834c79bbad9b3644b74ee5664a6907ae9766c202a004f9d9d62f55479027612f1f74490230f37dcd2589bba90c197cc5b3c5eb5c9b2304c0a8dfeb87ea71276de49053b61e445c1e6ed04800af56b4e96d21220e6d2c461ac19ea59e060abe951c0f51e067fa86ce7fce1d146faeca12be56c7168747c8c1a8d8366178825699df9ec5e0e82a1ad16e87a2c700411ac9154c9a044ed235b965a3d9ed93b078a162971d76b51cd786f991cc9562ce5c6066ba6fa9df17e936aab1e3e3d844d450ec133f4ce693bb72319e1e902455afc61c7d2510a91684d4542259798a437a2bff661d383d98ac8df2fbbb590bf4a1881b522e9cc92f747f873e1df3494bb698d747e7c00f6080dafdc890069bd65c32d629aa1aec5a2a03a779fb96e2eb6e4355e107260bc17a6b2e0c1b2615468469cabe9799afb699c9719a7f9bdc6812cc13e8ee614ddcaea840cdf93e5bfdd9d0bcc75a963d8aeba31aed11aa6bc357b9028a9bcd33bed26eee5a269f889a9491484e755b5b3174331817cef4281588ebcbfb677a8ad619e95b3f8fdfac54842cbf5783e6301ab19045508dde8e0bd1860f13f1f4114689358e941348391ee682ccbd0c799e00ca6396a8bda722df63ea73f34b79aeb80d724dcc460e0580471393e265e9c45fbb93d6c6edb08aaad09f4da0e5572d40e495fd717d517b7ae04cf6f89f3c28e2b39895507a5334d5b648778595c23076362b536e7f68ce8a26819482884c2809e15146ea8a7e94563ebd6ecbee561464b613a4c6d5b7cfe9a41362938f32817acccc085b1765db7865842ca63b279e0cd5fe17cee291e28f52b8960d674a3d610088cb19645a5ff41cb154fe612a543835e03f2717436f8a808fbb5e80a77baf25d7c8c6fa4e5bdc15d67b5fc5490c17d0f9f691203e3f35a879e4088611972511bf720e76b233aa369fe18f983d4ef3c72cef59f0d0c1d16f1a48d169a885ab5dc645b57b91eefa8d29f21905d1a838f361130cceb0e3c7ae880e902bc2a8d55b3eeb7daa2b14654bd517984aff30b27f9eaf7253299d72b57f3d0ee7b36b4deead141604d3e6772b02a4b682c8d3953f7f7fbf4b1f79b1ce8efcfc39f66338bc57aaf14d8f33cf7997160861756c4a5d8fb451be17074467ab4dcae903fa15de7b3f926871725770fb2864e46cc51c68ff5cbc3ee5055cf8232dede457df5a749bc69e251995f278c310ec26e87f0d449a047a9f10c9344791bdcbee81317f968b423d6bec75554d64a517f7efb0a807586f6f2546b3af93ea7d5581d2daf3de64a2fc224336e775eebc0a32aca2e01ade1156d78b45aa72cf20e798950857c3c481da218dca802404e4bbcacbba6c1ee3e1f3979dabc51ff27995f80de90557fd83d0e11a5c59f1c19ac42a82cf207a009832eec176a660d5331cab2284acc1355962893a5d7c9c94a0692de0fb71a7fac5a4162157a926660f30c653dbdd1f738e79ff5a37400a4a9828da2e9355b51f21499b9def53038b9b71944f6da78df0f7f6cd3f3f13d461f7f02e566e6fafa77b523b7ff7e01ae745f5f8e2d1d132086c44ab7c859f0447bdf3c2717e4cc9aa2451a25364109cac3f5e08bee7580702fc6858c8d9a98eff9eccc1ad77f2fdae1b7b1595df4532a1a016b7738284af30cfd9cebd8589cc26195974721ec23276420cb829c3425a7ffbc3eedf49df53e55fc492fcb5ac001bbd0f968bf349dfafbbe0f0132c42d1a4d0e77479412a34bc916be335dd5e4ad3bc23047652c4b3d57e2fa0375870ac57cbba4e2953fa8f28bb8c2f61f6bc2d7450b513b506ee7f3dac83838c84114ab86f46e0f6ebca4dda9d1f35cb6160b16fbcbcb6657b28c46819a338fffd6067e1e20ab679e3042b87e883051889976d692a91cf2925e9d5f7d25bac7f6af5369fff279092090270bef0628095c0593bcd2c129c206d6be60498b3e5358faf2bbd4078e8f9995d97286b4a51615d8a2b0b1bad1270a1595833501318a8d175075f47d33778ac7abb9f416170700802f7d59527d83967784278e41ff96988b3e1a764b783afd575f3652f2d5b3724a535879047ce866b0420f5c0730fb8d0f4ca00eeb5659c6d70300d188f9430cfcc92c78c95de00f1d9ecaef16614e976ccb6cc8be24e345fea59b16780de0a1c156bcd94722de15fd246eae97e5cd55cd68eb7de1578621cf0e57a4d7d2eb6847b49fd2e51f7dac5c67561bbf3216cd417f8fa5c4ccbea74c86ba89c1741b6ecae52983e5ff7f3213afe1c1a31882bd51f6db783264808773182063fd4e75c6d2d11685fc98abda545d18adc947fcd901ea07c779ef23d4d2169b31e462ac6eb9be5b606b2e840450f583802a712851df73f6e244f7a85a60882e194d2b01d3f9a46e674716c56c4ca2f2e2d9f160bc3c1801eea4b46eda472769c828d82b739e9881cd8c879da64e23f641c6e99ab9967aa17253c087f25da7e121d20802423f652c9d2017d7bd7e1ab85749cd3427f3ac07fed71d46471bf5e7cca2246e83d7dbaed06bfec2228b44a1cfe00d5c31a804d5c00c240fd09edaea45f79de234fce08552619e6cbe49584657eddc6d5eeaed623a3f164f91e715ccc7276d1f70da24622d6f276111aedadfb31ae9e6fa1298787eab52d6b4096a69d831eebe894e04cbbc54d57488e9715d4e69758df4152ca73e24bcd4ba1b92f6993a2262dcdcdd1017d3e92164dab4b3711edf611eaf173fae453dba673a71b78617791cb7234a3e258bea7868aef382822b09153106cacdb8e584a225b0d64d31a25a0e3094d7cd9e47d43277b9c8d50115a2c3c1fad9be9eae3caf5d732207049e562e8be1c1034333c519d090c450d5e643684d11745a405962d522328088bc84bad146623b5f2447fae6e4e8bddc6ff54bad5b57c76c2bf8556991be2f3cf58b73e544f7fcffc4e5bf981edffb737f483ef1e7ebab088ca461ab94d2f0c8c3586603da7d57a96b57800245a88cd14bff812f36c66e5a7db14d36bba046b17a41b6bca40e3820961ffb24cad609d304ab261bb56175569ddfdf3165e77fc87dcf8dfbfca9e78a4d7ce9162b569deb84763aa1097205509b38bbc79908ce96e9d85f7bfdd94c248b39bb049dcd0fa4fd3c4012fb87601d6500336e4e5b7db83b65397ea160998501186b3e5705e09ac143d6e3717dfc96e1b6ac1de0b24927f04655baef7178a45de7e497f1d49a29bfb9c4b00cb6e391459365d35faf4d53fd85e9cb20668e43aa6c4dab6f7c14bc8078190416897e19161bbb61fc6f7fab914645f9e1d40457e47cb1ea0856b919822b09f5eb54a1c9f151a7dd1dcb06afb5a6651385c14782229d7cf66472a91819ec8ffa2d93b855255a680debae1273dd1f6083ba9275abddd50f4f2b6a6f2d0081904b3c97ee89e8658fa1173fed2cc494f2f64d0f9cf05ed5ee00a1d30fc724a6027e50b1ffa535c22a5db7e8badc1db8ce6390b3b40868f9751a60d4f25bf985a9ba42a69fdfc4774f253a39480fd53de5ecaec22fc7b55fab613ac7692c31739a8de8d7af25746a0f8c96b1a58507be94ea249d7dc8af9078db3dcb8d842f846ad3225604149347213f3aaad37e974c1ce8e138648701cecc31ba53aab6ffcb138a5de1c6a41cb6373081bcf3c63c9380550ef205eeb29c4f1baaeca2e3987c1e3fc1f7554cf1d4aad6b273df5ef5ebfe6d1a6674c64b2369970bbb6ec0e693a7cd047f3c389f65b7f3c0939304ca070241849a331fce011696aacad241fa333e47b1b72e509e6c2a898acda1cd4df91126ad81a979040f8d0af782d176c1b93338d9c71c2775dfebcb91cada3e69016dfa1e87d5494d57d5afabe76c1fb9d9c35d832693d1afdf1f6234007ba946414c9a9dbd6ca6c3a19c0ec4540f4e3157b832b5f16617d9709158d59c088595c1e030c3a5e189c8655092b13d4b0d5906206042ee6969a84e6b3326c089c50b3fc18b0e1c767eae7a2927a69143f970d76acde2a77c2c54ad5e6aac985f81b9f39b5acdfd35c4c5f2baf790a7308d2e27d59314897d8000918c8a7de1a2419fedb2474c83df938da29c4167f116b3faaab9d0f5539d0bc233c9228f039282f3af78292b35957eb07725da4b0de7c6f2bde520f0a58293eb8301ae2d8f4f54dd0e0bb2a39a0afc31fcd9b928f57c537412a55dc00801f2a28b4d86257d4ccbf34312209b2fbfa2404606f9ae159e1c6efeebd7ee347fddaa6538e961d3bf167c1da4bda528f8fa5f21f1e0bd17a68e34ffb4409fb1ba8d300efe6b0aaaca1d9c3891f22a029824079835794997747d762eea3b218fa3b9c7b7b5140104c4b8d60c1c119ec5cbf32bc6dab41534cc7f42e4fa2f786a44e380aed8fc8236e035897f1b56bc3696ce558c228c7fc657d7d4cad524aea0ee48d004003f4fabf2aff65020f447766f813c5e585dcbdacccc53b323089a00b4d405a87b8892436e6a99397dd896bfec840e764eb77621826c1d6c530ee06ffbcfe4db433e614bd70229dd2ddaca4da8b41ca414ea6cffc0a167b10ea983c69c9814ced49c7ba3623d92b73086d76d850ec8477f9aa5fbdfd976361faf927d95ded34be6893fdd475909c41f264bfbc3a32e92d2496d0be98bb3a7d47e704e44158e9ab5970cebddbf7a65c75928d0fe0437d48aaa845ac506381762da5569986a16790991987ed1822a9fb7a2c710fa8cf8eff8b336ee4952ae91b553dd4f2e1c3df5488a8cd517374a0ee55c24b0b72e29e12790c94d9bc4e8564ee7be97bebd195bca5922008f73e31a1259306472edf923ac4a503f1462d2b8f7f933977f1ed6152fcab4d1ad53e03cd19d787da8c65ddfc4b6db25aca7a4fa156ca4254e58a915a312266f4ab3fec32a39d6dc05c0972aedf0e799ff4ebf35d6d9d9a44c06671331384cded3d68f3e8fbd0c1e98e9ef2e89822777a2ed061b481177b602497bb6e5a469b85d7deb3b2566f82d4f21fe1164fc9c76fe9818250581ced4447e98e8527c2e70ecc6efe60f7adbc0771d31b59f09377720afa4d2f7f12554ec5620eae9c8917a8ec6e8163378016fd1c61221f25a3ff5ad05c201eb9c3ebd4c3cb9f4eb8cc9cec56036b36c7ebda2e1c2f9512af0544a00a4197f0e4d48823c8da66280ad33c09120f53e43cb43cbaec43c5980e09f110c515e9d8c743acd3d5129416678fa47e279ed46aadbaf77f50c6c1e4c5da6dbbd8a567489553ae3776c324cda6c53a70f7d166a8630c3f36ce7b58555884ec5f92eed205197f8ae09c218554fddcb5ac0fcfa57f2e7f43620434543b8405800e8765fb580e7e8f2545081b2713837a98cd7d080f1cc65071b34c8bfc508ba8b5f8171a8bde3dfe0e3aed5b2382a7c0e708ef1149b0f98c8a7fedc64a757879892270d2a7261286df1dcc3a408dca9c249d1ffc1c6f572b154e9cd747726537c7e20e38d14aa26036de6fe5601f5ffcebabb187b9e3d2cc477048dcab1ba57f1f2448dfcc2ac4d753819fb6c04000a418e8cde3e8bcca2fca6656e664f98e14403a9b9795a9bd23a3f3d6f02577bf376764d54d3b728a3c9206441da25da71793f804f85d1ca52e462891b9ed5769fdfebf352c8e33a3b1a7fb173d636b6c2d74b05c436ec012eaca64adc2fd91d92302b6e3a5b00a0af01125d7ec4df5c4fadbc7bb26b46a9ccef428f48abc525386e8e957b89c0b2f9bd7a1753d3462145f048e2743c4d0d610065a580dbe909784738153d863c7d296e8565864069a6ca84410398ded1e22c5ceac604badfbf87e289d9503f66599874c0686b0395ecb36dca95c819240940aff43e3c694facfa320757fc38b408d39e7030c15c82d8097cb34f93d676795e16fa8d484c7c4e650eb03cd219d7ec59b679289846b68b3ed67b15c7a3e652a9f93931cc6986bfdd1b63ff9aa7c3d23c2fe9dbbb3b7b2397b5cec9b4193181a5eff6a12627ae347a14b458a11659e6584b1bb1c5e98840f49002b002c0fd7925a25cbd7acb1403ff7019a5e7fd15c2d295d594cf9db65309363aeec0beff17981360d65a056522dcc5de1198361d5e91ea95161fd052cb2c01e76cbabfa9dbad7a7f57d032c3f3548c56ef0122ae783626c164424f7c783c06788b7eecacf4cdbcbbb3bd4289cb817e0341c44723b8073b6e0d60dbc3c326546f6a23eef5a1dd43d918aa26616146bc3047e6f039664f35d3be96144d570b6de2f821e8a21f9639fbc069f8c1cf819f85f0066de6b1a75821f7bdc521c9f8c74171460c80d4c0754a75274901ad1fb21de0abbf30d406a75ab86f370b9f87714520f6df2635f350258a8075a8b81060b3207c1f4f9f806ef2a0ec3e67395c53eac8b2e5a88d1ed1c6cf414b1b501893170dd09ab6d503a3d8e35124f5ec3fff9becfeab5673c0017cfb904933bdc34520ea77340d5cf502e6d4ac7eb062e201c6ab6f3358b7d826d0067a0093bc2b67a25cd67eed0268aad4b803eadfbff2ce24cce105fa1c6b0fc8eb9ca04513cc6b56cfb1d2893ca0b906573e91d89575591e26c98985e6c6ba9d7401e238704e10c94b7cd0a96dc5a3e02bdc4d535bb0beb635bfd90c66fc1b41a7ffcd4d9b4b04f1dbfcf85262d6a1af7ecb1b46627c702e9bf95b89472029565c59682107aa217b8f775e80d9944a7075d3f0d0cfb9dc55bb52d8c12249c6bff45e0ad5b6e12da6f65476c331d739b9920bab89a4179c79374a1d87a3ac3e2f92c79dfb142ae8355f5012b7b08d52c18d7633a6dda4509ac28d2a1890e67dd1551f7410e07bb5ef5d9db2ba8bb56c278243395f2864aaaf5976c1b16790784de65d71149774f459ff816b6ed773677c779e23e2e62aa665684db420b5324f206b524f176add747eccce14d93afe4e930f4af78a24aa630095790729c026deff59308772a23bb5e66a0a244dd97ff344965e7d0372b7c8b3e63520283ab26c67b02bed61cdae6dec1395d8e63d56fba8cde6f35d6a68ee2f86aab6a3943abf23681b5874c0e09a5c796aadde4af2a4dbafef514d991b8b136f517800a457d6a7e52a00bed9e1997e1dc81a915244650f97aecb767880735ee4997cf2ad4e5a91c7a4d2d22a95f0342255c7aa9f079cc296075a4bf567d8a84b96bd292f9c6ea6c675d544ec232cb8f40c205ee966a6674764ed4156465f9a822f34a92317c341fd4295bd16db18e57471e71621e96932b13f21c259c0da12d2b9af3a802b86c0552acfce97828ddb56267f3ab9d1c8922f747da64cf51c62a4341496327cbe69caa9c2d26b30fba237327e300533aaada20dbb3296f24204c74bec38abb0b46db86d7913450e872ac45bc55039a0ad17a2fb869920c0a1d34d3c2488e151486b65bed53e5ac9a57ef44cde767b7e04ae3129a5266428e3837ee483dc221416f43c135ba2ed01f164d93fba563d1d21346b67bec279652f44c7dce87b350ec96bdaf57354814627c6f2da549c30805cbe36c8cd1bec2dc744000962332842c9db1329858805d854e165c99332f7f4c757b1055661ddadfa57b65d3389f49f335cefbd8f363fc73e7ca3299c83dbe2692e635bdd981e42de7ad07244e6bf38b990673859048d42518e84d5484aea1179510d407ac72be1d1baf21df90a713f1ad27ce5f7f943be1a5c274e8632ffbf067cff4edeb42e0b8611c5083852c7549e6f9d49754dc7d3ed5a982d55d99afab16723926eea8b89ed052eb40d44972d9ac3f81be042091517ed9e72e493d8b194155fc1ae3c53c790428512b109ab3dfa4ae3a28edab94aa8ee5869202b55b91467d2e20e2967332afac090aec266dbb5c597b7c8313b89350e52cbd61ac49a42dc7097ad01d6df30daf60b2214e7b6c7626e3390b26e9b5818866c7149d65b1b77bfb2a892fd767baef6685a53adf4ae49567345791d54170fff544d1ebc65d61d7889c264f30036fa484c97c78ea0e40679f30f1205f5441c2f2e2d13a5a1601ea1438a17a6ab758b5ea2c4d1d5c0d46d5e0d45a2f7fd74ad828d33fc5c03e48a16417c0dc065cc6f6c8bc736a624150e270dabb63c496ad414d3ba6aca6bd54c876ede28f1a78aa1d99ba61824b8a41411ea5389af55b7f4580d6a20f1c58dc4d2358ecb1dcf1029ace8b55cbf858cdcbead906c12bc1e71657e2e54c79b66677b769febb7e42b83fe6b0bc6954598d9205b76b6acb14a0ea5d2f420fcdc8a050b4e36929c03a5222f332797f7b8cbdf6f5427a5ee615f4671637de2e71ff799c755c24dd5482fa3bbcd04de02d5aa430b491035cdc75d69875d98eddc2239c93b41965bb0f92a3acc7542856752794b46fb9898985c399867d62e742dbc11123ab43c5011186ace7404f6213e89edf0eab0c394dc161d44e2910375f5cac5d084e94fa8429d4ba869304f3e1ecf49d56108b3dee303db0d3d74629ff3d0f3edfee2dff2d99076f7ee4a6cfa2babfee2c116f414097a685261d44d4176d2fe21f87737c90ad6c083e826a6a9a9164d37b1775d52ebe94de037d6fac6ff90f72a9ee934e3a1169b55941a92d6a2898c8d4d00dedeb439a653571c448c19e2bb0c8121540655e2d4fc62e1eb550866385d845ef39e69088646359a8648f0ae338411092295a336c8cfb07f0d9cc1e1d350dcde970f2b03a06ee82b7f4f1e92b7497a111b85ec4e13dd13bbb7be5a8f02d2f88e212bc4e59db0ba437406c73dda85b5ec9f72fdac9421f0e9bc8780f18c8635ede30b5f8818b451bea7f4fe3d2fe95d91e18e4dada03a40e1eeae40d2c0c95ec5344546f9314da5e6f5b53673c50d7983745e883fa6561c5f0b68ab38a83b8bcc7b0944cbbb7d46eb34914315f8100f991be2d57edf2cf796e0ac679ccbf496144a99f20a80281423565f7d12861dbc3a6685164e3843bc371778ee19976cebe8206a5c8bd9973d7ede08a692d765a7b165cbbf3b23c59a0e0dab275a4bb3125141280aca652c0fb6771af257b35a95f00045acc60f8518f225287f39a229bf676b2ea115549c83abec476fef241a0fda41372141e8806eb8051235f5d5e19b0faa33bba04949a72266f99ea4c459f446270f817a3dd1b36c9d35888addea88d779b4710b696420d5386cb208e4e5aeec443294d5a48e5171faa16ed15f51771bec2e92cc7dd811f07c9f1e7ddce14aecfd4e8b20c05db4c35843e060ee64899434569bd307f817c3a8e34afa6b5587b6885d7c7a7b139a149d489e016fddcd0e5d0c62bf77e80ab94a3c35f52efbbc5dec264635c551b36f1a38b88ee9546776d519d64d1fefd149d0f2caa0bcd69e13c2eceb38f879d264ef5833db03fff14f58e9a9890ab5b8767cee2e4c0d4d32f9dd9b4575443fac1b27ba81c95505408aa1264a3762fb35ad4e7a1f36b2f2b27805233fd2045c8456de9c1f58f65544f1a52692349d45e7499349288015a7891980b76f18f4ec8577f295783a13d88747a151cac9c2d5e5827ebb7fbc450ffa41644b6f34e4f165cd780c87506d8d188bdfa2e036ea7570f45456e8465f637ecca4f2453af849084beb6c6b60b542086516648396a77b8eb2f8245e16399fc69f4090fa6f7b6fc847a85c3ea29442ea82705d3221abe391d77a8eeb677371368f3eb5ae6bde8c80c02099c0af36038a3d611610266f4a0c317e5ce64b809a6adbf750a6c117bc28b876f55503eaf2eb17d254ddd53dc0598a79198cde2467f1c0f84a3f66c7223f54d658304f0306dfc33f0848fef95a9f3a971c2a3a60eca831a9ec5ac0fa25e69975361b144758557afd18607da936baf109552ebd32b3d29340fdca05c30d109fa5ad8676cb8ada5659561bfa87cceca66a7223dae39662eb73eff8799b2b1bdf486719ab2d167fe52cee94141041d4da8dbf1745d17fd229f021469028425096c8ec88c711fe5145fa5603b7e25b2f0e94287cfa79cd2ee6d65336496fd56ca13216a7fbbea7341ea08f1fb0fc4f672cb29555b8b20f44b0965753d03759e6dc301956c0cd001fb642af31cbbed1c0bf64339693ffd06b63585d0f325861800ec505a4ee63b0674af7451542b7f7f017208d92caa5f0ba65ada99c4384e878972ee6bbe2f50a6473548dfb55beb12f1190569dd9781431dd5eb7f42f56e02b49cf49f5e742f68754b4608928efb6405b9dcb0f83e74345f4d9c784469acb8509b1bee591d68a5cedf87ee0fa21f40bbf52dc8c789c58172fb513aeca97f62570b00166a482d168dd2d96796ceebddda9b6aec7b7fd41b680d1551a2a6669ff4fbef029d74c307c8ad7f9e6def1acfff8d9562680be489f2d99d11e165854e5fa9cf1532e2569fe1b55936fee8a775f77092f93d94f39bc7d9cd6119e959f682252dd61c1d56eb1599b1b63d33e40cc98f8213a625b8f9c21e50b43afed6dd0cfd5c2b98fe16e51ae90714a031680dd63039a0ddcd6dfd49b0fd384e347c032e28bfb0b5f18a93aba62bb3d0534acae0c9510efac1843adc20874f40e3af41058a20fb2849d26443c942c8ceb94cee7d054ce70e82eebffb4172d8e7c10a3e42a731af354dafbf34217dac2efdc3bc7349636052254d980e92d0d93314215e6ccb4f286f3a31a6fa6da710a914cba62ba37c61f6e4c90dfe95b0bc7c89313639630848be35daa43f1b7bb21b0aded61e2bf31b01916b849a52899b15fb2a3bfe0a7b60b4529fe56908e833c46b59fb75b986ed468f1e47572d3b85c6b9a1042e21ee01a29a689baeb05b7810eb433addd758641fbd50118a9b594cb05e66fc40d6b096ca6e87165dd6daa2e2ebfb471ca14b94adbb1de9b8e72e176436878b6cf2acba433d4bf561bd554dc14c66eca43eee8c2eee9b3846b9149957a480ffd8420261cccb37f2f46c32213fe864f2cb21b6d2d986962b21105baba0467a0f85eaf2f691fbd4a501474bdd8f20696b83dbdf0a7cf797c0f157c87e56e3bb0439e72ac02ed1ee057acf82282d132d371f9e5e99cc86d7fa1f60d8ccb660bf55b4d57fc81d77fc95986e1f584ba4802694fee63cf73a9c0f5c25c08681c4c9cdf3240fedcbc405922e2601ee902a276178d91e051a58c2812c8b77675dff929ba567509c35564f5f7201814be22dfa7eb9105157873ba8316a5f6912c0b4d342fd46abfa5947f265d2966d12d7cce03d4dfac03705ce15fcaadb45ecfd72384f049bf6831e52c4d78950ac65cfcdde2b21b7db835a7f026af6b765e73a546828dd4771c2585aec3cdbe8db5ed259e52cb01dff517a3f24efd4d32d5d56bb21707b4f60d4c2776cb4309b05ae2d681f0252c0fdf259fc83e0f1645d31fead5434504562d8b6bade70cf03425b7da20dd3470fa6088910c7375ca414ccd4f6c0f26bb1874cfcf7aa731b78dc00d391f5b611c10dc0efe227f996affb6ebfa1e4d351bc09029630ae7b55046583a625a45c3730767c2145052495ddaab1ceb6b2d4ccca14d00afeec1e02bb3246505915e1a6de8d4c1df18e8b8e8afe480588dce6bae7e49d1f891005a9f90b14379ce493874380106f4e740a6767cc3ab4f783ec78d5ff4bf46d3e767f1527ca609b24f3e14e2af0ee8a9b68c769c36487f8456bbe8c5373214806be55f3533ee9e71c2ae709e48ef59e9ade5ccf85e658fe4904947ffeacd56de491af6558764aa2944c67b413f3b4df83cf96a1ad1754de05aa47f80306f3917539235fff4b71816e65d883c5582a5ed2a6d2ae76a98216e04a6bc06670fab71d4725d356bf050bd7ff3f3ebe1fb972a46c45106fb9194c4b88e5cf08a220c0171243ea75f708eb56afc8f1d9fff653ea6e9ce40cdec68c6dbda608447829da7aa3415165c8c3f66350434bf421e3189d12b919c8a634a2f9692d02af6e374b1ef5c62d4e52d532438c1ed474cdb3e5a63f9298c9c61337502b72a38f69363a2fde5f82af25939ee50edbc48e6946745b9a5f640b8bb6d505516297c96f1dab4ef3d9e6f3241f7c5ac056ffae31e2134aba993ea0b77ce930ec935f6e99e7cf257d762e8cb88e4210ec332e266202b19aab73f0dee24799f0b67cef2d51b6490cbf388fef3f969decea6cbaf9ce6e8bb3f809fbbcef734c06b3f8d09ef424a52413974d88f3ca184b2b317a76fe97f984ccf390f879de96feaee333d31c8b0272a9211a93d9c01633adad6d1b3d53786139fbcc3c0d23bcacc8383b2ba23fec9aba3a665095189ff081f8f72ed55bbdcea33130333e1f939234df7e022ecc51acb8620a6fa9f1064d6fb63cbb2baa786c8bb5b2b7861e8f4030e12911bdda4dc9d0c6e86f6f5c0f7e49ce1a3efaed3e7af9df3adb8ed7dd0702fa8fd244fcae757da4daef3305d05df016790e9fb0f67d6811a2d1895b4fee42c325972bc23ddd49ee03fe58e6cbd9b911f34ff35bc3b46a43b82681e552e56178948a7fa8c81d7c6ee9a3c5adda8548f65b71704bd81de964c326d00325c9bc3d1787d49fbbb6203c6bf759b47fb5a16ed736ea65d3369f8614f0a920309c533ddfdf0f30645f2c037f0d5937e54b97ecb76456b3d9f4944bc10f290d104b1311f4f78e91a01f15a8ee896a48d0c4567333dd7cb5b837c5e6bf737e9e29c39641a6e1b3e0dfb3b494b73f9be7e047a43479fc4afd9518f90c875028e656ac340a38ddca86e5aa438646fd04f8cae43deec1dec6cfe1ea1e5fbbfb3fc24d7dacbd3f77ed590397119e76265582f44966d51e12575a17fed2725c13c2a0384b85ba1bfd62a24ccd93ef5f9ec7f4f8fa34108a57dc57374630f265dac4f34ad6610bff2e4e8004690ce2f2de1b94ae743eb61ca256742c7b4287dc0ec6061c764f85f01568e58ffb375f2092aed35eda0d27e4351088ae8f1d9d97aa29eb6f4c728050fc0327e460068cb2de89d7658cd559587ad58bdbd937d00247a3d13d972fa3eeed0593d5e4f5fe11276b4ef445cc1b8a5e2a90163a554a25538bc134b504b2b196367338daa74e8a29aa333dc3f6da3c8269a5872f680831075369d290d33faa8f5a84323a93982ca5fe50e9b5b6f1e426c4d40b0dc48a8d52b259351191c4b33a365213004b4d9b2f263eb0a1870de991506cea1f9c8e32d2700d36d478c4508a38b17bf60e1c99aa13e3a56959b3a8b5184c44cda04e81e76f0c1d15e8b48cb406c5b4f04fb28e0a480c8acf94c8ba1cb4851270732feaa1111c78987b3f303df9a7807611e4a5a821bcd658fd18a212d2ca0b7ac675291c353ac450ddbcd4c5a6e6323ab69c504fd601064c88a261d13dab15a4b4bcf27132696c4c85f456d61504f041c0dfbe773ea081219a269310df53b2cddc29fb0aeb2ae8a7d87de6c0fee4a9595848728d5b63dadf7662824d48b14b71338f822f965899f4b7015694b414a04fba72313b42351728e6798edd1c69136b186f43569cd534aa8548aa02b66db72ac414c2880ebf02b057a4fbba18dafaf82cf1b391d2f2ef5df6169b5d26677372903b22b0836a14b21a4310a14ff0f052e3f206c46321b06398973636df04ee6e186b74ec70cee7f0860c43b81d5a4e0a866dd6f2ae6e5d9b036989d8fcaf29418f3189006af06931f18a339cb2203cc9e8559eaf960d79d7093732d89cab131011f6e282b1eb19e02856a7d1e94aad73c1c2832368be4b4ec83665476487cc59993a829df3c40024e8312b1c6b4b661bd676cd03fd3a48a8167a77786c25cc890d3ad0d02f1d013437e5945f30c82988e836cae38e50f37deb02321c4d2aa8cefe992c63f00ad4c7e13af27b67477b89d3b7addc17587a3d3e3d94ba9152ba41303425f884298feaa98951e697b6a0ffbbf4b46241cb5cc05186973064758d024c28a2bb3842572ab1e498bb8414bdc726d01e1ebddbe5b8e81d106e41583c623b7ab5024e6a7f6803efee85eef2f93b598a5dab67653e564a0f2b3370eb621ca1e5aeab58cfde6b51f8fa1fd1f8c2ef647cda416083416e9f38fdfe0248acf8c55f9c07ba8222d4d89b2368d3088a6747b52dbfb26a033c2c6e5b1a393a944eb54e645fa19aa636808bd3fb11171a3e5865d43506a462d9e84c9b1305bb3418ce3654e85dbdb14c69a1a5e1ccb96eb52717cc041efad25eacf4bcc7acc67d5be47bbf67f9d293ea4e742c055791f4746cf8c68694ff37252ece41bd81961e3afc6aee1dcf4442164ece91a9339503c5c93fe7643fc6db9c38a4791bb44f025d9905672dd13bfe0c89d7f91279d06c322f6c2877fc6c5fc9f9d1d24eb1b8998fad6ed9d8fa67001af2e98729c502f4df72c0ab2756967b271c0d9c3ca3bcd3611f7785642720b2f740d2c2eee2e554d0e539d07cf9ccf754103a891e7530df1a804daefe9f22af1cd64ae983f2694b4976313151a37498e615846e7945bdf3f477abbba217f9cd5eadcdd8f3b98b857d28b342e719bb735e3ee13a32530240f965b96094b0f20463d6a7443338a07aeb3b5efb02e430d5eb13129d9d250280d67c2500f490a4f9adad3663d6dc26f201d3ba07f8657321a43e98fc3b5fbed08d992fe492cf2c9cc4a8e41861036146c0e811adbcfe81f3afbbd03e95bec8615361cf64f645304018fe2de0ffa342a0ef23d01deaac0647c3dac03655e6863151158a0c88f5388e87a39f76e3856a7497dba592d450fb98488efa67551ec0456454f148466b61956930ad2b2f85e81279c471ebd6849c46f85fbb8abdf2100f6c7a1dd283e4eb4f474d86060d0ce8628df4d0fbfc9d6038c69c9cfb5dad8f23dbbf9f935166b815c569555b719a585f3f16b85326743ea2f765eb788e4de0ded29fca5ee76a6c2355d18dc0dc12974d3b5641d2302c25dd1b63ab5e719e046409524bb508f7c4a0cc98ef38f011ff03872078a60412b88629a1bc73d73c35f1eb8e3d633261932a03c2a58a02c012de2899234ad1ad7d146b4ec59e7be235a8e9c335b25c3d1f3520b4a2bb97ba91d10a3dd0686aae0ff0604f38469ea0d40da75a7e21059ac952af51d8a371b498e976673398a4ee9ae9dbb95d5522bb1dd13e2f3a4f867b23d7216dadce4a3fffd352d4b8b9a2cf173709a77561b5292bfb925efe48fc18d17f276a56d6ee571bd483a002a037aec1c42b4d7681e5dd56bf41b8ae6f83f33e2cda0ed7e96b3e1cd308fb81a11868575cc6d85ed28aac9cc2f1a2315a0962e6b8d7137da16ae1f07960702628c6d3d921e2687a78b856a83fd205a55454a392e776b6507490f463d29464860a13ea80a62eb2f15f5c0cb9d4648e389a7540a1d5fb16b03ff46d47b9c4db64a3a81f92830685116d694ee72e9ffb880f1aacda45a60017a7e0d529c80ebcad67f66923c33026a5870bc6523cc9594222728acf9faf61a67be1ce08e438864502932f7ea9deed6a31c1da290afe56cececa4eb79b9ba636a4543fdbfc63afa00d670ad0639c830d28975d813149d8b955aaa6c0b9139b6025df29f35f39cf2d17289a300afa5be9a803f622af7209befb434e6bd268f3aca6903f50a893fd0c904fee6e16c366fd1f77bd4e00a4fd8507cd1de7dc1ff5522630a206f956d8df3810b940c9986c4e6cc559ba4befe01e61987560dfa0103ee0d07e9953d3207cb7164c5aadf01b0944e8a2ffdcc4e7e91539eb1fbaa4a6cd1db531548b099f4c1e469094b460143999316cc03649a4dba4288add07a4fb109302799ff8977bcd4eb4a529bcacd072a9bc3c574d6ca3969f6bc5cc806f556c06d9cf932ce06644c25cbc91793bc36896ce1cdbac8c789cffbac511a9725b49ff40540d001c55720666a1e9e0a70626888755e5b6ec33df2339c6380a1a7a7330bf218fc7c7cb05099b1667c3473e007eeddf3b516613371bddc60843993a3c4429078650ba6eafbb991e46d5d3acf0d503c7bb23b2e735efb0739b2c638b48471dae1a65421800033018b43a6ca91500c0006666f3fc1ef6e8678c7cd8b1cf155e468063ac62e068451298556b68b4bda9a5cf6acb745abe2408576fde9a6309174a283688ee9a823a2fddf0a5cfab5fcdd57d50234febb64a3403a0b6ca2783a93efcfac9ca98df5f2e2ffdf921335397d876fdabc5f863e1f509a42dabf692ab031d54272a2fa9c1278c5b1b10152a2524b1f26d4332b03d91bfa54d4f07186db49612bdfb1fdbe50aec0fbaaa77ab1c74b238bbcde8cd4795b1b3a472561e8853a88d9fec6aba646c8739fdc845111354ddd5abab3c5c017eaa62be2375b053a57dca37280947b5f9c12f0c22054f3be063926a9a89265cf7aa106fcac9547c188450cbc1dca2ff3841a975a6ae5dd32e1f2c35b5e8d066b0f811ed0d66be2d324cdc62e98424605d948cb9e7d5d3b9755305a6da44fa680da313766c8e9b580f5c9fe574bd9578e61588c309b89c58a4812a30bf0110e0ca320ee6e5159a7ce4646fa9e1661371fed7376ce7f7a24e839ea53a53b91ea5ed2cc9c4bffd4513b2fee46e77ec940e6ce027bde7dc5fcab1941330c2a971a6e54dac626e9d8f95664558590366d8bdfb30dc2c68700b5446f3566c289eb07e2d1ae89315d1db1dc928245f03b9c6ded7a71b49e98f547d458bc3dfc5c53a8afeaa89cfce07995eff5924e41ad0da5eee0ef6f371906a1cdd72ee83bb3e1251e7a9d78c45080b2728ae9b970c299327a3e01e6571a60e2af5aa39820c77afb5965edcd6182cb1de79ed8bb7ec6a5e0bf2937f0a5fc52e0d0d887bc28d5c0e3d1503e3534297a52565c024ae35a61daea2eb5580fe5e82b020a427f2da2283b98fb01ea4091fe0e573f2a829fb3f7a814ab61eb5a4ebb27043ed7dc948f3cbfffd67300eddcc5339144d0c3519f206078b402746c396f93ddcd0304bd47749e92d9de273b90ae328ab475d7283926bea777df76c5def784773cfcb4fe79cce31772e8ef5b027ba9368c639921f07102f725bea9283dcb9d6aeaf465803bb0c894e8de727236205112578ec3b1d7c8dcf908bb075ef62f202857ccf9ecc420650db7914cc1fe198852677b38ebb6e6754e7452bf42eb9383dbb3656cf1bbe618fd6323991eecbcf255c0e3fde9b825711db1110ce381047c527d2dd6fcbf7e8eea4b24c65c1bcd3cd3d8e652c11c71839d1fa163becb5b7a695f355777d746f8e9403e5d8788e1de0ea29bc453f833ac18ae917bf9b434fdd99f642226b29e35c11cba5e55506879bc0e4c3c233abd2f6dc12346ee3e6cbbecb3ac5e2cf184cc7dd5bb0e4fecee005562f458394313b9eeb88a042182d0d186c5c89469a229e0492c94a66c83fcdc3ed50d15a9debbc8b5efd56b7a62000e61c57ff87433564b5ce38712e4ce18621af4b27e8945ce8f4091fc657f6f7a5e1622a45416ac6ca913aec2889dfa418f70e6f9003755e7ec4243fc4e3efd4eda2add24aad210e594b0b4151372aadaddb1ec035a296063556b6ac5f0d77d379ac50de063519eee2033bff9236e16013e292a4159f7df85a083867809aca6cb8e1adf498aa52b3d4c64de30d904f0197e02d8a302b6c079eb1b08ae762a4b28e637cbf223c6dd8e197bc97a42a84816fdc216d1d0d170e4c595abbf487e292646082b3b224ea0ab4c59d0902d85dd2ebade0fc6ea811f010ae60f6edf77268f24dd3f1139c88bb53da6600fc6a5dd6c402d96cef54f8e4e5845a623c91f6d9d43a3537ccff30a13212987179d5f9dd4bf7ddf6d2fb3b2ca8e10488db40a791a8ee7d8ac1ff3da5e408dae0db3e72df971b479d4b810ca7ebf1968471b22e70e8672e51514f06bd2d7d62b41f551cbd13723f64bc64eafdcf63f113810ecbfb1aebbe45dadc381eba2025c5d1b0e20846a559df1d63485866f203315721dd1cbb8b8e464fbb46c583ce778d1ff9985fe2af340aaabfe38b03eb1ed309d074895c3dbf4bbb8b222ee3a0ae04e7bb2a104b820d2c790e9242f57909f45e77096e1c0260e62aea9cbe579601d80640d8c1d4ac5898f7d9116013e55f2104659ab3d26bf39ba65ca47350b037d27e829a804a20ed1d00e1cbf911622ee886159ed77215de77fd85ab259a25cd1afe77aa5c668badd07333b4dbd041f26a210a85d832c9c6a0cf2f61b710639e79fc317510deb9f8ecee7d3f3b7bd07ac07e6e86967336587fe222dd84d8afdd814e19750fde27a0b54bd4e9f6bad5d5c0eecfead822a7b86fd9ad85295275fa45b963767550e29b17bea173e1d7414bc309c9557662732deb6da5dd1d73d9d74a1e0ecc43eaeace45f332ed5d8203d0b3fa7f6ac5c89c49a04b4d0d4ab9c52765d5843e4ab64a3ffd0f6a60a8a225992fb39a4ceb367beccdec02d7e16b02fdee34654580a6abf8a6a24298003ed28d7589b08ca60389dc22edfbf1a834b70f97629ad04fec58b73cfcf86bc3c7d881847f12800650182dfa1be420a5052c0450a6d0ddc34ddf7ae221e5c4f9f97507bdc4c5f214e61c393813ab5862b6ba618633307128e77cbeb5364c8c08918b00f80d3aee6ac2c46b4a403f4c5279daf6eb7d9aed59fe9ea2bbbc43d30d95c266671b8cfd7beb6097e21a3a96cc83f22da62064d35037f29378d87fb6d8fd7835edd49aec98fc2d7215049f26e7dff5c27611d3aa2b1c45c8e6d3cec75c47fb029c16063f43c016d975f5f1f23fe5f7f4c403bb27133668de7174657cf845d24a63ecb67a001fac62cee54512a1a79def9636c93c33be4dc57343faf62df654618dc029b2ee032160d80f598f4364e568c66bf23f01f3b9e4985fa673765b87339389749c0de50ea3706e67e542a2e7d038e2788769b303e75549331e70d8cbdab9fad8c2a0dfecc811c687219af4c62eccd954443a79a466ff82ce2947873f3de711298e7ae02586569932c2694b023bea322cd595debbaf30627883b0c6a9f6f18ecbd66551b7f2de4107ec189e222a8802d4135c3a195e81e19125d5bf73f077a3dd110c2c8670692d432b4bd8a1dc43a993c75bbd12d869a9173d1ec7f052191b931a6f14c4b499d0737d415506883288e877e8a54b6f43f94084a413f861566934a7f2421392f15ad2db8b39c8000834cbb9dddc018306366d6f6895eeab9af9ce2a02acd22d00adda378cea2ed587e16df37a10dea38924238fc58f3151906c5ecde1e7a673793e08f3403ffe6ca0aafeea5243901f442090e9ac32896ef21e4f41624e9a5638b7da9a43cab14c7697cfdc2e66edac796fe165a76dc6f156e98b00a1f838eee8dc8495860b22bf0df51950ead8228cf756b4b0b2539b25e3e405d56274c791ea51b6781fb703b98337c6e8bd65af2c930bb6fbbd699089fd2510aff6bdbdd44b3a7a93a7d892c5fa965931d67e7cc26d9fa11a0bc8a17a03b5d63c2d63636ec4b5be492eddc748f51cdf5958f60339d2bcd539d70f255de068c23a801bdbfa21222694f192395ea951a02aab5536bdf4274f6433afa849a6bc251c7fc681b596b422aea06f1919c4294c8fef28e5aea48843f2dc6228dd9c7fad5d8e86ea90afccbccb52297e3eea08ffa0026cbaf0af9112e5e4bcc37d3e545e72796c0d50c2960877a709cb525564623e96e3140131398b39eaf3a3f9cf5ce6c65168f15c1659d49cb185aa53b6c98ad203ebddd4b644544182f5efba1afe4282d11e7cb77764b856e478bbed95fdfb1267e723afcc1c57514a64a56f0f409a81c8547e84943efaa32b327c87e3400f88c07375e6aba94e11baf4e47aa7adeb864885a001517c05a16506a89274878eac3047654c0e4713f387b51972264ed7d6fb590bd6ed8ec2df25f05873ce77b00d1c1ea6685dab548f9c86160675cec344755f08fe5ad375702fb386a9837127816f148d8a88937d407255af84d482a4c89357bc4a0336d2bb344c87ef9bd0bfe30d2ce85673f51e699d1f9cf44da44ea51ca936303b05377607839ecb63588c976fb8cf3d630601c7dc1bf66e33a7f8c74fc7060e2f9559343d1ab414929ed10b496ca866881fcd41f90e9ea1a1cea8071936225a1b0696b06170fe1e7bcc9fb53429fecbd195bfe029970c39071cf2961a59c22cc76e758c73c40b7da8c96fef9ed812bdf1a0d146f9ec07fad4ef862ae709415abecaf6c9cb16143683ac2ef90ed1d047e56e236ef5310cb6530b169ea93f2f9e849142df51afc866f3ab544ee7f66ea50ab9a08d86afc78a6efaa4f82f523bf6d4bb1eacf320b2b1e2882d043eeb518420cb7bc34e112881be4fece191f496378a0e4fbc363214b9d85d62638f2ea0e845cb04059e6f7e0acbfb737edb0ed786e92d33b5d007f7bdc136151a858c3f9fabc34c8cfa351aaa74220f96022aa6c826cfe0c49191db96101a2c9434bc6ee3f7c4fe281a03e0c75f663eb0ee511ed1676d60a9decfe5017a12f14eef529adaf3b469b47ddf322042a3770ded9282b461515e45bc469b1970b9265556f55869078af4b7bf60118395e6459610edb2055c26c8dbed0c0ca3827299497f7a13006f0d4ff1704f5408fbfcd2643d5c20bc760fecb1666ffa5135dfea6d4e74428e8c502c45aa4856e8720027c152dc80a82994fe7b4f701ee3a58bbb8fac77ec5fee5fd1ddbedabcfc5625de631fdb0aeab66e5b03adfc421e5190702438ae9361d51d190c22a83eae3d7733cf7fe2920db6915343095cee479ea139c9c43bf8d1a0410ccc353cef04b6b6dd6108ef6cfcdd0427560722b4812b0bf834853c7ed782dc3c4da1864ce1d2979674cbc5318a4db43cdc563861f0a8314755090a6426e208321bab6366c1344e629282f99f5fc0d24dd69de52e491fde0bbf2afe0674cf69b376d083bb2dada96380720aa4d7a9669681363236b5f401c0e3522a2efa2777f134fc3cd395f4c9fddacfcfee83931b2f9cd9c9f6752e13f318189956a6491256a861635a69703bad69181b3663f4eafdc79a317eb8ec052d2eb59d83d00c315c9345987a25fb091f3e650f1104fb1496fb43aafb1f7b285470936a7000499b6cc6f696beee04885fb6ef36ea73fb03ab8a834cfc3f3731f7c7565460d87683bbddc94ae2594202b02d1eaafb574548cda4060fa18f6736a1bbbe18bcc53974ff84a19b9f2148830a029e65823b606be38a254cae4d0125d359baf73b520c4dfb7515070bd7988fcfed5ae163e1f00c5fbb5d4518857cc97513b8ed421abc48ec63b4ccc776eb9b7138d671d769cf5812b1d3cf463be1ff9fb4a17d76570c803ef6dc7b2dc2317f7162c92ea3948fe9a8ada5892bcea2b08f8fbc7dd8916312d57453b5ca9917cc559978dea8a78edbff3bfed89c8dca91a2ade45725a5bee5ef94c12942fbb998fb38e6073bc200f195ad43d74d807f16eeda86b4f419c872fe0c0125e8e7c8719483e5be4269ee083871ab3ba210ae96c693aaf8825bdcf8d8a80e5018c70f89108dbe3cd448fd1348b226b4b5ae7aa6d3b2ba46cecc48218a73b8dbc2a029e8eac5192a523e3d82595c64dceaf4f276144c07104d56dc13e763786dcf55329ead9652f7642563c3c341489e2497a865b7f1dd75de9dabe8b96583f88e4b47a8b669673f00ec61a0de9ee4b3ebdd06e21bd5f905be90b2f00edac57938a5f36a0bdd46121c133865a406c218d8702f1ecf4f2db01fce0bbc70f766b98c8429bf6687e17e9fa159d787025d25ccfff4122013efd5b7fcfa7ce32be6d033070d7ca57529129ed730c141a7e41838288bff85db6c5881cbe912e0b9597fd364ee45f95d87700315497fba9f3be05682a8ea113eae7236b2fbb7e4e6e34e38547d438b40c507ce38ddabceb3dcbbe6b53edd9c85cc26f40e08b71de3ce8f2ae2e3c501908f810659315a1fe0309d753fb0f4058dc30e51c44a7de1f3eddc7d8d0ed8c653fa8cfb7ac95dda25eadf6bf81b2ef4afc6332dce9a3cddaafb75ac750717b0d2587fcce45ac2bf21be6e4d54ce45cf6e2c771c3aae2816aaf707fe715fcc4fd0dc9920b8e1e9e4dc8ec7ef2dc1a8b339576ff80cbe87e8383e283691723fed86cb494bb3efdae81afb22909796c16152a84fe6bf36bc44b67c84e7aca4c4ef86016eb785b999c27c224811a5f463fe6fbd1ff25216a6e401f5d475e9c5b839815a07c41e58527ee7f8affe19d18b33f4dd664e8b4ba09e679f91da061648599a5f6d84c053926b281a7b3893f91c502adad70568323f744999cfd1adae33c18a3da0511e80b8833a36551959859bbeb62fd0732058b40653d135ce64203f2da045a27c43ea183fd1275d156a8be38374afa9729e5c46242c604a95e4a8580b7f82278cbad159a7efcdc7a7835ecad616b7fcab8ed8455bef89ae3e08123155d534dbfa31d9572df79a7cd76e8e052f96ce2b83f0acbbb1de6541eca1054f45edd2aa40be454767ad4e7d4d9f1565f69779c23a0ce37036d6de5382c0909d11e0f708f2d9680fe984d1657bb415cacd67d1f429f6ba74e3274f0ebdd5c9d2ce329c7603c9b863ae79dfcd66781975b6c7370ab6b0faa823f6ca8844be36838f758ed0797b8d4852cffdd93a02261bab55dd5324de6ffc3f00b66c1abe350f678de635a2619300b7f35c89ed60876ba12b204d9ba63f10180d14452f6a5cf25a92526017096918dab6da963917cf78806d10219a2d36ef3933590cf1bf17dccefd1c1028ad7b0cb2e6d758adfeac5e01cb6cdf65d502296ba6791f4d78a2e43745526dcf62b76c009f406bcdf803ebfd65a175da5b356c282ca00a6de059d3309289efc7f942927861e05e6df423ae27d6774f89021a2c6f1f75cf91a07fad361df85eb9a1149e7a31fbd2a7f6a204a064ed4b64ef7a7fb5674b3efa2b1f43867891cef724123c4339e8dd4bb2a2034a00ac544b7840727f5492435aff3f401f90726025d7f47c2508186ec20c2df3a64d8a3b75acc7209eb37f4532ca733510aa6d3114eb4f2e5a297b116bfa29c8962eb1c5c95f91a47cdb853aa6a7e1dd3ab6abefaace908431f0ffc201d049a3b684e75a80ddcc2db30f3a85abbd1697bff627f339851dcc97096b68b23be4843b5872ee0090bdfb443028cc4808136132b69c4401ee99bf41c64ff67ff5fa23148223514dc5cbb404d6d94d681afd31f251aa3dd6f03cf479691dd1f373f02a1803da131d2b5b33c419b451a3c636b465dfc3e0f0d35e46070e37c5cefb047fbd1d331d1a22cb8d51189d303f9251b719bc7b50f1b62a15c60b3eaa8c3cebabb8f1c5b198bc4c8b31484895e6d4c9b7c3c138eee927b4eeba779b46f186cbbc15493bc3d602160b495b1b57b6806574a05d54cf813976c73cdcf0fb7f1718bcd8ba394d9c350b07ad2b137a56a6e7a06f5f1efe9c31c56a3dbaa62ab28d7c74e273f77cae1824a6e0eb4971688facd627162026625afbf170b1ae24183f4a014acb6c19afe96ff55734a4a743663b848f99df00cb927e684367bc893003f7ab5a90bacaf190876ff75cb4d6ce2b526dc2c05faf03cbe8171f5040edaddf229c532c6bc7571cf6b66c6b75a8abe25efadbe7658ff46a002d1129c65dfbdcce32f80ee9e1fd72804288858dc8ffc6c9952f9c7718b2aa5634e23044102ea6fbbdf8123e9755088ba173abe2a2779e71e5a6545e35bb9b272b10bf350d28c4da6115686dcf47141c29920148930fadd382a9e973ecaa8b64e85de5fc1b419c5ae708b84d99ba7833e1415beb17218690438f7485edc6355a6457136f4c0b969fb9ae57f2a5d6a47d058055bed2d65a4f5b30b3721f8cbd00dcd57755ffc61218425176f771ea2a69880f8a44901f415d41bf0fd1582b87605d3512818bd815d133b658e6bc8a703133b786d1954e3f3e767a563d5d6994d4935a19748d3fd56912b5055e80902c3f9a745e43e016fdab2b6e508d23dff92ddbd7579aa02d8afa1694c8d8cc172712f79803f5a417e13d0e9ae246f91660abbf9612afb963f3743d57659f6f024cdbb66e188bc20d279cf80de8f2e37172d65875f8e96dcf564d315b9df052dbf498deac9786fad10f1cdae9b9bff35d2d21dbf1742887de902f384d0e87d1aa9b70018fc01503a79e23a6265bddde5f01e2ed36420a17af8e6db12116489786913e18a11ed03a27c57ae93830c9f50e601d1d8d763a1a52d095e3a856b279a822ecf33a4052e075f9cb4dc1f2e39bde13dfe2ee9ee56f9bccc3c7e38b801a0f84e73a605af2a7f84a396d1051b56acd82926209db40237d630c4ca45f71825f518d1de9f6d6401f3c8a27549d01bb8b17ddd3acc6258198035954e47374f20e66dc5ff8622a784851aa266e269214d7adf5029027477b1683815a81653799763ae0f833bff21a07ff561c26105f811251f971b309aab06365f77735e372de5f376948fafb335c15f2df0f9d4e30c3b4da4b3bfb78b2c853d19fb42b01f4be79001fa049931cc8a5d636aeac4f33957fbc28708112b1c57c89357110f1e6272a47cb38b6d12017d2c5fdd88d91e9f7adbb74693fd5fe4afcddefe5e7a1b38ab80d21f2844e502074048b323353235a792c1a2328c99e2cea6a17024ba6881dbff6d9ad17bdae9f96860519f56bedfbc59437493279f94c8762411d51f5b28d09703234db3d1ea547d0a3510d3387e5adf19607ad96cc329adb4c2da3815abc6e80a72ca3b756eddc98cae42f0b33ae471478f513accd96c5e68f10e7e5bbf32480c2393edbe6f0926a5539d9b40724eedd3f32670a25fbc3eba636cea6c04ee64a27be92e016eb00ea925a75e9561d8edfb79ad3387a1bcd793ed2669d27d5adc55a3ee653e492bece6f15d74d2ce557243445230888f74cf43b9cdd1b4138c28995c99ab81e4eec453da69c7ad51345069a61e74a3b658c5fdf67a177e0669c08bfbbc3c715a6e061c8cf8df6a2f427696c69e19dd4f4cf65360a440aa1301979b02223609ff3f0eb92db155f3b442f33fa88b302c26aeeff1ac4122baf18795099be8b0866bf4d1edc3ae2088d6617e24847a0b903da1f2db298d8cdc4af188e01755a1daf8d7926116e0c558efd2cdc6c44699d4187decdf874f4f43cbc2c592501a7d35a6db72b6891e6864fd4d541cba6e8ead089c09a48ba8d579cb5ea5d31096a9a1f6f6e9da2ed29ab5460fb47e89a2bffdd227fc9396215f2799fc27e149253ea7411ff390eb48cd6c4bbbe52338b7f5f2e67fcfaf61798c78dfc9ab732f5fdaa258ed448dc77ad8ea3c2043c29452ee900a7a813daa3da998764dc4bdd7b660f2f60c71bf35c32e3bcec8cb397528890beac83e1d29544458b0e1309284ea26a55a004edab5d10d7188a22f5ae80cd60fac12a6214d0dfdd3fecc8b4146392373c0c2d0fcae3a69c36786110fc3e1d5a14dc2ab9f5df4a0e73c9f46e678b51c7b95c168b017614bb7df8fb7b9f66cb3789c722ae0dcaf2f6116ac5856cfe708da479e265459d65c45becc77badf5c8fc6b5f0079233dafb47b70a00abc48a904f44d9997a1ed8b1f855750929009cb9a7d52df3339dfada32d9e140f19f8ac8accd73ba459de2ce1b82cce9fdefe611b97c04767c20b8d6f7bddc659d1488c52722768a62b7b5322fd534335cc8e829d6b0bd2a7158e7f8b6e7da16609f0a9820c759619809f62b6ca3311ea94097a5bceb776be4b49cc07ec2966eef517dcb0dc853f6928c6188b2893d55a1a79dcd758b7580410e9c9b54e55254795b7b737d35406fd68709807e502d525efa6354f0aba0b8456529fcec2dda0e790df63e57dc16b01865def852f2b8ba2fba3c3687e4f6a92fa33db9768f07923b95a80ca3dc5c1883fd321b260c0891f968c0008be2e214cc2bd8fc82ae8fd140aa1df6853dd96db332b4dd712f2fae24a645a0081e6d8a0513e7e08bc375dae1a861f3125b03ba6d116eaa49e510e7e03c5c87346b481195aef5ddf1575fa99dc64f1959a8cd18c4c290321ee10ab70e3041609ecb62b56d460eb00f1c1631fdeb32ee3ae1ae7588a14ad5ad4cbf766bce2e2389fe887357c6c70123f563399ce83b2ff78ab38b8052ac29059da0331fd0234561220f812a9a7f1e51458f0e564f3cb4a8af37435d75cba12ab78bfbc2c5896945c793a6259a8ea1be96c73c4a22afa877770f0d15c4eb02a141e8896a39315be0e4de0d50b91f644ef4baada7c64bbdb1d5d6a17075ec5de22f9b8f9f8c02f65cf6653e4d55274f51bcd3789c80455d41263400443fd1ab9bf7e4c13a07ebdae177bff591e0fa43679029e8c7bf4231a57a762e7dfd6c6aba5145b6a569969647634787aac30b167b50a18758646cf9bc90a46cb7f555d03960ac6229ab70a723eb4f05189bc27df400f4fcf8a383e56cc373a6f5871b59bdc46a55e2f5850670d0cc673e9efa03751284da8e6ca9c83a9a5618557366ae350aa5587cf127a51a51b9ac598ee2abf671671b7ccb10c37dffa104db09c66eb4d62c97b88792070be7c21eb354e7be214996d03bdeb9f19f0fbb91d5319be2b16e0b0cca2d1bb7999085e170c6a6966f1f0e8557250a215e14bd9124352a3b9b22a9e21b059a11e67ca23a5c9e572ee12efc21cb09463792821f9c0814946d3b54c315b53072144959c7ca783497d67ab5bd1fe8161362846966fa1f319afe4ab6e23f34d70d976e147ef124c851e7980b27588f9b50abbec8dd920a792677a8156a625308f591e16e2773c05ca14d34520748031d9f884f69fca93c2e1e47c1d918682261433d6a27828af60f6088ed6ddc7f2b9f1a41af53c7d106621206985fc1b1d63de4f3e0f3c79ff9b6f2fdfcce682cb68d38c77e3a14b4c69ad30fd7b9df7b3e44247072fc45ae732191ed079311d5425bea66dc8f2a67663c98558ec635abef5bef49469fec0305e6274dcee97e1a5527d3d1b572b803bfcaea0deb9c62c8227f93bc8c257c97055d0e546637f7f96be864d66f52b4c16d1c72c377ee481e0c5b33b0934f4e70b72e142f8d6be353e98198bf887a26cf00133ce2741de85c8880b1c90ee3e0a5522816f627b1ca498d87b3da8e1fd5835f142fcd44535a96b6aa0d685c138d4e8507926eda849dc531971fad5ffe019f7ae0fa0f8b5e49dd31f7367e33ba52b65d6c87aad7c2a33760f2397cf663a6145a68c8cf528a2ef2ddcdfa5fa98edf897f6f0abfd84a5be7fd099902c5aefaec55bbbb293eb4c22bae7e528e84063cb36d29e7c7ba0af09a20befb092e43ddbe0fbf62dbb9cd02d0b107b34b231a880e78f2cf5a5af305519913cab115f816b5adf616396401606536a457ef2ba34e5e0f938c753969f9b41f7430d8bbdac0dbf81de4a92c7082536403a4bb3eb3445b5609c996386a8f33bd9a164b3f62c24cddc3c64a5c7f9b7070540070e6687de5fa444f34a1f070aabbefc8ab624fca8ddd58ed42c77e2fb6d7a03068688b2f705d7dffd1b045b91bd1e045799eb8ceaa0972e986cdee3e8481cdfde2188a899be76474f289f0ef5422e9b7f4d4d06084e7243452b399c37f23aff12d572c3fd7cb25d74828931ff664ce07c3f465a84a4baf95becae8ce7652207f72904133ea407f458d53c1ea3b8b6f560db4368acb403d61dface57162aef2a158a6e7eb3e30e9c9aef56e6a311644b3dccd78cd1d1dedb2451b74a7f8a6ac2b9fb0e86e110e93d0317c067d74d32dbe6fba9a4cabb2f391f3c70d3ab4c071e705e9c9afedf77aa5e66826c0d78a189988cf67163e86593fe48b2bd53a2a89419b98e1cf1fdaf2e733801e71a6d8ed3dc4083d9baa00fa6ae905eeeeba0e541bd8d85ecef00b246a346c87547140e746e5ab226b3931fe04b6b63a7d42f6c3286d3c92b0c0a8ce520627ff401d443c415a28a20e276cf358f7e49b70e296d9f9ade971809c54a8ef9557303aff372848495fbc5a5ef10ec90be0c2ea2f3129e10544281ed96f890dc1f404cfcf3bd7dcf8b54d2e82513f50c915945a62edcc6f225f5942c87dffdab2b49b3bc970dc858241103a4b642664bfb86fd6af0dc5210306f2750cba2a146d52d674c8ce7aaac6c0461cf6e89ca3d87d3f9ca1a1b6924e649038785a02901cb1947b890c56dad9dee54f686fb350220d579577d4065cced8ba8c391615dd233d813ec5255b05470f06f3aabd54157bb785dd9f699fc4ce0b25fb0eb50b8a4095a6cc584a3729e0fb99119ff25239ae56f3df4c1a8de89373abfade79641218bba5abefc316e28bb6d4a65cf5d36ab88d2d2ad70eb5fb9e6e3473aad1e129f6aed461765f7279d199c849d2e557b76dffedb601fd7653d5534ffc6b5074469beba88133946948300a96b11fbd02c4e70d741e0c3c918dffb2943725815c72846135b0664b2d8f87cd96fb92b523bddf0a4ead252224af8eae4a8e636f9d8bab6812339d20d977bb0c10898181ab427eab95a4cb762b273b1a5b3758b4b08aef87b90fd34a861a18a18d2459fb21707d93aa4476e7d6074af21d7dff8a22bb32acab75cdc68d90e14ebfd6967c8cd7a1165caf08cfbf905363da7c11b75f45c70507d96acec716703bfd2192ac7bbe361e740f330e95379e7c7c4193f5d019df783e475e90f8b803b197e8ed4d639639d6b5860a6364e855a4ba9ed5b7e4aa08feb78c9e7f37f777c18b086329b371237ad43257aad45301d6cab795e633e952b73f1acbc47f3a5846204bb55d554a90fef542ed91de011596080e21d5d7377af28e8d45eb49b5448ee35510aff8c1f2682f809b93a1c854c2ce3b3f934c515620469e3297741676362b55a5a4224df68b3d1b7fdfc16cfc719f4b010c7a284557bcbcbc5a5637bc598863c9bc5a97cbeb66dc149e13cff2a934ce196c7d9b7ea1ecc5548f03294be7c61920a7f71b5d804b959762c3ccaacd3c56c6d843fb88af8cdd2624b03b3bbab844f78d416a4a5b681a6de7ef9031d88a7209ef42ca39eff0f465777254f31a9f2259bc93482067d835a20fb8d657533a68e09c7117b2dba73b039d3dc1e03a77b1d7200030a83cc9bdc502e1c69d6ecd8c5ae642d7fb27e6965aca0b47ac098038995cc8c02932bc4be64008454fcedf97f14cdcf119f46823af5e6b36c2bab1f329f5c1f02afb69753753c8a2456263b5c089f816cb3746130e72e4c4c5beb6421f7fce47bb8e4cebcfa8a0c61aa02cd845796127296219b39b022002af3fc62082c803cca97b31b7d107a5325cc05560e833f2b943fdbe7c953359de9edf425c6dc77f31c2a511224d5423c82a9a94a547c70e663b5b4467ffc386a3d94a3801553097c12a49b37a36f1809b69e9032b8512b026d7aff9db04f7bf3e84bcbb3612e068995b2af43e17e21e23a56fed500a7d1d5e2f17406b2b23215d2a5eb8c6b98912219f9780edf189ecf44bd348b7ee880cb5073f00d6987228c9eabc74ed23e2e64f9af264e2f117c1a265b0cc5099999bfcb35d9d8db2455cac11924b1991e3ff472fad49261b5355799f51ece09951c01217eb099e48fa959bbc6b044e747d843f353652b0d254152402cb5eeb1ff038cbfd977155590fbeac170925af41f2360bb27defc79a60bbb303ffded48cce630b8d65856cda4289b447de9f6047eb27f09b85a402f6523da7f1f0d86cd496db2a0e0b75738bed1fa7cdb8a9162971dd96cc9ceb43b4a898053e2c8bed1b10f41a79b01c9d5530b582d031588ef3a2a54bf979166a452319b88de89ffcc63f2dbad09072ff6ad99f50204d4d6ef4ecde1a95754c9ca8285dfa40aee0abedad0c16942d95d67b89c850f256c84d70c2764f3e617ec8c1d178cb39ce13b0f87eb671790021290c0dbcad1de3fc7c96a2ef3ef814dbf9626fd16da10cb9278c59e19e8c3679cd1ba172b36ea95ee6924a302d3cc3396d471a88c8c3c70cc6b04cebce16d57ac9eb2c7985dd14ddb8a628bba9f715b517097a105e7f9730c2421fb3e50e18eb8277275047a7c07805756a576a23b330b16e0828215990c345cb3f8d874d50a5c6128ed5fb025d25dd0ec39986db39f2a78dc7e8cc7cc163f30ad1ca096dd5d5ace3fe487e05bb4280dafabdf329efae0f53021394bf3b0f79a09c9de046feae5fdb3597f539d2b603b305fee0cd8e592cf47c54993ce2304e5c42e503d596a2fa2c3c19273c5a7e6bef906d6f5cbd1b7770d6ed1bab682d5b723e94eaccc0a2892a2483cecf6983fd26027411ca27f7a10431e7b7782f30ec18726edf2ea22be783679a9df8e61d9098235700ae95c7c3a6d73fe671c16a17e079cb239159111e51425be3f15621516dd831df05a3d4143a17eac4327d5a67db243c371d23948afbdb78a2b18b6b703fb3cbd48ef94265b1e942bc3954ea648365ba6cd8d6e8bdef7beacf5997b7f9d43fa8591c511cdcd12e15e9aa209a6f07d609a0d715b02573251c30ef68c3f34181bddd5636ab9b603d135c40d080699163367bb887b42c3835669c0111b789c0e9731438c5a7c7cd25a8c44d8c7b8e59af457752cc9785b1cb5e5f437a714502705ed1e43d7797fb94a0d97981825facd1ca79ea28f045101d5f2baf1049308d03a8d773b0111be444267b6082b7138bbbc1623a9de92ed883819b7e3fa7bce2a3667493f493dfdc88b46c1711544128746c52b7d8f3530a77e26a0d589ca1b6609405f8fa6cdf193c481bef5761b0693a02e0c214170583fedc06c5b5c427a3164b350d28f23e8dd9e4e954d72ccb6801ae14c62b3a047e3e8497200230bfc9ed92353333e2a0c51c68ae399510173f7c2de9c3d50fd844ad1f745173eaa7420be8948e0e99b2d077581daff59f2d8c459ca38a66988e411510a31345739fea891181c3d1ea47590b9e5889dbf0ed8de4d8c81ccfce396e8bc06dd9fda411c90f7a4c11760d091e0292d3c23fbabfe2061bce5aa5d3550b2fcf0fa898b293abed2a2537aa20f9a19f56c00470c2a484a709422fba3b9cac01050fb6ce62b8469fb2c429bc335c1971cc4093ee789a2531c0d99963d8677eeef78b71a5f9c0940c80f5d7fc33a2f9c21c8ac0f9e9defa831d01b4fa9f7e6e7ae45cfcec542205aecb3b5fccead85ae8d247f6e439803604aa4b78ec88c485864faff730b8da3409d65285ac71c05f939a258ad32dd4bbfbceadf5eefb738e25351993015256ded7a9a39c693128a9ba8f3d9f4f79300eda5faf432d209f592c7fa39033e414690471fae2f59bd6355bebad1f26f2a6842180cc991f5f36cb6253e3eed0016ec0411e6e8ce4fcf0a05d4e3bd89ee866282e71074d60b56ea18019b72be418d2ffe0ada10d24b58d3a68b2e742753d781187523a6074d841937a156e42c510301bb0d048314bfbf7c34685aab80a3d5b845dbf71ac08fbe9f07ebd34a8ee99ddef327fbcbd2e4bc0d15a041b2198d53c80055849a78caf7166f936148a45fd39415b7f88ca6661f90c071a867ea3b5c6698f10577d5b1af3949cc2014a643710c5a5815ee03c307077c4902be01f71a1057ee5b0cbda1f6192852964a8e976f62f598a1b4f413ffeea596958cb7164c45f237e60bacda33390ec4e2cadff937756ceee5a10548fac5c79fecd214f9ddd73194addbb7c80f67495c6e6d05e80ff14102f5af8a7f34d01e1d201b9c56da2d9e83f925131d16c0f35d298f13a7ddbc74fe9ac763a82bbe2a524cd84fabf44be360bf204b66f164c599ec78d5c6701ed0a68c1874654b0e0f8f6ab073c616a83be28faf35588daa98915d9cfbed664bef4e818f488065fcb16107ecffd4cf10a7cadcbe54b06c9d39d3093db978af0c74203362d9c3b86d1b874bcd524cfeaf269e973a415ffff7f6342334992fc76a6b4e24c723108dfb112d8a4cd539c463f15279bc4fdc6d69207a97463bf3c18c72d46ca978e861b35c0412fe8d5d7b8b512bbee6ca49256ef4bcb359915ba503e3fd2393064433a1588fffaf67aa2264efda260bc39f17426b3055cb0042f3d4a8e2ed89c023f666f74fb0c3e2e6120a46c65d9fbc9e906e3a50a37017f9c81aaca0bc8bf1547602ee26130f0f6027826bad00db406cf904d2ad1cdc52a91a4b69e5f35a80acc76204ed917573a4d9e1f6c4bdc054070377ac667eb237ca97917194bea854bd78e5b40acc02fe5f807cd696805d1277ed40a4fe8f0468c21a6d4dfd14fb91b91072c6b25eb7586cb5d2f30a90344aeec8fc855ca0a4e4c5b4fcf189595dd676bf26d1bdbe914999c427b967d34e2df550b5cb11665443a3bcba9e852fbca4d4b15d8fac595dfaaca9b3a0f2f0b048158ce595185d0ff775c475d9500c613c96cc07ad0ed121f9d6d9c92280471dd047da69fb3366282f5d2852151c5f36101c28df7cc572e55983b6f4add6a1a4da01fa7d1216f7cd9a00127dba881bf7d3033df9fd1873d8d96745e967a240f751e84b6ffd33fb7ee7548256cd1f67f90289246ffe3178317960d887e4449d84d709ca73703a4528cad71bc603c4732b727e9231ba1857f3f075a85fb7fe0584f5f4f8973719d4206887d8ebe595b8ec51908ac63d54b5cf5459fb7683fc5f6952abe24dac57952dcbe186c766ea9cca2fbdeade3abc0ef4c7369b6f2b946001a9f849298459fcd6f98c7a82d496d1aa46276d182373f868526300001b39d0ac9805f4e64f4666b1203a9bb954f8f3526ba67a5fc5ae0129b9d66ebabc65dcb797e73aa0c7aabcc7fb83386dc9462353431aa7e6fa0ec4b301d98f9e20f63b407864014c9d56e5a44d7ac3d545824e1c0b1d49f1e69b7fc29df4a3bea8836b61c23d9ff60848e13730f73fb625afc2bba45051e3c6f9750744a991c56f82f7624f094c0b61cd5ae2e71891cb033a1963c5fb72ed8dc8e2b94ba9c70524524f3f12e288df34a19f5ad9b0b6cabe383bedd5536a5bd85d056c8ecb21e4b8dc97e4020d957a99ac551479fcd9939e7fc316e36578efa06bde1a974ed1bcd09c238e648f6c99527fffab1d7b1f59335482f3acec3841091c44cf2a0505de8ef86c28348282fc6dc34b3cf19880fda29978594066a9a295a0ccc72df32fe0358a32111c2d63c6b807aa65bf3738712a087b5df80bad7bd2cbaa0db6db814da10389f96ad23eb54af7691a3d2770a62d4cfc9284a3cda920f5b1547c6951b5985b36b5c1064d5a90aa4d77a6fbad6cc437964cfc3ab80e802ac29310f0b58daa57ce3d7b4ebf5b31f0389007dfff4dd1a8bebb9cfa8a15fde0beb3e66b73c27a117fa601c1fb53e5c00c47a913d7f427253219199f7da7ef95fa7565e5c92f2b94d40faec6ec05e548b86bf8267b412f537cda8d095bb1d35bccf26b1a148f80926fbe07318b2f6dd00000dc490e5284e6b716c7bf41fb292f2f8db279a90aa616304d0b23dec98cb80c5c380ae17e3943e3094e328c0cefe68a729bd469b2da75aceae8fb63c262960b493093c1af5ab17c18aba9c12e89df9d00d9cb7127399d9d83b5d80af0abb55a568a7c08c4ef3dabe35f673f3ba78b0208d661ae9cde62a07c78e7b63e44b37ddc5571d2c49b4dd822033f69fda72d5aa17e0c19ffa74707d738c489dd794b6f51e933d1627d728dcefd82cb3edabec5b2a0c58ce0211322dd4d3ba42a7096a530a81222eec0ae72a038491c548f08a500a8743b1ca52bc28804db8c2abd0ea0b2abb4ef0db81c42c977205cf9949dc7d585d3604e90bd6054520c6870ff94ed2cccd48bf0bd0fafad5da3dece027dd8f9fcf1e31b00d3f2dd3cf01fb66f866883fc0dcce3205accba8aa3926ca6e3806e1a933a5569099b4d62f3c4ef3ab9a3671857f1c6e03ab590f2bcb63e5a66096a67aed87064ffb7d736a519d6d40b68df6b8006011764d8d12496607d768198aca1fc6d279017728fa5f0e8e795913526bd65b2e770ccd6a8621759793f5807282b2b8021703a0d4745ef7b693bab08c52bd888c32a09657672c54923ffbff60c30fab28ce8c82954b4d80f587fcf9a6394234812b0238881143d7d8b861b185a21697df8e96c61c94bb5c232efe9f6a68e2ab064f3f39b89497bd6141c3685debc82224bad8ee4bbb964e561a75b4502b39079f579bcc81abec472b5ef055a03f6bc12730d623f4ff87928939218a2d77cfbf1604b2c059ecb0bcf6b5a803a9a13215ee85b5fc64cbacb5e325e4a6b9dbdf5c2ee0884bd1136d068b1f877532f3e413b51f59b28cb91e849ba6614c4c181ffad9ff761f347725344cf3d411c9c4329648504d112c9613a69967fd2ef4d9560a56b847ff529aff9d7a70a1f11fa7191ac8ad55a4df8f7006fc789d83099314ea82f11e1736149a94bd0ac8854eea4be0af80c2f853aa0921d7ba32b1f6d20dde110f076eef1bcc27c62e1497ac53ffe0b10ae2c9c8547d64d168a009ca9d28eac924250def40a70c34301b0cc17bf09a18fd190cd8780f2e5ea8997347721e51b9078346feb7deee8700c0a4f22447b7e11b303d26d61968ea9b559f3500eeb67b3cdbc63858c202c41059f7f2d283e243215db953f3a2b90d0f59ca52dddbdc7eca470f431f478a6ead897e3cb53c7b4d1df1a985cdfe57604e32fe8989ed8087f620f90a76ee7fa98a71ae611ed155e3831bed766a487919930250768205384c54ec76e3c595589d95bb49bcf9ba4dbe4aa4710ebe665b4c040cc12662bf0df434892a7e56b538a22591956cdf2896ea5a3f5b3f1a4009509e608f951a806a9baa7f650a3399adf9d1094ad9ec6a2d1d1e5b73d2b7a71c36224dd48b3d58656b134406187389de79c5fa5df896420789d6592e916c43f4a0abaa68792be353be902ebc48f88a7bd37555e9010e8e37540c9c17ea0e41e9746fb799ba08098998bf48c33484230bbcfe932383ed34fd459f179f0718e468f88ce9e564e3dde0aa30d8f30de134ee15b977c30ac48ecc4cb3929d9989f1a4797917c30c50b0bd6fa3a870d65980f4671ffc62c9ae0c23ce8698b02b89618ec8670adad789f99ae7105a74825dd999dea23294082433d2b3e670494cf70d7613f9a0849f885ff7f8b901b27f4bab681e1831123977c27579b3df9f69b91e7e40a2e38b5fe96c3bdacf008b8d9a14ef2aeaaf63eedb2c9e8d3bd92c1f03f8f2b0f180dc36ec2d3bdcfd7ad72ff7419a854ee56110560de51aed727868f1dbddb86f7e9c5d85ceb50a556b3af25c7068969f64dd072073e9717c239e6c652d5d888e0d8324503ec195c8b4d86346373ae2093945ebf279442ffb1fcead9227264ea21776b72912f767f1cb031f7c8877d1b3972e06644a0d72963de7481f03f67538cb0ffbe7b9b2d893e686433d47a7832d367b536e26bde33639dd57f867494c402b6eb861ff726d5a7f9a1b5f585355f4439cdd7ec5a26be7c909fb4703ffed22c87fc61c034d7652e4f5ac9d5daf8408f7a818e529092456a26aedcf9c6c6db740f10715dd69790d569934fa7e8eed510a4af742347b0af361bd9ad42a752a4875b0780a27d95cf901c010df61c1722e90e3b0307b1bde304c56be886e7056bd91ebe0e602e96ad9b6c44fedd8374a4f45c4e18fbb3a7effb1d9b2dd8abd8ffbca3978faa2532c712a63e364c89ee3c5679c571eb7ba840c14d5031085c50d382a9339c942a6860251e00ced0c34e02a0a9d6eaaa1ddec748d74337752a864f57126829aeaa811f8bab71dbd46c9744e062c6ca13da72ad0fc16f9832a54d7e68dca5879f7028da07e7cd1eb07d1f66682a232fa2c20f31aa64ea1ecfa48af89716ac56ef8c43a744b525179465c391aa8dc6030db1bcaabaa53004453bcc61c639b2d66b4acdaca0ef10bbf1dd96d4f16fa5bf1507f17dd30a8a4d7afbd447828cb8e5b6dc8fc5677c2a3156ff503da517c7b11c515a506a9df6d42ece44ae9a64d422635fed1710261090c658e469b557a52e1e539e6391eb85a6c845e74a4287a2c97d09e2179ab87a2ec8755faa98aade3c4c68d529929ce6d2f11e232c9d5fc23473a35eed37f6cdb4b40990cdfdcfa937f565849a431bf226519a99b171998e5ea3a2b0578ee3c3e3e2bea442a85f762ca44802487411f51c20dc5bdaf885d87bff4afb1be2b429dc05ff24b1534d54a3fc7c1f4147a21fdeab47df8e7bba203ff42ea707c6c5f78af570ba0343425fa3031c3ff0b1bfd8b9605efb06e1ee7067efce8e7553c27c3049e126f22e00f684e8dd9cab8fe0ac9cbdc3a19c660b5da565a340aff894c955295b00660a73a08a6840d319f3f062cef559fba5c0cd23bc1501dac4f5ee30959497ebea4678a7325fb056c4ffc18669be4cbd59e2c7242b8f1eda8c196ebb5369b818c029186d2d731146674f5891929282ff4dc10005ec55b4379ae802893c7912c1dcaa2f45b87d907c829c9d952d416e6976d6132d0f7be765bbb5870af59aaf0f2476ca0ce2915f31cee162325d406ec07e78452732032ccb0e52f28fd622b6fd2bce883db8f7799a06e9e116cdb08d1193f9f57052681d0e8dbb9b8930fb4d7c1f6dc01cba2386317542ff5be5e954bc8a12dc2ae3b4dd326b258ba64d316b24f0fbb71b652354b41d658be4a2cae21999b31c3166c7cb73ed5f60d66a5bd87fa45d641dbf08d5b8b99583134bdc1a782f3b52d4ecf523b8252c3a85391d7f49ea7d97a74cd377a3f4ab69a2590bdf6764fd21de4ded42089d9714843bb0bf16ee251872d7ddc128f619c65966570cdd05a906a54145962978a9226f082011179825cc46f90a90c81b8ab64c375b6e52e1f18a70ba7c8336c1bc6e3b0b30d6a02aa2d32ce54cc0aacba48143870ad89174cf2dcbfc1f87e3cd4cb5c4038eb83d3f869b47153939cd2629e9eab7538d5ac448aa7971c8d320e8f86fca5f1544938d171490f8796c043405601c26565ca59fb8816d6b0f65e209ae34662b47c30de69cb035b4b54d4058777f035ff9cb451cfc1c1aefcfa89daa28548d80428f0100da6c06f1578d0049435cc0cce311620c91181bca1188d64707d09422f1d31ad8e218947932d9b4b0be808a2e26ac91fb5358517dc1c15ff866c0af1394c98816b303ff5c47a283838cfbfd87374a3b087dc80a5970d16fea2c0f4fbc6977d30a217f0a9a3bdf3fc670e88ae5472567367ba91ab80737a5426f6fe066ccbbf8c1d0b8d66fa23a81827ee1a538fd72a4dd991151dfa312932345cfa538e07cdfb6ce1f993e99fe5d84aeb75257b9cdf6e78790d4f1d3957d288ff0583e955033e19f5630e1a9c714f5fba0fa8b253a4002499ca5cbe6898793c7566ffba3ae71d423642c17f79ec21ba8ce4922bad45bf6390f00cc2879f97ab9ae4422df3b7a3b0f9808cfc820f0e95d9f56e78c333813c3c6c53df150719c199fd0664bcdd982040ab0e1345ee234d55ee09d875c508b7f3ef745d4b409652d1d6de77a7f5a039833da0cfc50194b0cef51e55477fb1bb8afbacd45b3ecfda1d4d691b73b7d2b14ec57e089eb70163ff769ddbb940994566aa7086637784f5cf8e5f35c841d075d3eace7580be5a1754364c5428c5078f2122926c12a065f60fe58710c24b0dd60984ed439d6edce45b855f9a1156f4bc4f746f20f4c05d35683aa2be0c3b88286e5e1a4a5d3855f8ab2043aa565c5346906a48ffd49f70febd9438ac4e9d7d47a945b7cbbc3f37eda1b79127819c009d3716ca70bf592340e0ee42408c3464dec999ebd8a724c1b35c9fc85281dbdf8dbabbdab8d6410459455ad4721e2ed67da296d28b1053382891d7f65171c25fff34aff697ebf5a162d7bca640485e574dbd8f642895ece6dab65b6dd2fe6689ac7841999147cebb2f8a467fc46d1175d59af8b42a2c20a1aa3f0f87accd9095e143617830fe664d56862f94d2097c0b32a7748f7f741d56a51f27ec3521721919f4f41b6d10b2baa05eebf218011dc14e984b7daee8ba0a1822daac422f92dcf0e2b5494efdb6fb0c4901764bc6352a57a1419a354a6fdd4e78a9d862e39126d6247ab258ee2211328cebcd8de99fb31481716ee36f1e74b2fabedf4751c7740761946c72136dcfd4660c9c1f1d6e3a1b875aa15123220d008049b0d3526097a1957a9d4199179e26a8979705a2683afa92c0952b55fb8c27bee76348de822d523326bf2f55d424fb3f1c10a4d81ef304c232c3bfae4f6993412f34bd16ba33745d2aee5e7b0967ace53eaf356d06df44a114844bc869069daab744673f4b4d5d4bb0cc834f9d6f214806717de14e320b8892108b0fdab228272a52b8d2ac82109c281967f26a37732539daff7c1bfc574013a73c179eb7050c0d21f72f3c0b4c49910fb944fad3ae0fe4d669a21709db266e5b7d0b278df7455f7314ed55cea2a0a6509a0f61fbd66c41ae26000cf747d0c0d193ca20eeb98b71baeabd40c7b073fee850dff109e6a69f5ffbfea209a0eeb6715c89cadd4230402e43a16b0730a336e798405a102dbe05cb22440d8df15fc3f919cd3094572f6e8939cf042eed2827e6270723ce268579ffc9b39bd1815e30c71f8e5a06ee17a9ffd1a79c6129cef054848e1db530396e081fa023592beb9e3eef4a0b61cf4fa088d1824b71f6f260cb2bd5791d75d912c17759cde51055ec8ce1f0dd082749e54a1c8d09b300549e5a0efa5500897d38e4f8482cd006490424452adc5f03664a398a359f32bc9a4f2eb424d8fba66cfd024c1d519a9aab76b77ec5278d24806396af201bc415beb64b7534ae67585a6569220befe37e847022b5242a98e89f2be398b8a2ff1f959d39e7d85ee5509a5776764e2e7d29433249f8897169ef233a0df39d1a43fbf2ac0817507f027f5520237910039db5b841d01e168c02257fd7a9eace873177dbe897474b54d186e41e75d6ea5db247ba2a2cac6cb5c792cbfd55c22ceb19a83cd61cf4eba496ef7618914a8608cd9847bf60d7317eddf92b614eeaefaad37c2213b41524042c2afebd629a2ce60ab6295d8cb46067af2acbe2d5d76ebfea5229c036b9231263a39abd309c1c5fe8f8a2a79e5b649a45fa464e65596495d08ba7964939c6b41cc133adff5d047a96bb8e49cc39e7424f54b9dedba9ccb925c3d11824e4d4f308666d7be92849897d629307b5ce0ecab6ad9ca57bb08d8302cbb5cadf6ee7b9fa4545ac7c7ea1fc939044dcf34457ca2a2e8a3a807ce87f9fe3e243a854198b4546a2939c098af1ae09a0358b23e2f35284d721fbf1d8c8da0afc0e287705357accd2ac016e5ca3bffa475e03063a4c8370f2f4155d6c08777c637cc7cb438d0ac1d815ff37bd9d12f13952ec9f1c85dfc6ae6d99e7869b0a823908e2d8afe34729f38f8a4d69d559ad465596c1a48975e38fe306a601eb25e8527d4123c4bfc33bc043ee7f72f8e1907bc4974dd2022f8179744d02d3eb21fd59b1c6c3967ee812cfa4dba0581598475869cdb799b6a90757e21e8e776963253b22a1b52efeecc82ae1836370954a23391ca374048db7a172958499afb253797fe0e05005bf1e48e300c1ca1ede5651d8190e68cdb1e72bddcea0b1419b48b47f66ab96c494f63059a8cde9bc5b14d2f77f9684b455d8b15ea115f24f7f212cbb583110947add6cca91546fd5bf0ced9fd14b3b5cfa186bcb65ac6bcd84908d080bc56254ffbd8d1feae2b13c42c0100ffa4673b5ca36818c8d07f47d9ff8812e30bf969a6a183df439b5ee2a60cf0845f3fbbe3ca0a4a7f5ab78b82dfa6bf344d37cdc9b052369719ddd7f3e94ff45ee1e8820bfdfffbac25fbf66c8b022a97d171fba92a8d123b18e858db962942f2a87d31a99ab0a36a2825383c22afbc0046defe9665ce87138b915400b9ffafe8883d79f2613bdaed1b4c3d08d3e6368f687a74e38d6594013c6a283fb3d0b9c32034f5c7ecc052227c5ff9f01937c6efe32352ca370a180bac57bf09f8ca95dc5235a330591d377f1a0b2b9db8cc958491c2716828af538d0ba491f5a53541cc7f7e8967281167fff94e5b11142a76bd65c658410804cf81072d19c11e893a919d73e75dfd4c9530a40ee818bd0ccd25924fa4a4857a162c17d50be171fbfdd98eac2d3050756e93204fa1f92a0b65e04671e06a6831a8302d0474a0e847e0e0545c9147b3b50c5b17cf2bf992dd5c57f5678fb3dcf11f9c0ce3248e20753f1fb05e88bbecb36d808f1869d95b13cc2e706fa527f879bdc43e000f3321322578cd934593ae0736218c36c2f7209480e703929471d5e34883c2e1d175251d14d11a76ceb7d8f5cda342399cd2818316bda8e27c80a03cac52dd3997e579a498f5b2f7a9679e5e678398ddbead25f57f31ccb823449c45118e1df878e07647981813147a9925e6ad5d7ca32dc3c39f1f3c4e19688fe450c74bd74544099ce487ab7e4a4a981b85f5a8ce70135cb648f98849bbf03f42c5f7fe86928b08b0db90fdd787ec548f4203dcfcba83fb4b05c1cbf8eb760be8d9030ee4f407ee2caa0116f31a6a71dd94faa5cf584b54bc7c5d8f38162c24f908e77ea8daa61868981210b62b2eb306fa2cf0aacb0d8d3904c2b89ee49673902c16fee4582778fafdedb3dc9c8bc2371fe012f22439076952637db7be0d1826e3d848ce2ccc141783f1a1e164b86aa3b825829fe3851019c8d03bb110c5db406e27f0fa089e29d27b399e6397edbc0f33b66d18254031e67b7d6be5d43bed631fe3ea59f1c05c3042045746bb4ef20f08cf39aac2b12b1212b9f28782a48584e2d2ec70ecf80f2ac2c77b8e9d3a98e88db8f2532e9f1db00e942895ea6f493813bedf8fdaf2c0c2c4a48f8d74d5698829d9021d1e8941c190d4bdf213b1a3d285a0888de70b624cb1207571226223d7814a94e36c8712779e29e1d5cc1568b94a685a687288bd7cda4aa8495a24eaf2d0bd29dd590d3dd1bdd1f253fcfd939b1e773db7771a4ae0a778db062f94d2314358c9abdc8880b986802e91368c56fe5abbab829cfbb783a6786cf074e84b253a79d5c89580e423a207d6dbf925bcc9f1d681a59dfa476f9fc43391c049dee37e64fda0e2112efb38251be457def7c9be2ceb884ddef18a92eae143955b30546a86f447fba8673c0d25f949a1ddc3b3fb3a69847e994c32d173ebf17992d4a38319c7639557f260e35d2eb0de8ced669eb5ebd25ff5d5611faf855c861f4626e15e8f4b424266edfdd26fb3dc186c3fc655d781c56af4a0e8a9c026403ce0ec78c09e10178aae5758b2f9e6752b46542e482305d049d4d02cad23d8887a72c9d083371e27de86fca7318688ad2223bd6ca0d3f169c141533db5727ed14e4d3477d8e0503addf5a7e9653120f0484a789636aae9e954c53c6363752cc5cb8109d61390c11d57490d9e8dc41b81563ecdb7ff2636ed5e2a17c8be21c53554360278d6e69afdc964025c3f3df2187a8bae25c338e2ea57fef706a8bb3c1ad3c1724d4db5d87113069ea6ebd01db86df04fad83763bcac7cc460acdd050ac68fe8c25b9a838311fe3c86cea9d8b60af090f8bab5dcb68315e41e99efe5ab17ccad0ee9d773772bf26614714c4d7e9763a1ab2d6278ac15ed50b840874c89ee61ec23302c28f24e36f315eb9c37173b0f79b76ef5cc7f3e405e3f769d9b881a819c4c104b727674c94ec69c818799442966606420199fd94f411d7373623ce0912a336d4fab5d8465ab151184c240ddff7fb57464bd1e8dd5c36e9b5fe68b672e8154e511262d0ac5b7ca4290e6082598e7fe34123ea73802521d81c6e4c45e5e83c772430b56601c6535c08336a1becb8fac5f6d9c8f103da1b7ff986ca5e5a1d2d5ef9fce051a6ec5c0aca9a0cf5074c7b6cb7fe92b50dfed8684a7cfe36a6d99f4fb7fc99694cb78add4d1dbeb1539bf7aff08f2192168ad36fde7f9da324cacb232aea691ddbeb59e187233cb495d6195bdf2538dbff82d07a5ecf947cb04cbb9bbe998da4fd98c4d21ed2a3971f4b003d51db68cde2bbf0655c19277e1c820067bfd689e16291b8721517ad81d0379b4cbc25a3f299d9fc95092f5e5a4510208c0d9695b15692ab922041534c4fc37091f7bc16d642a7ae2b09db375f33cbbe0c9708630eb294d4ebd57449c236168fd9f2f45f27b6662c08da02f33ed9758d23bf0133d8f3ff52a4aac9cc091e9ebeb203dd0f1c26374fb707003da18996a416bf8ae773682ffdce9e63668db50edcdc601ac295a35ada9f2f175625564362f5e130398f4d63d509b57dc7fccee269488ec683901eb5c190b461be065c59c747eb02caa288da4dde0d9047bf1368bee29ad458bca8d50764ce5c7edacb3ac0b89c7f6a7e51aaada992b96777b6e77488992dd49597f9680dae896ffa2c8d5e821b2cad1348e99a56594b51014ab905b979a1d0f83a5ee54d8318fcee260f84c1ef9c7012b17640a6aa8ea61fb673910a73bda560edbc30ae3c3984697e8d076dd98402784a7fb3362bd831aad6909663bf60cbd151c0afbdc4243c4b4ce3ddf44e726863af64b2a1326524135535d81b608b3a8e186facde153ccf194891a4255577c477cce97f6f35054fd2d78f8e4642d7f88d4bac49403c607a62a663a30e4c1e17c364548b8b94c7c499f01f3dbe1ca784f0ed478691096532c6e3afd4f58a9a817f5859ac432fb9b511d4ae5560d8dc79ccfddf5a18e1c90c6458452cb9a3340bdebae9b86364fe4e5b6c4144ef6836de0499b20844c955610dbf6ccd720553e9e6ded683772ee422099c9f1cdb69e4989ed5108ebb403d7a32cbae94073ed2484a9cb47d95d6e19a898975f4a226195c4d96723feb756f4e749862dc7fff5b0a8eb910d9174324d91416bbdb8d10d157616e56cffbaf78921fdd88b3c5ec67bc18def0d4b47e8aa5d1210cb4b9fdebada741dd83ae990fb9c48dc8073446ea36b6a1f17f68730e9bc6abaf251ea36af2995627bacba06c7f4ab75947f00a29c0c374c96d4682c3bc8348f7ad4f5e75cee8886a1cf713a40e9333bf7858e33f69058c809648cf3fd22edd59e534f1b2fe5b1238e8c8ee06ff52bca72c620753d5757f500d3cb7d87be7cc3d48110ff430977fa24c1ad3bf0de22bd1d38b7954f4ab353e77581761cc5e74bc90d2f2d56ae22892e9edee8c7febf29b33fcef0870d44a0c95e1c5f345b7d100f2798ed0375d341a8256a65c1d763f0afee02153d96d72bf59f60cbaae786256b019e4d5ac1cf52f93b6190b3a0d6780c51d6aa3b037f5028a3314d931e76fe8ea0fadabda20012f2dc879c7899eb06dc55f246815e0b6b0ca0cdeefc08e64c26242cb9b926546423d75f3b7effb5c31aff22c338977dc1c18e8f411f9a27cf8af05c63b96d87928d61961cb9d041ff13a925c8e212799c3c22fa32dab14b9a0513d1efb954c8bff78adab15470ae17c72dbb27fe7d2eefc581c5c6e19c7226c2e71e147971908f5c4a9ea78dd3ac4b23b4f15cb1b92da325d4f2b14811d481c7ff378665531dfaf4b0ba981c0e79c44e5006226aff8dc1111ffc55ca984584170c62978dcb5e02be3a0ff4e666de21130e5a08dc04a57e680bbd7efc5afc8006c4ef819cbaf6697e559ddc5f525f0c30a03cc503a0336e2a03b2d6ffbd29795a55148bc9c27298af8646c0e6eef28b3b30cf0293ae200e99aa426aeb96df057a459fd1832f434e537618015b294af65d9ae127d179a0196099d2818cae76e0787f5e928d5f4297b685321fa2bb1d82dfc047f503254c5d17fa4582af31e8ad4a92d819882c4ac154715ecf861f35a89c86bccbf77ba34a75df5172e4d86c8350fd2a12ef2c9b4749289e3fff4e745a561693138b2be1163c1e5f6c55c29a360c04490a92185f405c05ee459b5c6b4a6dee3961bfaab860d9d33877bd97469ecd47776c5f72f13c9533cdaf1fb78c18c0c5c3b503d9f4bffe13f5197e095f3a3a1c9084eb046f5cea4ede6e64c716c8728aaa7b52996e35b56feaf8d356afdd1f64899ef4481c4ac43f01ff99e28ba83136b4e572300f6962dd16730a50731b33c93e6432a725adf2f91f2ff5d12b917f82ba5336afb4fc1faaa6a7bb6de4a8a7f23be65a10f7420421157b11271d34954fa0726a316a1836b27785954ea0b84400ce0a08d5c8f80ac144d316f3f81b8ec298f8ba5dc9666226bdfb6be2aa607f40390033fb34b2a1866da770e1daab11441fd60d4ca9e4d44db81864be65ad27564854d9175249f2bcc36758edaaf20f20d092efef5ba3a979698f24c3b550e68e85d5965653b4a4c7846e1591004cd383a4756db6dd37563cf464f63cb213c39198dc66c1f3aae592c5c868761526eca6f3facc5ae056f250d1ad0ec2f4569f946572b536c563a873bdbc822745a1acffcc2b855a42dc44fc15bab6d945ff42ea478d8779f47e4ca1733edb0cbeaacfa8d09c5f1e9073b94a7d3160e0434e8d3cb23d8612969492f82ed8eeeba627361056288c2035ebb7354b53ab170331dc611c90ac417684f176eab1f97f73d1bade70f7f6a420a497c749fc491a294aec34db146774a7554467fac8f6fa6c3f64ba380f7f064f2fa6dc5a0880abf5a744276c455ec6739f7e56290e83c2cbf71cdb8ce788f8a1e88cc82217126bb4aa0053b3b8209c4a7abcf78f30ddb6ce054b278ada88704528319f2b45ee2a98604880b6711521571e8d0c00cb1a7470346d92699b9d026e820bd951802bc33518a6fc549625a78679fa806aa482b50847d6d63e16a527ed80a963cbd84c7d2ee7524fff85ebbf3e7bf64e7a96e0aa9374f009fc635433c8ba64b208e6f8b4bd4f03587eb65f635346807a762f30bbd24bfa06b2c51eb6c6d9f75c94b42da545d95239f99bd941d585f7c2e302e104ca463932f4158209a285545448f8ea8d9d291d9ffbe453ede61c20554845e054a0a0e24a33f5b33a60fc22471ebc2ead294217241ba7d7c07576684b603f6952f2e944067fddacfacfbf0f204605bc56badb6573d0dcb6ab14d6b38d8d3cf5ae5f2992ade8e852d27f656f8a7747f07aacfe17ceb8239de2a017e3480426bbd19733d5c77aedcfd8adf6acd563b50fecbbf7b2a6a5a67d8cfc573e52ea8d589903395634b4ed39eb68b389f606abc59ce9c07ce1c0b8e06b2279cf8f59cb3d663044e6d8887517c53eb29b0bfc66b7faad70df0813147ce08be80d833ab964d55e9695d1955389f36d38a591cfd515727ee342cd351d4dd881c3813a097b283ae38712bb7e9eaf1ab602199d23aaf7283e6d447a9e816eba0dee7fe6f8bf9d631f121f7606f07277452df08157a1e07b254ab4225cf00edcbd75e988ab172a37c49f637038dc8364856a82bd600e22eaa874268e6350870c311c686352f4fe7686efe382727b9050538db8df165843d7a593894cb052f18b7d903ad6b5d92bcd6991731c6071eceb4d7c97d3e48b64483087eb417ddcd4297827edfb054eed918bf1853704d51e1580d912718e4a88b2b0398b489d5f929ff35013299800b72064727ff0d455cf18723fe56db69f60c7a09878b2969fa77556f5470ed713bf0c7eb0f4011d32637cafdedd31d11a6b8e2b34304803b28441a30072aa66d3369fb6a0427acc2ed00905b56ba31e92a496fe3a82a755c02cf1c2d92d3de0c45a856feea1ea646f50968a235782b95d5d61468dc7dc79b20cd586cf11a661533da0febd96f5476a0d066deca8df3d80f71884f26c3802239b3f7b7870b5c133e395ee4b9badf1c9b6ef59d4ca0b429ab0b5259aba7261d85f811e32f726984bb4e3c08df38b430b3fb3394015ffb094a33cf21a6735276f501b37ef5607831f3ce182cd6c67bac716314bfa3d5b1f9e24c58dfdedeb9cf2e5e0bf10d4c432b95bc697f8da27759f8752a3db960d142230d58a30047b81d78126f49345a920aed77be35ef8e85901f4c4b565f1ae4d6962bf0cf93d1822345b9579d51323d31d8b39fccfaac2499ba55fa8e5083d53bd7551b2e7a89ab2dab3f61901a26213bfdb167ed2c6ab5c14aa4d066990ead2c5e1d76fb051de06c2c3146c3e68667716d67ce7486b3fa883462e248bdfbacc9fef4000c1aa3c161edcb01b1fb6c21df7c1cadf312819002e150011644eb13d27a7a60cc8a4ad915cc3255bf7915d23f762ed16ac1328d9bc1e23bbd413378ee81ea31bf3bab7ee8cb9b1add89b35ea6603838a47de55132aeb26f6d935d608f77162435130d3f0d40ba989282aa78194a0e268969157f58fc996daf5c3762a31de25cb44da0887ac2c4fbe9f4eddd8dfc98edb945dbd20045cd56cdf5885516c820100688813654363652a65a6fbdad17ba5df20859ef363f042cd066735a29301959aa36f80dd94af43713ac9971287ef568f45b77465f7a76ed268df7cad32d9b09702cd6fa14d6df5c87da31b4ba68c26ae616af5645cc4ee8dd0c8f9d78964b9c0f8b4bc6849e6e3e8fe9c1dfd06462b49994e52c7632ea82bbe28b14776aba12c5ed2d2ffd9ca74d1225e6a5db2f287b24858e4598ed4c88dbd4b5693554cf12ea597b8d5be5c7ad88eba7a1fc35355f79dd05eb1984f9e4761d96eccee7683837a7b0ccbdd334f79fc53ca36f90001a5f7ccdde432dff1255db947d0d50fc167e099f14c22d2c43f6f143133eb6982373d53913e6327ac71bdbf9275d7e975bc8af651d91e22246d5c90577dea465c6e9d0c2067b27edd355a512959470cf8348d75f9d8bc4a496a16d36dee5329c0eeb3a037a23f303810dee6cf26a2ee797caa9c435824c40ef35de5abdae3ff7a30c77d2a6a165c4ceb61e7a67f7fa45ffbbae57de331fddbac8730e95c1a64742347355f5ad18803be39dbf058e1cf5f16cdfa5ba2e909cf3426a88635a9359c438659ef6daacdc0c220406bd5782d534bb16a52f70a0b1ac9dcbd3837ee039bb7ed5ef4ad22d1a159f635cc0ba12bb65bf5638b88eb0b5917aa241776a1df329d429b44ee6c756db6762c255de4de35f232b154cef1d174b6db8b889b1984dd448ad53099a69fbb7d133ba3b704c2610396cf1052fdc2ce7c0eb415bad8e13a4f564d5ebdc3ea7ee13c64d1569a3c1f5cadbeb6862eec91317193f97d1287793303f0dda1ac023b45428261ac457a9116ed2cc0469eb0ca9732a1aca781704e3aeecb62d4ccc732be9ddac96a4c5dd410880c85d75d7416179c1a2802efb1ced4bc58dc6043090c890ce5808b38777465a6e84c67c86638121dbcc5db5a2cd6e76f20f2740e3847a9ae298097adb16301fb1527bd58a317ff55e376d9bc8ecdb5812fdc02e3580b484422597fb4ae63f0498490f432236deeb91a6f60fcdcb08ba4c2a3e0a3a48bba03f344edf635c63accaa79ab8f63bd6f361e78b5cf7cbd0fef8efe3eedc550e75554a4df5aba9734c9c54edbd265c95a8f3bb1be8e082db012abe0832fb9294f45aa8ed4afa2cb4340bc6714ef2a8780cb3ea8d91c0ef05148e573636be971a9f0837d4fa9594eeee06c297b27538b235afc050fdf4c37e747f5294d104d6fb0b768f8de5c0bf52e2dedc70fd1dcaa7fde03390ca0ad1ad208424a0fda2cbf84b4986f07499fc95f64428239a6c55dc86aa9ab3ba7ce0e3431a028b4002050bd060290dcbbea6ff7ea0207ff446c4ae9a8c451d13dd314fda66d2e60b3e341849675eabb48ff75bb396a2f45569ac934fce7dcd953a4999118b72a9e9b2a81a32e87ff2cde1f3fb7776760d9b300497465a467485f3a44a77a5d4acf94f9effee5c19199d3ddbd07fb1ca685eb1fb6792bbf22f2b00eed8014b41683ce1554c7739cdf1085de7358933baa95e2ac0595733e3c4ae663ddfac837aee4e9f774ec523c93f01d2fecc07bdde4a6ace669a7ad3d11313b08b25a04c265c04f00b6009c76cca4c9cd806ca5acb2cea18e3001181d6ad25c7e028e223a60fda2fbad79230b2f73ece22d1107562edafc84fcec0689120a736f799cc08b1fe50353296960bbc19d2c40425859a57709c2427dd505757a126a42a1658980019efdcc1ccaa41f4ecc90564c30ed84190a71a141c75568bed93ee3cf93c85e38d771ce362534325f8a48aff6adf6462c5b1846857a2cbff39d5252f09760a1c64c37053803adeaa86458fb8912eab7b2adaef331f9277017710b419a32d1c8ffe55d0c39570561e0c3cb34092bcf3af7c4e1e4287771ed0fda37fe83a5bb5e4efd01fca54145e7a55d6995c1b2108cf06d1fe71e291d6d07e5d35ea35a50bd9c6065af2b57789f461db78ce426b02c98fad58e4e132b62f5c434de828b328249f0c12a6c48486579dc2f4b4b0ac14db036f5a57091f16a8203b6e75a5c6d73b3e7caab7ab83f73c5320e6047cfcccd311c269a116a38d99458b54585e0f52935d54802b824d0d7298453a4b4d15b2e0a9842993a06223b39e49519b02e7060ee1c8b6ff1e3e5ecafa2856233a7cca4d28774436254ec8e0a8bc141b4cdfbac18743f565104bd711179589818b458a4e84e808f0360b05c9c00e4aad66dfcebe3271ab22a0b9e54dcbc20ade30eab27f9ec127a4a23572ffc6e641482fb9ad62b69c6a9bd711195263a4299fa31e29ce5822ccf49f6eb135f385101efe01fa1baa98ac00b16d9d208d8d084832b67b2fbdca4e4f9244eb2ca6521dbf9fa19629b5f391de0fdefbfa7a4b28a8b959f51cb521d1ab790c0a4f06695c17161d71e0df7f15fd93954e4067376b00569a03e402a000d6457346a83abb131652269e4818a03191ca01ebdf657b21180813e4965a1749d8a242707564465a4c031727911462966236bd6bfcb81ceac107354c9e0b9dc5f6793ba78296ff255e47bb7ccb15a616a300c59ad05b4aac117642cb77a1255fd95ba78a3b03d958c8fd042745441474f7ae46de21164b3a1147ce2240dee6986d560554d047c9694363e5f60ccbd698fb0f89d29fbd2cd1360b0360d821b46bf30acf2103dd82c223894bbc212dd2d9e9427ec39a95591784f6ea404efb957122844a8ace6377bb11989012dd7c324043fc767aaea5584280d6dee96466c38cb9e0efaabda509bd5538e4e9ce2820d474a0ad0e48d85e314e73f28b9afb33cddc386d6dd86cbbbf903b7162594b04b9f39fcf9dae67851d814f3ac3e989ac558a452139e71fd8bba941f1a0fec53e37bded21909d9c69296c428d3450a063c19c7ac4fe39161296e4ebb37d40a6fbfffe3b4fd7a25dc08a6b260b7b6726691bada0fc0a05d999c6c02f85ce05979e6e290be7ed423b8e5fa8715ac9e9c2f3e39399d73e19f85d04561de29d1a840b64783c4f7645c9d2978a1d8e38a2620c57a9f54d8d70ceac177277043b366fb1d10ba8a9006e4124261a4eeb3cf07050ddacbf41e8bfa139d49a5ad826b48dcd30f3960044a964f1d8bd2a965e83eb2b386c210d48d744867c88fbd9be5a56b5125120cc34552cd8851ea8540825158c6b0911045124cc3274ab6859bf15c8848212800887989315f1e6cbb33cbfd7d8cdfe68ababf500b9d255b08381edae9cc9cdb59ae8497c5ebf4c749251c7a97199997eec02d93dfc61245785a3d3885045da30912db49d7e33189fad50ec13b47dbc9ce9fdf85b1e1c8047ef2af578814f7caf5612e6d6deeb77a19041e052f03006c46d3c90d7a6b0c1f05877c45e5b78359264c5e8258b108ece0722b4c8d39dbe3763b8c25a457f15239e3a7bcfc42514674118ac2ad4cb4299f5de7b9683501371379d8cb4ea5fc619faaff6b4978a217b773fd4acba1435b5fcb523846b2630dcbf76e3755f4f3cc835776101c3f7caa95e093a6ffaa9579d172d794e5b42c239c8b6f2e41b1b78cd693e5452c4804fcd09d4ba29318a120d2df69b3cdf3de000bcd8dac3022e6e3e8fea8a219d809382037aff222ebee224bfeae4dcbfb774fd5be610222f2f049309c3f25e46901942383599840138700f3064e395664d738e92bd832715e80a2a56d2b89419241f2e9169d5b24af7b1d58f7edee9d9926fe16c1720bc6e19599902386a14d38b5cc12943803a3d0837275c7ef5db766c47a6b454924473452aa78cfeb0fcc58c659de64c1c825a746eb89117d065d321a841f15f3666661951777d9badc791489d72286a3ad729d95e6545a758da5e58accae209b9e00099a0e2b9863db3337903752dead1f4ac684afcff3111baba802eac7873d0d68fb12ce9b6ed949533eaccb40860b38c1dbfe60e509373631aa279c64271152bbf8348e046be9fcbe86cdeb91b2a8ee4b5203220951172ac934b59ca3e18920607a9eec69e219964f21f0612a8a4fa25e50fd560e661e04bc6bc15ea8fd6eac8f5f3954a374a5873bbf58eee19a05d38a944a69b20fc188f95c651b7ef665d3519065c26471a340ef42a3ceef5d6822c7f42d9c61f2affb9bc3a30ced203a9feadc2d5147ba7a082e75f9e12196b946249330d696727d6948c7f55c78f888aa9d7eafcc5f900baacb75795d4b066b7eb75b00fc2c235124a5d172f857b1808f87abfded8e51338ae494856bb02fd181bb183cb42d459111b60df6a5990c138d859c4c17bf383cf0da9556a827de39427ab426a5a6d111bb8c725e601e7db453f0a37a0eed2e96fa2c9ba96a5e69a0d39f08ae3aef114b7bc0ad39a0a78e18fe377c498cd5cf7977873d07e33011acbac9e1b4d0a2019f78eed33aaddbe5327e295d8346525e082436d3eb423e3824ba1562213ee05a01f711c35c122852747b05c334d2d0d2c291dfb89fce6a518fe97116242f98548193c9294d886b7b1d31f8361d81a3bbf11594f2e35f1ce79e907c6020f4dc85549350e8ba9acf3bb077a55b6d8fbcc154e4b8d2d2faf7ea1a6b6aeceab6daafab4efa01bf87b47caa57893f6b702e724a2e3c931e9b0f597c33db7701f7f8de13a5ea6c85ef5db9cf26cd3e01559138a31e16ab1f94ba389d3c12e57332d46d053badba6c3bed2b020c484df0ec17e5916b074a57fc7cc726fb0bd73de48d10601cdd2309e3f623573a23e9bbc5ea6fc40003556c62b1142da837a4aa0ebdca80262ec01001e8ec1bcb17c0dfcaef84b0850838da46bdbf1abcfb3000b090ba355d2370339d8fab4dcfc88f62b3c898915084ce5ebf1289936bf61270ade5fb0234b69ad1979a7f5b6150b8316111c29e85107da42335be37346dedb40931baecfa7fd278e9daa92663e8a03db12ae63d5f97ebd47d7c7010c9c8e6c77d34e9699ebcd8af02823147ced5173f9ca8044b44e1166596e1ddc0ea82189c143dbe87d91b21f70577489df8660cd55ff7562ba5d1ba0c6f2ee9216e9471eff3fc4d03960f3e1d2e9329883f554fa38b69232e38bad86092b0c8d90178c86a28aac83b83ea71ed5eb3a5de8bed33443c567743f307a1a8e26d0ebfc7ab606cfa1a9789058a049ae71e009ef58e699ebe50bfd6a25d0b0c39cf6e889b8ba76de971634b1cc5f80ee7d9968d0720f8f71b45db72045c6e110e937974638c7cb3f6b0c65e35fdf5863f4fa6fd5c90d67270016b8d4526fc0d27349dc5da20d053e8be37c03141d136e0e133a554271a3f3596c6566602c8c107e16a0278960c4044f7164459b50e0c4f576c5e611ade0ad8c68906cf0a7b82be2e14a39a34f5913c5d21fb19b4c612ef8de636226458a1d39cf29d9914a7aab91222c1cd5d107568324b43caad41ec094e0486d5a95af9f6812a0ea1da3a19944d53f0f0826508716756f822dfe8255eabd87cc8a7bbb22993e73e77e28e1d679900fa763f5b2691412011bd8e9c786b16602bb43289ceda4714753640f561bd200784e86c9a6795c2b60881b1af6a83ecb4d485114351b52e9876cef117c9761b886c0da74c6f5d76e805266f615aa3b57ba0a58ddf6b29c236bb4de13af8b1390491067bb0cc7568abc94bdc1648903824b71812af8cf95756027966c1c9fcfa6c2a8023146dedc845ccdc6742f4c836f68309cc953dc36311598a79f3eb4232301c07b1f6fa800c7675ef326ab8ffb34162f63d6b890656183161cd962f4f911f3974fd78dd0c527a00e7c8ddb2e5be450f334e10ff2add24900187ba5123e88f7c4a8b0b5f85a78b81a12daf52c3bfaac804544af8080092cc5bcfe7ead19e0fa34bb0c8557ba10ffcd167b819e9e5f414ec43407b8db62093beb210f9c16e7932ac44b0e498241ed599218eaf71b8f47be7e3efcc1b0f8536611c957fb111c645b55bfe99dd0b3e6b991e09e2001263117c12be641f8938a3ddf1846e5d6dee4d40e81f772109eaa40e23f77482b8b7891fc28cf208fd12adb1260e0546a28a5bb29b767d12666bbbcd6db3629b07aaf1f27cb8a380714d0b4905884c7836155df5d9dfc9573180364dabfc8889923cad2748ebc23e44282f4f0b050e2899dd74ad4efe0936296fc1bd8a3f451fc3b378b27fd57f03adb70882fc0bc0bf305faf9b89bc9f76fde1b2792643e5b6f4966161bec212f1a34c2f5a7d704cc1a369c5223301df17f797754e33d67775fe07f5f5ef0d41e7f55f784301762604f9d8121fe085d81e4167cb3e6609a852baf907e019b8e8443847bfb627817478912b3fa3e03c5b3903b6cab60cf9e94423c4189320ecf7dcc4396bd3fa02c3c9e4d8b9ec7150906f3117ba5eb5ca4cff7ec2cf8a844734564a863cd921f5d6880130ae17a8656c559accc83a188410eb42f68c8922a5893dd6e60ee5b5c0f86563b9605af3f5b01e84eb56738ff504e52d239c1869675bdd45cd11cbe28951b94856c78c577a779cf1fdf6cc4c2b4e8acb310e9d6ba7e3d9348ff2cd357a3e3a6780963a2692339e4dc710909fd6678cf97f09203565ab0b1403fcff1b96c940b490a434a2f5289ba25695ddb5cc89eb91b4af3f16a63cfefca4cfb5b476e485b566a09e84e25adee372ae16078c85cd51325384ab177c3130c3cd3f4ba64629a7cffb087742b7bcfdf734dd6454b1a49ec7bd645f71fdb43fd8baab3087cf8fb365283643a93c3b4fdf867bee9b4d43a07b90a01300a74e35922d69b334289c2b53ddbf4eb6d1de9738de1988a3727cf3d77cea47ef05e83600f736f45c1976250024dbf78654f6cfe8ee851e7d9f17dd1337ab343aa9c5eeb41f6005ff61e4c829dd05b3562313e57a9f20c7f20e0606cd73bb8c6edc324c0121cf051a8d6e9592ddb9de71e86c38bb3f7b7f8e113ef5a1c8d200a3626456fbb9907cc3a1e0d09cca4de2590e5b57cd0b4c1b3d91c360212d99983747455cecd9bc33f30fc36b34edf7281daac6e12259d0a02908352bfe2791bf08a83e50731d6763c9ae1494b0e51a3a7b40beec048ba2c59daacf83ff72d0e43f50aff48d5eada99efbb01c9141ad983e2c415ea6df9efa5f887c756610417bf6d21ef29c1dff51bcc98d75ad53bf389362fc0b1ad86f13a9961478aae265aac1973facb7642f03873f27c48a0211397337c9c2dfa518ec1751541b0696e856b338b0296416ff2d8c88d7a2315cde7d80e61f0d8722df18aba8b1589708abb5f3c48ab20058be01815e4297460cccb0d1464ac2a4976850a6dd30feda4f8df52b2ac0888920bdee5103c03eb3f297bd15c29c1bf1eff5b283aabec0968553056f966d5c320765354adb6125c4554b2e39967c9bbe5d0def21476324ab5b0149a8f9fa94e568669bcb89942647c308baa86ec52f15799c8f1c546845ad8f108c7032869dda23d23ba45d184a07ed3a7ffad686dd5c6db68000cee8799405117d28e692d57a55dcdbecbe336ec9203e58b9682f8eedbeaab54f9e7fd4f46633aa27ecc31c6c6c5b8594a65aa9a67688256ffb47e06ba4b49de2a7c76abeda9c0e8ae2374d01cd8f619417ce152493cd5e3e4b7a1bce7ea4f530cb3136a15f6809b35599a3eb57adb658df9a5f26e5f11234d052e13bad68a22f6283c7982188d89e4cf9de5ba8d7db704e8d497dc666f69fb82853578083b9ae0846b1b7fbb4318b72f23bf7c0aa503c231ed1b87e58e5a601a248bb5d24cd2199579daf1d4e2ff18c05e07318a771854361176bead314ae597c2569b760800498c199dfde4a7b496359cac9c563cd54199877373dde482e9eee7b4e8e041cc0aae38d91d935446e87919222a9573027f8879fcb1e5a52461b08d1efbbfa30b9b0652c6a762cb318058b628426fbca2f9639b5e526888ec7bc6c388b1fc769722652196a40b3b3ef6ae93760775b99263e9fdf0ab3bae8d1b2be047407d8aa763929fc3831b78aed2d1887f250da49ca47ba04840fb9d3f6920c96799684fe94f8da177eb5768d6935160aa182544b1c971f61afca350790b5b3c3c3cf0889c3b74ad3f865debb051c05758a2e7445873bad4f5f061db68366c22ba5a03d846ae629c9a8abbbc7621e7fb5d10b1df869a0b9e7cc098d1357dc7e43ebe9d5f9a6aa9d39aa708543263562133e067b257ed352827bd9e14c7af6640c0508c25afe770bc282d6a5f9e037494b7f275042a96de307fbed1721854f3da5bd28bfe601bbfc7428a2ff105d32ea1f5203463984a18bf4f58b1afacf292b91aef95a82795f6f651dd8957e6163026dc6b2dcd13f02c4fc33d6acf7c0920f0d352a9eb324b0b501e18264065466f3cd244fb5c3aa90d065bfb4bf27c8617f55df64cc1bcd434e23a4a79c1cb5a7cd70a1593b1febc2dba1c6a15e7b9e2f80a1df6e1c4123e46e9a516e7b7d3f4233dd5662a1814125caf49f53dd97723559756f70fcea33efd10d12b8ff2efb698049abbe3cb2fe4f69068e7237e5fce8da36dcc6c9efa9baf4002b16764c69342ade0d8412e5c161b7694467ff8e962e3bec6b67a8f425fd7dd759be223c09b078fb3ab746f08337b77bdab7c8fe9682e57ab8e197cf74dbca18e8f89f1aed8ed678d99a50a07c07f091435b690f069193a9afeccdb18758061dc5dc7b33f2b7dded9ffed7b0c7394d9ed2b75e7f79ca2853a8c5565d3ffcb5ee1bb5a640d3c0c01b569be88f4f3d0811340844c05fff3f11a1c7eb6944ea907557dbe7461ae9e49f7d08b2cf35175c39eff89b37ee1c951192eab55d9277f9a34cef48a7b1b2780c5e89323f6d4dd305cc7dd315b2af19243dfd44e4ceca74850ff3d33afcdaf8d56ffeb06621bc9b0c20f15a71db379ec2ec5fe55dccb32503773d1e31e9249362e776528fec853b5c32246f86ee7c304e2a4bc9f009284a9e6e101e5aa2e3e52bd8480b203bfba1b0e6814ec2814ca9d7b6938917604342735febc96630b1d6dca0824fb5497ffcbd72aac7cd12aeed7e241dd5a2ff88dd1125013eca890b1e868821c8b119ebd846b0a67cf087d91d647e4afe66334dc558043179f74cc3c1cdaf2fdf55726a0912cc3ffeb03074d1f64b0995adcd5d06744d0cf25a3656d88fab2b2754c8117848903739ec306423fde30866b58db123b5d585a7a19e87ece1544486c25ba04da1565799e61ae9adc7d21580d367b97be407133e705e611c937daf44e743a723a4a43de61a3aba20fd5d7fddcc749ca2cbcea8a904552d0672b47e0b02a13f549ac3a8eaa465ffc0ba1f90f27f2671a550f90453341a77be29c98827c78115499e69da867b4895f2c051ea67baa07e4d50d2eb4acbe263ef7d109d28d37b728ce9a7a8cae743f866a24272798882bcb8e953f0247e7675f72b5c9c0327b74909b0e70b48548a02039351c6f3a3230d93fb29a8704acdb9ce47f0df567856627cca8b74cc72438d293d86dda62d64b5d1443b79df52b348b8fff79a2b8d6c1993c95c0ce7ab07c98a97a583a5d4cf23e8d887d1e8d3e539b58e2ca3fa88fa0ee572adde70eac4fcf38f9bbc9f88b3da30f8f93f61d3db423994f7a58c276fb477707b1252272112f8010135190ab54b2fd5dbcfe3d9e3010d6688da5558883d50d1ca0beb7d6715badd1e970e5fa82407fedafcd077124f450af079f5e9f911dde90b5ed89a4d757b7fb0dc0e6bea1154889528c675a1e9f56ee71ec531cd62057076be31b36f5bb7d8c01a0c16bb8cb916e1ea7e3b0cf1c28f414f79c5cfcf352a03c7cbde15206cd633b863cba96e8f76a52c49d05da7a7a41699df230f0059e7f65e65bfa11bc17cd95d23626383e56561fad5d2372434005435b0fd407205e56791978b836eaa9adf1b48cbc2e284f4adb97e35875bd138749d888a88e6ef8b1749ad28e34ea7eb89f39afed05e6ec1d5f4fe9cf0c6cbafdf26eda84e1e380354515fcd571c2944471622e3c8f619d2fc7f9e8571d7f4113bc065ae4d9c2e3601da3d008103b772aca4927e145e52b9ff78d288d35412c58e949f38bb73473fb865a1af38aeb1076b0d06c92baf46c759f3f14889ed8f6ba9e380636ef7c11d25fe38eb6249a77896f524815cc1b565576515e4c09842b03bc76b2c94c152a4540d79467f0ef88931e96781ff1c7c31f6e258523471fe272fdeaa4ba7855893babd5cba05c4bf1cf21fcbf4ded9e3ba5b4efd9a6f3c9652b2be9086ba795d56733ed6098c87b7caf09bb0494ac1491eb54320dbdc2ea7e0b6fd6dca3482dc3dc3cc13ccf0e28f68d0752141e70e05d26c3775fa896dbfd70b011303c7377664b94d00258e70f22fc81fefa0301be37b02554d6419c354a3a81e1018e4a1158ef27a49eaaae820c7791d3bd5cad241da637c3c993aa15b30dbbbc572ebe61a596db3d3e357ae6df45f1f0fc41d1ef9ae1f01271f11f7cf3ba77037cc2defea18f708817c0b6ca30ec7c1a8149471f876396dcb5e3e98adf56f8e6d98a49a581d213ae97e83533d1d51a3daaa4cf856d1693b52e09cda1671397aa98c3a89c0dd932214e87bd1a91aca82b1fe5ed3cf0988889eefb815e522eea88efa44d17c678d62fd36bb5ca4788677eed2d4c5387624e72bd325da17b5182b9d7affba00bc2dbd257a8f6cf038ba3f3ddd941cb9b9a733aea4e88c0779850bd43c817e4ae1eea03a70e5f623a847aa58db79ef1edfccae7ccc6afb50a03e4bc3c9ae218e1ea696bc0c807ff8b547a6f5a4038bdff65ecf15935043fbf1e1cfef82ca4e75f048053756ccb62bbcc2ebece6e243fceccd6e45e16aa95af019395297031c73f279776d056b31c7249c1e1a922f51281d33d9d66d9b2b9fb007affad38107308f6ddd31221101b7c01c5ad11fd911bba8c024e7ce2c035e1a2adb47fb8ecfafd06eb146299c3bb553ec4a450f4ccad99e1f30ca5f1208a7cf6c4793a57b8467cf304c119c96e91246c0f3d7d2ab5667f4c096e3a4214bdc2949f20121f7d166bdb894bcb7988985dc6cc072afdbddd6cf13c82ca4e2093548b889bf40ffae711fae1123494ebc78ab292b9c16334024bf7fdfde7ee188d72d7940aa3ceea73f8197e1f212fd1ecb84d658bf50c99b22208a9a3f437319c2d3364f6beb01841b0096fc788f7101986aacb95bc6cd78ef4c62305cdcf2cc518c9b4e2bd818787595e1330e60f351c0e3052ce1fd73a3fd4101514b52b569c7f8391f712b4cb4c31569cc32f225cd2be2dc9c62fa1f0eac568f6b7c0f269625dfa0e3c8d6ea6828c5c20df2638b77a825aaf5c6572b5d9be02e5d95c78c3529da32bccbd5bffac73ebc96ad057072c78d7040682220b0d9ec3426aaa2849d36c167999b579f9b32f804200b5dea99c9e38826f8b8f0a7e67da4e5d3db50ce9d011cc037677b9b1ce4df9022855dca4364ffe707a49eb67ad870ea77dc9866ac9ed72aa56f617f35fab72a680abc50370c535a65e1887147c27cf83b2d4654709c652180a57938ba6b5cdee8af715ea3f938f1afc3e8e21d1863aae512e6d8150818a29cc84019d6baf6203877190a715aaee23fdf093b2e7b8d68d45fc72fb1e9b99738c21e758e16b10e72ee130135b974836c694fee9c7f823fdc25417ee9790aab5cf147336494779a8eb53c76e479b5b16fde4fac8117128db57f778873ff8fbd03a3bfaa7642937ae77d4ead265e9b092b4638f2453a2da96bfc52b0ffe4348ea93bd6859c664ada36c9cad3db9e3a5bb939de760596158b6c034ed5b96bebefb5c4baea266a8a9c989edf7238a1ed6f3d59f8e8bd57f90697f6cf4e2df9fe2378c6edc91260d70b8fa349e8c9904875a2e960887006a28a52d5f6ffe4b03b4f02c152fe364c292d12a7a9fbd3632bb2d694833e6ea743b40ff82b82b9234884c5f6df9e9c4e67cfda66af6d1936c1bb395f1c5258bc4f1126a109b85583e05b5646949799641d9170d7ef52299fa78b9e7f9c5621914b40a419fe0fca7ab8ceed66644d8e4c65b2769578eccca57939cc6f524f74b1050082220ef02616cea5fc5987655b5041f686b8b72c6b0698205a75e5f594a40fc5afff15f74096f9b1c53d8cb6f6a24527c87420e7568f1dbba8249211106336655ef500d66255038c7def7f80e23c3598af568917df7e250100514e58a192792dbf245aab75535e0c1a77f2c8237e43f4fa97cbd086fca5d524ec94768552670140fa6368348a3abf6904f480f2e0d6465d3ae9b45b517168715e9db678fe366eed9aadb64b57e09cce17717e9fea7dc86f08a89268e758e3d11c109498c6c58fa28e6d91b0c722eab0c5b362dadab7a3bc7804ca6b565568fbe90c0ff6c4603607d0627741e2a80ed9d9cbb4010ab554f96b84e5c042647bd17901d597e55c4001de2e460e8efd39d8c034a7efa4c9cd88da8073cd1b6c48d77afd475a9cd605ea907af3224ce19d8251d4c947d08602054c3c030b394debd39f29d92b3f6acd43126300d1b95244927ccd62957a5a9cb130d998028cdb30f1eeb9fa065e4a08de710e524627b0244d35a047c79ef5955ea6c1d64775144a2b9eb9b9d0df59bcc979b6810a008a1b564c50b04be9bfa925706c4907e961bbf59aca2c53ffdc965dcdf32ecbadecc8a45b58c9979261612af5cb1e604990bd9f6ca3534566cfff7dfb736dfa37e5c575754fab362015a16f543c1b5c63d7ae55439e2d2aa14f2bf9f526d57a93b6f6df945a38ababb0deed4215a069b6b6f7b6c94e4c82f220866028a781e62c6e1efac4b792b6acc954140a9701fc49c65d7b824ad200daf23a3c13039f5c8ea7efc2de6e2e104348da5ac3b4e4baeda205128847955f8cfea562928feb6cea8ec4cf3e4698520d067c27c42faf5e759300b6acaecfb275c5c0a5c35793fc28c26d34ebe1ee0b727a49766ba72adc487f8c6332fcd44be93224834ba51a1e32e676f1eccec6ffadb9e9ea80b01bc44ef51b958e45ab46c805b594d9b0f2e1de1a845faea224cd52e96f6c8020d99c736058c97fe133c033505773d53a64f97941ae0a43a20d67770636920ef10ee0a5eaedf69e02c00c9c121e12fc37708774edc137bfb58689e265d99ff5a44ac3462db9a8327b3792ff6bb5f5ef9ab290c2750a323152b8c0d84fbadd72b26a86265c9b04b1b53cbf752fbe8503c3d52cafe7be57033dee880e42f423ce799543a300743b461f7e253f125f8ef69d8c01fd46dfa998a2e869e49059861015b49499162032c104b7a217cc8c7683b5562dcedf5a2d27648fb0a1e0e6b50a8c569c4ad04185ca7d467d358e4cee27bdddd6e9d635ad54844873797217a0546eaeff5fd771fc436d722ef5f6c02500dfabfee3356a5241981d389c177a353ca49a29f449b3312374340f43c7b7389cb94e56047111229bfa01a485d851d2f6cbc6130b3aefaf16ca82200f7bd0e251b09956ffac36967f42fe46b9556773ac6e7d3f4598cc6a09303f6d40fde14c738a76daadbaadce2e387618a4f77d9ebc0ba1a96465ccf3c8cfb192cde7f7381bf696aa3b7ec3c63ff912ebe03784e02617fa90327b23bc8ad4b16d4461dac5edf5df309bbc32bdffe8db6ffe1790c7fc8ed65eb269479914f5bbf12989aebee5764d417de6b83e97bbea3bad3dfe3f03dc2a9dbb8f1cd1f370da5e7c48857f880cb6dc7eceea2cfc8ce72fb3ace5ebdd062d1be2b2540dbb1dcc5e8a9caf14efd4f8bbda67d3748b62d5314350d8d9b5a346706b03d3abf4ff65a30b8aea3797f96de89d1e401cf941e2a351dc6bc1706d93ace95e1efac60d2189826307abb4d1f096a90cebdd7c45a9b4cb56f95e880ecb4fc53abcf41806b1489281fd214449d6511a97d810c9a8bb1aa98adb2594571fb0ccf7bae6c58ca988784f6aad22a08efa32472cc856e4cb9c328fbbe055dd91d624c87dc6fe15f1e53c083261d1588802a4b2022eadfe1944bb1ff2dab0951a798aeae6fd46e00bddd35a6dcc8111cf6f98e4905e988f69769f803166b932c4d53e99da9584ee22dd8bf376de347f375294aecd8680195a60e2f0f10a325ed64db5a434055d1a6f42aabcbece846795e0ec36cd63899eee5f79dadfe73002bde009fa18f1d27f77f3803a9f04f151d2055de8c896766db09b6342b02c6c71bc59736225b540d51c52ab9cf62cec8ec1871f42c596932378009ec4255434ec7f0a5ec39de1fdcaa778cfa5e2985e745524c22b9e52235fef24f18d066827336ad99450e8d3610b6c909b2b13de72352568502f57f347d83e2dee077895b8c87533a4bf0760e31446da79c6c40ec05d07ce10a0fb5c34ac20a38acaaa2937abd402d7e43dae21745a538ec3c814fca6fdaa9a3ed80b415919b2cc09fcfd956309719c96eb2a8c889afe91ff92d3dc33732d1543847efd735db12819836e1304b8789cebddf81320021ece0a65b1195e8a5b600207d8b7b532581db6e533147fa1f085709b6c251cd1249d15394b4ab67ff394c7b7a701d54f3471599fa6c6293fec78def503be34f07454f34b101fb3cebb2198db273fbb603674169cc4553be59b3aef89955e2f249d2d76334e9e58cbab1b06516f56f07cd076aa77878b5ffd4213b5c186b3e1bad399316a010519cc9bf6defad685eb0f241d2e6015bfb16ae8937e6931a1b385ba0367ec0922dca0b18f58980296699cd7ccd8ff7dd06e5ad2e952f8a7b46a00218801e26a2d05848f48c0b248fca665ad45e4fb29f4a9a96943cd630e631c3efb34bfe93839db2d595e787d02a0e0ccf0792ed4578aab33a44f58fb5be9d98ab9f91f78ca62b5c9cae20b71dcc361f6871d307feb2efa8f215a26c5e93dbe46e019e50726a5135647845a9f0dda0be56b696dd7fa2c471c817ed62069c0ebb1a732af0cba74d2f59b3bee9798f43f29d23c290994d6b0cc8bffdd87fcc312e280aa59be6ebffb8381a84e9475022aaeba675baf1cd0ff43ca193f9a6bf4bdbce0503a9ff34ba7d0b7c02dd0ae55a2a94558a5e686ca35f32a3c72f3ed24048dabb5892d96d43d29917f23caa033d02d69743dbe5bc4e9acc12b27b3786477ab24a4173284fad966dfd5c1e04c46364cf724f9cb6fea34b262599c3902d385f9eb44ca4e87b26e6b7bf8be5abfd56764c283e3de4ddb04713dc6f3661df3b51b076bc5e7cbc862239f2c8a8e390086bcedd1ca4e6bab312aa4f81dbfcaebc132a5269d6579705ed15232cf44e5f0f9dc555cdcc08e73f0b29b41504925ef752bf1165eac482658ce5e4e6dbc2b44a5404f7e7a633b8d84beebf33db32cd089c71ffa4628910d7efbf6d345b4b4855b97915a66adbcd3d8ad85c3e4a494e47218ad8162cd0f7e83c3515144b257f0563d467d7ed0626678d6333977dcf74753f546ff5b780b381d0e09e47d674bfe9ba220a452d8b04f19932a12b6cb236e1b9449bc9733b83a1c43c62c40d3adc073df5501be349b1cc3155b5391473e38ba71714933d8be0e239dabf5903ece457909f57725e0262659c2f1708fff79dfe7b3dfd7c0efdc7ed4a9fbcfd7dfd260c984e29f5b7da1b00a9f530af9fe510e6d63ec7bc83747420f3ebe10dfea7d110604b5020b2f2bc82ce0d1c47537dc7c257f8c597ef54a72ca816e4cf1e01a4f07e197643b485d3066e3279b316673c5b8b5ce73568277b37c3f46b7619ae8ecbcbce21d06e4f67eb2f2a85317ed0e27160350bd2b5276b2aa63db9bbf549717c13cc7d6647b0c4398829321ed757ccbce8a9ed073e3d396b71ba6337156dd281c4cf5b4c67687faa97519e6ee9a383b53691d37fcaa1c72d63b93e455d54a9108ae543baf16157d9d7bc1de6c4946752a45ace3e057dee1c4b5431ff4ece5eae8cef73dae6c3d9d5727ed600e75e7a17c711142571b504dd441d3316306930310798d1e3b228e2e45bd8133693838a69fa9ef53bd9d88779c9f4baf701d967ba5a16fbfbd5f46bf266bd2550653f8cd4e9431ea1f21d3c67dcb78aca0a7746254fd15af6019e27669e6709d2071d6d1f8163a831a52995af5f04aa8effd0af87acf564a279ba05a8aad49b8cbffba34842007bb968e6718cc64d11d972a77b356a1927fecc63f9f7f2a147e7c15da916a23da8f417156651163cd9079384c3a50403af3b122c5585ed98d35482277816cd57d70dea3f5822eef3139d19a1dd96773bdd4910f9e6ad6d843cc49d01fe72671534f751cc79c1fd29041be01c637ca7d4e2b7fccadf02cc1b1a62ee6f479652f5b120e5f087ba6fa47830bdf1c8716b5a2dd7e751223faed21cf721a72d7e1cdb30f9f12f0a3a363af77def5880fd6e099a6d66787b95b3ac26b0f172d72a9be70d4ac52afc778a3e6c19e32a548d60559e7a79fa0e9de8e66365676f1cfb1feeb2d171cc74ebf60cc54d2b075e495c59bf55e2ded651c4d698706f12943a9f549ef17da3f9dcd9c95ad8ac704383886585c94c06de2699da6727634a92cca266d3ca43fb30b82aed3cfce2973d413a089d7389b5cc76c8362ab788abb47b60b945e2d446ce6120e6b3561b0f7afc873d5b1544c05e1c5f5a3da917cfd66e60772b678608539456fa92c87d3324c0fae52fee143d3e2394ac83b690b58b0915ea399a39eff2b8acfd5c612f92af514f61185a2022e47cf823c5d3f8217b547d8671f94f8d495753dff2256d42633aa38b70dc54d2b49bf811be8c187b58e2bf8e382364f8123029ff1d1b63a68c4040c4d87be1ce762c492e3c506469180dcb6194eb06314ffc2b093a747e90ede4f4437be08f10b83ba6fe0c7f071a15f9408798872c41151276e52c3786097f0e894e2f2578adf37cdcfe639542d8b90f39a0bc20ff624d6c10c46ae151e4f561cb75566162eca354f35027a74c6db1786bb670f55c571d76537ff71745a66e9850f7e290e3578b9fb2680b891bd8f629ea92903f1a038adcc9c87f1b6b8dcb03ff40206787d0ad1378748b2abb5a9f9cab7d93f50a4f5b94eeeaa4831ba313b02aea3ae6796db60cd3eb914d4bcd9232e2b60b291df484cee5a8df33aafbb121f887b887c98a3095cbc7ebe80662f01d19b96b1258328d040fca5f5e7a776aafae5b4a9259450e02b8dffa01417ef2b271ca5bb38f7d183e4750919184707c77040182fa1ea39e1ccadc096b58c410dc29d0001dc7c1c07099586be661832f3080abf8963e406caadfc0c101999483feea2a4894b7e38721d8e761a70e3169ab97bf7a80805b37e86d8801b046726b3343bfaf586d826b44b9c26655b1b18d5eee4ab87f21969df636c6e99aaa3d98020af0e9991cb5757838fa280cbf2a944f22f56b9656106a1985572970fe06be1183fa73d9b46a91ba426f2b968a82246ff0f9b95bc80f5b23077a4e4b6613bcee404950e82f71208cf41ac78ccca615ddb25d1d7eaf40282bcad212d156b02e58767e2f3d0131c187919477d42429f0b293b9362406c9a9fc73ac2da32c1342551f73f4eaae71c43c6205e32f057b25ad2a6e88067e831dcbab61ebeaae233f4e65d10c6d1b555df9336a71339e7fe90752e10c5b97bcb005b9bab32708e097e41e61b1bb2621f4b9ba792e59b0d3c86dda7cadfc96040e88ee65dbf922d441b600d9a3f457eb7fbc5f716404ebabd9334a55f6946b02e87781baaa60b832dbcf903aff9da6adc377154a9feb71d5c0529ca8b92a25e8ed7b47b895e347d9b3c209b0fb7745fefe5c3be9a1352eb8c4abd6d4239473212ea95c865512c0ddabfdec365a021dea506655dbac65084ce0aee2f2d856058da6506c399f6deed5cb1c3a32c60f727ccb5b062de6628639bcd59e4cc9b5ea861c152f87693a31da917796bfa656129d1e6d9f8e5d023bb958770866f4b1222b9ba7df83bc2633805987f8de471112f53c3fb9a515d061bcbf044bba120b52f56556fade796b04ebaedd0fc85c913468490f2531d0ccac3fafdf4d4f0df3a458bc3a19e7d54392bb737684d030e7fd2ed6e292caf7184cd66add660b3ef8e14c5bd23072d1c35e0d6146b8c1984e69341a70750d6f956e074703faa59069cee427e4931026a0b7cf336ec893120d82fa2fff04a5f69e2b2ad478eab2f9153bf8bc30da61bf88fee39ed07002d9d81599a2787856e1c50b38a78659846ba751bc1877a2bdd6238d001544a6bd263eb89810e5678d3f78f8b45e05ddb7b82f0b069d8e590a904f6a52fe473151821ce8236e885573de4110953872430f27b270be4b5ecfe611016defdf19588d059e1d282e8562a21ef6397f7af2c1fb01b7371033ef15e5a26b1c2788f746303047b16aca517bbc0e024c5b71250faec6e6bea2f9decf2975ce6a6fe11a9049e7bce50c72661add978a9c577497d988fc576ccac05859dae2f2c9a3f63c5ed5d64908eb4528a972aada5b1e04145ed3f0b6cf45eb13fcc8aa47835ddb458cf6d01efe90e815cfda631050daddbc63ffd3c3d7313b125b5c64627733945ca5d463855c540bd31f969fcf0fe9135deb07c2d821239fd00b9085bf5ef96e2a1b35f723767870d2dfb8419df8260135128548c35523d6395e390346e202e4ce8c7d9d4da7b88c131582442d662a7a87a769da34d22a140b933c11fc582b47c7d8cef0511026473b639c9661535e9b2dd9ccaa4a4842923dfb5fd316ba9a2fd837cfbae11a617f708cf035bdeb4c98481bfae677446f67703eb78d179772f81277b76ec837da2a2002c713e03aecab8c5edd9cb1f47e430ff8dcefa7cde5647669765d6f9fe47a72b1bbfbb52767e6393748ffa5317148ae35f458dbc8f821c16855433ca7e97fae770d19ec975f918a9ad40d8e6a7d28981abd46f0722155642b0b41a73017fd210def50fe1a234f38c6c191ce6886a8ff5264a2089023c92173b62f2d6080147f27ad77b805fcca89d0604a60bb421af9bc057f6e88275092a3c21c014c19faf361deb42e22118c7b7fb4350f4f23e4cca672cd2c7227f880d515d8a14736903431a6b445c0aa6fea4ab3d09b2667b5cdaa32cfe39d8bb12e144b3c71b4fa745b0752a6a1897b1a1ebe80a4c36af2b245bf62d42d02f404f1aeadace5dbd54edfb109ec63e1b5553af0e43402b56fb2af1a15d7902ba1dd856e53a6ed5f9d7a560c1acec2a59ba56c30e323e3d02785480b0239c7223a988ae9b57ac0fa8a841cd1b0c18451edd9c9f7161bb03c34df77d1c13af84c8eccc989e89df4596089016bed7905465a291bac3b3c6e242b1ada636f5d49c6636ded2d5289c9fad4659b6c64e6355f3234152084ea4b9e9c3003916345728c9783ae52f6fbc8bb991b9de959905019079e39022ea5b0043d7fd148953cadefb98e8c20c7157fd296f7535d1c03c293e68910d5fac382354f1863ce775f3fff3f0c6a34dc37060bd7adbf5670ba37680131305f8bd73282ed45f5ccb28157674e3e8adbcb6d29fe37cf48b3eaa19537399f5bd6c8d6747c96abb70bd67107690e88320ebb5f5d788b3062bb2d6442b9d2ad6656160e405d5d460ed4995228611c3f7eae8ff00ae83831d063b42bce88d757d0b4fb7631d99887045d6c0c326e2a4c20bbfd8eaae553c2111a1b712935c9599a19969f05069004ba7e7fc2e961db8d6c3bcb504b88ff507917e4f538f3aaeb6303c0cceda066380b1ec82717b40193eeb4c17796e77ffba702730cfb776f80fa06fefbf5bbef809dc9076ed6b2e64cb5c71f853cae235fa951d2bec7fa34bb160fe110d3421fec5b2fbcd1bd27299949db188170b1234b1cc67d5dd430cd35108826e20387366d38edc45f7a5ffc234dcbbd67ac3340ca1213aa642482332e9f1485d2379cbbdcf73a5702ed1cd787ae326d8af94115f9a9004b0785a6843e17c1e1cddb4c0fca41938321f75543c4e93211fa34ba3fe9e6171469675081f80197cb87955921ad7dabeec1c0501d6017342dc346d61377e016fc11ef24f1fe01567884868baebdddc4073b6a574ad6b5cbe400b749340710575426f5ed94efb9b2ec23e3bbd380d38252a19560c4917d368c23a1490c42f5f1ae241fffd8bde8fe6e199c3a05af88583663b930da5284f526a5daa2bc5b1627ef77fed4df69ac95fbf8092028a97b8b674c47907baedc231b7914550039ae4dbebc452170fcef48ce3baa7ae7478f9f50738b4f2251820af8f439d00e1423267ee71549634c94a5970b283bded23a8e7e20d932313dba627fedf6c840445d21c153391bf4ddfab2a86d667861406ef5ed2acda3d1836638f7487c591dcd25579015b4ce064bba8f47db39011b965752ed2c428b4be03e873de7fb303f020a1d3763bde86a0da6ef0d9ba203fc1cbd38055c2374803a48a1aa78ec2a2826a214f33d12fc56308824d5c5bf8df159b18aeeb65dba4dd0650fed7ac6f7edb6c17f0c73dcd8fe670275955bb14f17631e534f3a2dda75bd85c2491adb21421aaaa30129bde6c257c1989b705c6572fba0ed8d3b9a7e4558ea6845485c3076c96d613205f4e4b63e1385b495e60ff84b498b61b8672f9d09ad7af908df23be655ed41c729e6cc0b0ecbdce6383b66c90d0eacf038f2f9cf03c738f52ed4f04f5fd9bcb63f003b88772050b4f8b14d61456354848e8976b5eefe9ed43251233bd328a663f5573643a2bf922eb561012ff48f0b831dd7b9ce15347c533853aefc64c683018c5d6892d4c43e1d8830f12fdeb73a2793e6b9cc9cd9b4a40f30d4fc60e8384a9a68724ea5ab934076fcb80ddd1224ab52a5e49022c1ba989e5e756d5ca19bcc582302193881b75f7f9c07a90734e0d56d920b4b58730790baf43e2cfcf93b550be4c1a2325774c492a15169bd730a5350c3e668c7ed684388a40f7b42155f76c37529cba5d5a617f735e9d5928a61e95d00982fa70fa2b275f72eb685d3e0b19e3c7344f35001cb56bae5dbe201763521ebd2e8b8d0ca593fcfe42c2e73cc1723a69781a7298bf8021a7609c7ea35e2885f1ca287db3e11b68ef668d764cbd54b62e85dfad1f85f0cf6f58ba4825f60b083daed5d461f597cb7736ca20f3c4bb8b58ceb1be94dbab898d5267abc448fda8ddb5b7a85db29134a0935f7dc0ebb3680332f7ae63498ef34df7d9fe6b2fb0084a96dd3b0470c1a493a1710ddff6a45be3dadc91190f0b8aa817a1318de7837053e0138e3d18e15c5884800d9dfdba6d9a1621091c371cff8e3dc803e3f3cdb83324d799497725fc519b56ce91a147856635c48d4e9f91944e73bf5891b5aeed5cc7820fea4e08fc47cc32c84202bb3d88e9307202e7fb49cd74fc1f0c080bad211b6ea3f6abc6b0d93d8b0b335ef735623224aa19438bdaf0827dfa00f0241eef50074f4bc14d1f7404e2266c0ee11ff2ff550d5fdc137e1e9fc372c32cf71d5c81613b07ef3c9284f32dff4c20fcb569420829bf4c42c57d307c9053debc955505e0006b267b85adf0ac4a4b1cd2712bd482387ae5f27a59c791fa40acdd491d4472c0d090f64dea37503c65d97985cdcaebed5c7dae59833984f8c99e67dafc29eb647d6b74b3b48a9b692ba1c74a2a15a9a5dab525e607fa51a31a29b61d0d38bdcd41164eb35b5e91ea35a5275630decbbd4db81d3fb13ca856dbe313c24dd597b4e44d7cc2f1dfb06a61e007221d59a0e5a2038f90c1f20898e9733d08ccca114a48c74eae6d227360869401e44fd57f50d3256c4cbfa0a904366afe9dc45a8d7113096e293b8038681feb20c4f55fd8abf6bf55b0632fc51385b6da574feb41bd13f8757161ced43ff048e3c1792c26fcd33d8a85a8bbff3a28d1d4fc74b055c1b36f2d1043a2fa7dac6dab84d1c2292b7f239930d2cad250ba696e69d5797f7d85fb93109694952eb1ed58d9ae383162369b33f05c26b2fce876eb09aade7a2f90e6e1a2d2d9fd3fffdceeba124f425d6a789475f6e6c26fe01b74ea56b27e6b3e40f9f6e9020271acc176e21a899eae8f408b68e8690def3b4f7c7dc58681f2d101f1b5a2fe6fffdb9c8cb4b34a6c738b35d249d2cbfbb23b81c77888364891edf4b8f4134f6f4194275c029d3c8fd0d0186f96d01535a1ad586774474cfb1bc3bfe649f6c27bb2fbce7520a40e8cebabbd660969e7aabb762d3612f4ec47857b8c38c62dd3936ac87c75bfa6b8bb0807ea0ca53f94f0b9f2170b902e49296e3dccf3ac5e44dd920837c58580ea8b3b05ec4852e818836dc11e269880ff391275bcc6b5210288daf8b9fe3bd829761e781bdae24aed79c1c58468877aa41cde5fe7159891f333af12eb97a120752062c5db18269d946072105aa2c7cde31076b6fc8ce55745360186298633cecb8c748e2902dccce5acb04da71724d153cb7374c62805206aab37d7c4145667a4645abd4b0164bcd3c1a3eb064fd2eec2ff592be5214935a92f9a691ec681b1f6d23b6c1fa198dc8617de5d8370a445b7bd7b5ea4301c8cabef62a127b10f6fc49fa2f9f90a6450ec75574b010a7daf81a1606c69ee9b4fde596e4efdc239daf10163fceb89bd3f0e3c5e36a185fcb19407110c904b097997b68d2fc910dcdbc0c2bd4c71d4cd3015fd3859be92c2ef87545dc8a791f2cd05a40093d7d0b6380b3f102f1d4599d224ddd130ce387e5a9f13b788d966fcf1b19f8b2c8ab837d548d23410204af765f8a2d0e8ef0d8dde106ee1599cfb1505e133aa6c4f85439ced76cc9ceaa87b4d5439365328681c48a262da1fbd1599e603583c08f1f26c4cf472a34fe3627bc158153185ff0c2b0d54380a3b48467a9acbef371b52ada9592d9bfefd9700fbd268f579f5e7973aabd7129d8ddf28c5b4a2ee2903a295b4c79d94d46cd979fefc307ee206bdac58abc876781cf75540fca9371ec6b6ec8086c410dd70709db68b7acb0dec266f147017968bfe94880683744cd7815ee691682e831a177fbd06db370290e422b76171ae634f21e2e4aa7850c05a72a5726f0a46cbddd1b3340de1847311aa586c609e627775cda3e20c066da8636bb35ebf39d7c74239d25f898c11bc5695c3b4cf8a12bda47acb4a0004e0d65426d46e75641e0740c67aaacad35612b94a797dde0554b4266e413734f8fc3e5ba91c7896242f72c40e5d340377eec334ee862f6f3e94b92fb1c582a5d0484f68c017899d8fafdc41a64eb6fc7057259b6b8a2a3d1ac7a4f94fa8cfe150579e9014fa95604951ad90a63f70d8f0744365cbf2d0ad4b6f25cb679e513100177256bc62327b91c1f82ff4d64c7b9adf5d2abbc80ca4cf676c09465ad75ac131a6a6ecdfd5ecbc3c9d739e99118345804302d97c6081aa4cd9ac240398f557e1a2a003110187d32e4222cc7f2e4a933f10cc2c768dfc173bc4d2a8efc2d34f66e0a15a0943df2b12011c423db7317f8257e0674449fa9c8cfa7b6fcfffa164a2c95c1d398354a19273ec7f3fbc34c7ed7a6548ec9eb4d7a58a1ec37275296526e752a47a0f1d7ccc91f2c3290fe3af5fde804d5c39689047b9b306546db4468e711a3b818f006a6c355dcb453e86f443027f20252be2f65e2e20421f3029d491dcac8765db78b463cdc3704f0b285a553aa2fdd17cf666a130ac5dc4bdec1c6e7b447f023ab58b0eda46fea737de25a49f06127b07c530afa575cb1411be628ab3d1c7991dea4829874027de85f0786267ec7b59d6cc44d041b500abad017b32d93d8ab8e31cbc506c3ec5f9264cdbb0df8fb19b94d351e521e2162b4caea804cc144581f2d2fc764ab7f7eb471dc0ab27e62a406ae5da0ed3a68765759be17237b1424e0ef3ac42a45d300026ab40dec04ff440864c6a8f6984299f0339e805b3b330fd794f6c5c5762d5967d22c59aea063ae4d3b42b2c0b64d005a2098d4b7f43bcf7615b4d5e0f57c31cc0e067a8d548d3809b0cd3ffb64c72e49339a35185665a32f03694972b85a335760fbbd73dabb70818eb298088b5e5a3dd10975568cc6f0c233f45f394e02bb3a40f3d4fc38ec040d7582b00e834bd793a65d68b272e87b04fa9e6ea903bce2eca00e5692f7ef5ea34b8eb4b411a8740cc64504f7dcfea2129187a38c44c36d7e68c7018f461220837d6afb87b7934943ed0395332905ce7ed157ea202eb91b911de8a730f1bfe10bfbeda28ed9d17de497489323d7ea6a989744e417ca4c8a9241acb21af95defe68424eeaa5bb434b9a44deb25e2ff13ab65eb0fbd2150bb8e51fe80d71e19058d6f9ada0e4402e19085181247e0e282e7c3f23bea0db2d3f233c5fc3e9369a6aaac957e20651d9d5e58fed88ba0c3bb98c35c96864b0721cd8edde616205e152851ca0a51e11579c8e2f1f26ad4d50799d80809cd8c3d5caaff22b4f05d1e97a122f30d2ed3c923f4e23161b84f42da8b02a6f79341115cbe5a138001d265bfb94f0048c971cd3b30f4a4b1f0b3b4c1840fc2078fbee2fdb887865dfd5f99b17395abb6076ae73d8c92ebc546ebf55bdb78907cd7234ca30eb8fdaa37dca96789046dcf5c8f546a541bde72e1d4c502962016b55f1b87e431f23288fe68a3a76382330cf277b9cf66a9cb4a648ee8d13eb011316cc068abea05cd9ccf10e3c4ef2d1d520eed75aedd04cbe0f20709b857e89f1bd4959843995609e49d6b465ebd99d1182eddc287d4a57e19494dc178acb7c3e4e1632dd2f27f8b170bf2128d1612b3163d90c6babeb28457b0fc98346de69ba1a130c9071d9cbb71898581b739e6d6abfc7f67cfda52011c0403f55913831e77cd71c9d9061ea5d227fb6fff3193c2f93342e880f30d2a699724f439320699ee55d14e1597f128545ad126513d9cade0f2e561287a2f4e2ab4c83ee87d9161f23dfdaf7918573b84fd1b6570a99f74b43005bb7f906ba89064e794e5ae82363f7ec6e71066e66d0cc34040f961ffaf9d9b6bf5f618ab7be283dcd037c9fd74655dea61a479cf46ba7155c5be9905c3444174081ca5e198fe127d3c73b477a41dd3f18fdf57ac685f0bb4ac9f0bb6e3d9de9c58ccd027fa9891889dbb82af860673aa4f62a8bb0634838b03d167c1f96c4c1ef231c6ec647dbc6fa89b4fea032bb206d4a919d9b439daf4da989e345392c6d223a769a50fc191d0c010480d8421f73b29f33a5d4fb0682fcf192cca5005d0629781a981a94fb5c6a57483b6833680dfe34d2032d5ec8cc09d03b5c60cb1f55a450fd26aa58b2d0af40e4a639be84af6a9dcce58092463ba905e2f0bc94d5165dcdfdbfce7aec960b99d507451a269e16b620a4cf55422824f688a626c8b093255ccadb1327cee84c57c54c8409d53abc073104fd98efc7c8dc8a34e6de7723e4ffdb4dbd0219bea0806c44411842a114fa2056754727a7d4e028938a2baa83c5e5450a58fdd263a63e81417adfbe5c4b34c79508930ab9ab4d47d61e8079c747876b17d1ac2d32f81f4dc7e90f39760aa1f2acdd3e4750deab7109ad8f8d1359725b684a59951490e3e1b5bad4f8460eb6b4d61dd2541260b1b2d664b5eec1c6793c31cfce815fd47e90487b4c2a5fdc681134ab5046440271e80523375a34cbe1b3874db41d4f8f75020b424a2e2445f7fbf55f5a191fb19f740d6374bb46c80bcb49ebe795f81330b772cb2a37c393f3a1c9ce15c3f05a9f9144e56f71db4b1b07257ba2d88ae98d473a4af8f65ff17e9534fdfc70bd307e7e3464a372d25f2b1e6b00a44daf5551c0e4143f64aedcabdf9a35ad6312df30ec76ef8a83e3e24822708e304960a72932968c47d0580fff8614cf6a7dab991b369a959bfbc410c44ef083b293c5936683d2a063bad4f91f18f7ec7310e7ec20ff0907e65db60b7295de6cc8ef4d4744ab54f7648777adbf2337527ced34c1e8407163d6eda54083caa5008a180ccecaf61e1d6dd25f0cbff6cd0b523c5f2c3eb19672fd74a7dde79786d5722fdb276d73627931d2ed45d6d3c467b29b5758f242170a3c245b6a2fa20e2ec909a8d68817895aaa6a59de0033a98ff6f59541fec98a09eb8c6dc222a5c76f6f8d5cbe96f573e1df8bdc52f93054bb2e0a25483e116b9bc013692f10b2390226c89eed1ba335c43fc2cc035285ef3e4aeebdcd644281f6c6bafc283bc819065cc99cf950b1c59c63d9c7022d751b31968613a4d1a559325b2da1dfe7e9af8616bd9de01c18af4cbd10c3772a29e73930789839cead5c3137c90152fdaf6875dd33d80bdb3bcfc1040fd89237a36434b357adf09da58376217a4e109b69e96f5086d4d4681a1d79f407fa4da068ce4a682dbe3aab5be53f0436c186d98ca545d45cc08d11fd63828362602d45bf3333c1e5794a5d339e92c883cc6c5392c2fa0bf11cd717fa4c4c3186b0206489b9b906a51221596b8f6bdad9324742936eefda8574b4cb1193308bfee6b5be0134c433bfd4f1cc184708494af0b3a94308bcdf342b6892049df6ec3864e1d71a7663481b19e3f3c147deef9b2596dc73fef1309110c6aa292f08f5ee853a2a305726b5a0559517813e3467f57398e252b7b6b0c016ecc4906669af4d9857e25b2bb64eef476b2c106b8e86ca81b1c1a195cca03ae4baf7c4c0767a53d5dd765f32c90bf7f4758eada4d4a78c15711f8b701f6882d0d725d803dac38447aa6e4eb775595375934f0af691aa7b0f74b55d7682c9bf632747d7baddbb1984dfea7f72a3807177383d089476d9e91e4e4a573639b821955cdc369f96067c7e6af49ed833f1132ae9bb18dd9daa36108aff4e78bf404ca4b18b14417f8e6256616ba8221b897f96fea89ae5e48196a5ad8151079d33978be6745f37cb97e7ef99cf8eb1d701f9cdb6c041d126585c57d764cf5cb390c9fed7ed0eece925d102c81ea29bdef2075ea4b6bc0cf0f4be67c28a0e698847c2de1a8fa5b8498f1296601fec896577d70ca2fd1d5bdc414dc2faf52c30bfde4fb50f99ee751b5478965a53da0555df30efcd7eac8b011c2a7212cffff2842cb9f46bca0b4c07221c9a40336675a444f81e0c87b99854bbaaae82df3c0b33e5bbb25521703ab0bc57b35c28932bfd762525a06d46dcc87375ad079c50f9f2406de7292ec878c49a6cea91051afb3f05ae4d922e81ecc468afbc8d5d8645654eea1fc98b334b52df09c22dcdfd01c3e54522bba934a7478d850226b1638fa3c6c9c3ab6d11a78007dda19b814467b7340d3024304d63c07a9f6b8bed3645a9bc16ea41ec4bfd6ced509c847abf46b147df7275205fb9f50f70c57ffa1a65b430a2655c59df56e6c43a6427184fcf4ce42d8596ceffaa20192de75aceb7e3ae24fcd86092f190e0be7dcd30ec1d7dd95af13ec59c2c9dc4dc53fc390fbf6fb7f0eb28c5f4f49f48b6d116147e095c5fb7f89c96223665c55b50de9962c76df740f1314a8b39ff67012c340f519e11f68ecdaa7bf29649d8661e58b9fb66ed03dce507981fe48cd043459711517b35d0a9e4bca976f7aba3d4635a713f2c027003adbce9f74f8c87e21da58e98cfb9afb4fa31e137bf15663754038209441bd33d98a022e2a6a641aeb9abf74ee4a631d2f0d26dcfde0f9f2abd86fc96381d2fd83ccb24d5e759a7f80679d5dbe1033a3f896386c7f59b46a9a5b7e87f6b37368096ddc9c3e54a9ab6af42b595854c1ed478b782fbcd6c7cd301d25e5b1d494e2e98b0d5c154f3d2466d42ebc4106eb4860ad3a6f7410687f7d14921655ae8a745bc5905d76ab79a7e582f2351dfa74616b038ed1bd46369c5711fd0f40d122aa17ad3f17128b205703a1cd87fa0069319f70eb095c4425200cf450df57faa1ccb8dbdf1b437fe530ffcd20af4c8cfed5a9bd5c297e0760d06add90e2dfffec0b71abaa1be973a3400384840a87b0d45b66974a26baec03dce603efbc3813241b10fae189fea07df25bce82eb6d29768d678a52968bd2f9d76b3da17747fbcd2e345d1abfa1bea742342b90fb441c692c206f15c23fa290d7b2f5aa11a6bfe1ee24c03ccc895377e147aa54fca13b2c6b60d60c9a207c847570bd1af583781f855b56e7f6ffd2bede13929722ea53c730c010a831923a7ae11dcca29b1ce2753e8458b8168cffc6b583140eb147462ddb7140f84b546e5fd0223edb0e36d96e541835d9b81fcaef1e5f68bc5973ec43394318472bf8d432fe2c8cd702ed051d42c64c8494645cafed355a5ce51a4d0249db5535ada8142d3c76251ca7a7cb06ad2a03210c4e8d21475be465dfe08e0b18d83387711e01b6f4467811d69ddfb725710dcddffea251d2b62957f94d4c1abf6549440426bd08778d57a37d0593f184511e8b2b18a0d1f85a1854b30e5ffa4eb194216dbc5545058732f7a511135a0423eff024cde79918d7027be68df4efcda506f4a2026276e57459e79797f5edbe0f7e2dec3d068b360836d66f414d73d91780c0f7ab8fca78b6f35d4c7313b433f41c31945eed7f74cb757f4c2649e8bf53bbf11173b505e53310c6ff303922a67bf5055bc47d43b04ea1e396ad493cc1fa69fba5894736fbaa928a490fbddb59cde3eee9a990e340b60128156c9378c7a074df49ad71bd3d2deeac54a3a1eaf18cebb210350407c9f4841fac6fb194b83abb7fdcca51f6bcd42afba0739c33a6b11ffcef9805d011b431ea5f5ad6820fb82ce96b3f585a8f3f4dcd784dc9ed0796883fa63f804d2d0406a9ec1b6b431a156175ef225637b7c213d8ae076f6a9aa444db714f0e97341a9789a2c620efbba9492d818fa0e49f69ca5d7468506477d9e7871b5c6b026883ca04073903407eb00b43c93e58d14235a65bbd9b5c553fde08e92fe63b752ab02629f177633b0001b7acc8c39f4d7a4995f74c65691419b9e95aa05ed6694a45c3561f11fa186f229fbb4c84425a689ed259fe26121dd10ecbbfc48908f8b3e7919872e46a1bf8d03e2e437aad43f26cb02267321dfd16a49ef31c40e34a73f66f0873f1f1d24ce14c7e11053793d1cc792e5c6fd282387733de766302a3a7e8896a74d7aa3268124553ee081c339e40d43f71dbaa3fc919326dd16ba7863f05fb4b594de86406ad904cd0fff2b62ee989d2858edf3dfa703e2fc3afd5c41a50540bdfec7e3bc049a1ed4e4e4819121f3b01127136ef5c13d30d040b956225d5f85453a27cbc477baf8b2e65d89351f565db895d9c76299e806f14799d90e125f5101f55e0854629cd5afd0d019f1647b56417f8c6ad62ebe28eacd76cfac2721cb139727ecc974ab92ed75701eb7e061902b21a153ae2fa1d7b0903b497dc347c9604bd4aa7c77985c83b81f6afdf0479c2e6176bbc798ada915c6275cedff3435aaa3ef11b31f718d1646f57bfece4d7e47fb1f9aa0e5f185858447feec6c79c18d51ff98c2eed97a506f61768863cf7746bbd8ef5dd91b29b234e33543826d651cad398b2072c98dcc1edd21cad49d0aa2e44a2c10a60fa555facecbaa0491232118fabf0fd109d7d5349756141dfeeb38481d3944e1d38c69cb624dbe507affee9c3c1be31be908d4613ebfc2a28b3715a90ffcb99323ba2da4ed0e416c81c343a23bf1038b23116af01fddc105338a961aed8ddc4300a7d50ab564243c8fdd080890908856ecbb6ff113b2f4773fcae0db6736509df3b197a7217e23fa5164497ef9f9c1af0d2f7aab13e20ea5e1b2c44927c6e43b12ea8634795587596ae538a2e963d77682eca75acf522eeafc62fefa8f825f9eb4c7e09f403f9d436320abebf7b67c8e7da7f096c256dfa9b0892be57167287463bd760c43b1e97ea80bc4c5200f935c8a5825ba53a73620dc06ac169e1fd95e95f01047985ba54e76763e22dc7d1c49d91019f326cae6121b44b196dc320adf4a90469bb89b9ee85fc487e99bd9a3c76d4b62adf365ce8cf75e4853eb42304598ec30fee80b2da7eef316da6e5150971901b0c5b82c546efd695ff0e35397ac907a6bdf4e053c5c6630c8cbddf1358769b1eef91cf4b56df379d9c9a5fdd675ccbadd3209a57f932d86b19e1c294efe782ad8618435374f7effb8129298c1c308455ed8dc1a1df616eabe6259a9994a2453627a14495c529b2a5887aa2c389539d45b27311ea7ac356db0b03be49df5c2eec20a9d5b2643e636feed7107511953e510d8c7221ecd22d2a4117208f7eabd1da3a505cc279e30653216051b47cc7781f50b158dd97de62d241705c5532df567489e035ffcfeb3a419dbd1e4463a6b1dcddfb93f46e5d4a3945a13e0b6c3236383060c285311a80862bfd863094e90ecc5c56bcce3d98abed36abcda79605d7dc1e88905e580f40436e225f6c8ecff7b3bbb98ecd820505ac9a3673505fc4869e6e48d2e80333ed7c6a5f638eb216608fb8364e6961f9ff43463eb806b46f12fc97824d104a1e78d17397afba81c55d4dc48b8bb6bd22787eb9e03286220e68319cf91d0594a9557e8faa56444808f8c012decf459ad0691e508504f4b2356f191280db8831b9e774d9113383ea3311c1198da1b2dc2f2a521954018907530b58e89b9d93721db1917e9958e11a7785a9c9b8cd8b28b081744492aa73a97a55afcf876ea4678b6322e1943cde5646fb46330abafccc72194ae53a37deef350da9368b3782c285933fecf4cc3956bc837b7b341a042aca0de85f8a7348ff165196763c5f505e358d85d06a9eb96d0938e8679c67bbd815b2f283ccb11f974c8852e65171067ab24b565fee409cc80902ac6ee4385641cfbf1c5a6e2ed3b4b8fe78640423c5b3ef4226b60346b69fb8d8eb26f6add1a6d8fd4d01246a989e749e4ed7ddc7652dce2250e89de2a71e00b10120b67db9bb2f1b29b886d835811874ecb229794c8f921b3d8a24d01823e0207ce4a4af30394548265115fd2ff53c3ee2e21abd5e7954b6708f078b7f5d0f47e75f0ea8a16fe6e82c27faaf2e3bb2166fda8b6e287a65b8ad1fbcf24b4c17cfa1595a2d274c1f142b4fba838ef7145be9138e4382f1fad9b5f9be38849942039e733102017c967af69a7d6a77284e83bf80eb19674d4f76959ebf39b9833d7aa306e1cea36f98c6daac3777fec28d3852091a3b1d574f14d7a6e9c0df7ac2bd753db88fe4ed2d7a82a041dff11b196a4fd71b0d1f42a70fa17d4c01c64365acbde5851c22d579e1ab1aa3f15ed2ce35af1a0c8a3853714e79197b1e3539e94baa631420e2c66df91564cfed429e7bba1e46e487e29fddb28d47174754a60dda6b805f6457d4d0d02cd3eaa21bd091d927953974718ef6aa9f9096f7f0b0b84711f47d0c4ea32ccb40b3e0ec2e5f04df96e9ed68f6aae5789fa38de8a96dce87f74ba8d74195d924e60445ad10130b9a0fdf6a4d3705fe599bebb95fa95fca5944d60e76f4cbd4e76920af03073d4dcf41bae71c7fc873d819aee1e1c8aa63db9a1e4e0cad0864864dc42c497a81a1194a2df5928253ef778de29ad0ec415c014a9977d03a3f968c8925ddd7646857e094d2bcda2e2a5820a04f33cdbec25d1e0f5e16779ada5cbc02db9732605fa929749a30875b3e381573067a9e0efefeba2a8ec21f78231d4ead16e5e068a30074489c427840ef881bdd2c2a99b3b1693707433b19ac9b1779e236f586dec542fad7e23f0a105d451151956050455a045597345c36661d46cf3aed358bbf2f4fcdf494c0d77e52a9eeeccd6eb671907e5ba081d0d1138c67eaab84eaaabf9e6d587bc96bf425fe56c60e130d602ed529917f162e066bdb466d7650d27c31ca10960fae69702ac51e25f9ed9cba59dfed250044bf2d09002ffcbd8cdc3c4e371bdbfec7d7fa21dd03f2c6cfc5abdbf52363c8546fbe9de8a9a8a45db214282a38f02df6fb55e73ad7577ae143f4f65e0375d1d6096de493b6b04cce54e07d4ae675d4870ff39318b365df5ec94e8fe89db2f55f69a94682ab7625f677e4013942140252c35e9d23d4eb03dcfebf3be07a3e6816ce8c86a3d9b96475d1aec950a6f035e838e4e5fa0bb779e29975be1cd90a2aa413878ed4e77a1a35263909699d9d0957e6070b988dc9d40b131f2c4dbd9e4ae86d257de4cf7c1606f48253e429e672c440c67a8e8a4814e521d53822ef5d86b6f004b79ec5e0c56c6138af5598bfd6534c0fb53a1dd89dc601fd84cf05323b7b7d818429bce5a61c19eaeb12a8d243b1fdee29e6dc8fc1e588a99282ba14a6cd3d4fc4092cac3def64c005e4a587b1fbdef2a07a8671b8b5a387b6058b5767aaaf590d2120b515a8856b4f6ece805075f1f3f96f2a28b5dd26b04959b42a3bdcef63c0e5ac512bca6a7cebe28b062ad77b7474f968c9e8e0c1261caf0daf25e63c25e99fc2dcbeef5275cc483d66df1a685bd71a252ff95ca756ab80cd842a7c3bc067d9394f9cc190fb550732ce5abefd6061d0c232583dc0ff08be64114968ac23ce0e6fa445e13c241a07843e0d5fbeaae20fb040e7c72207e6a6943fa52386ee2b8a3df0f07c36c1cc83d40affe830eed1b355ab45bd012ede5c69e59997409beecdb95cc5b9549bd873c86c7a75b225d74b7356e017b24b9d60073e5648ec0eff1237e5ba435acbe9397e4f59a5760ab77b451484cca2488d14d3ed45075425fe9d5f5f276ea0b794bf4cb8443a56da601bcd5c5b50a25113b4452d35d6014f5d9e0c9e571060182779a3096b15506f0c8a7646e4dac11cde3b594b962858b87b05a0c533e0a13887f363fa1b9ea0432e3ac116a5655adfb2d23d84cfcf3f6f1184e22ad93adfb86571198e15b9a46227c8d8a53f711491f2f7d4e9825123be30f5b50946494df708e50bd0a8bec0c0f3ef7a7b02a52fbc0f2d98a05b59a7f8b0f870bead305c1330f385719a73d9aaed91a2101359f55cb94007b5d3135cfd567dc2e6e75f690a129fdb2c9d838e7599eec7961d67be9c669184209c8d87ea87033e604a0654580efaadd6ddba2422cb4fdbc9f0edd1488d4c601c9b23df356dae4d47dca00e239e8734a5c356bfe27193c3f2ade8781072b0ef4bca206c95c5b194a4527a800ddf455d8e980d3eda4be5f5f28d2609683a22f431cf6272d9276b47c3ef845e65dc885e5ecae826cda150d4d3bf1507a078842d5cdd76d8f33fda836190744940fee73b68989833608c08ddcb98227394bd1a0e97396ead4d847029dc5e0df4e5af795779b66075e24bece266f3180773677ecdf6b322b783d4a4bac6d3f2077d6f43ef572bdb66a576f5b26656d58405c2b0727004208b6beca318aeb8568e6970d488ce4d992d22ddaa5da86c6b576f2cb30aa0c480625e04c333d858605b674af10d42890832fef1198471073d9596ef022ddebc55148a8ca2d39cf5b21919ea963fa1f537424c35dcbd47de0ed267317e438ede0313f3182eb2ae863befca38c2b71090f26a29f19a27a24709707c7ac41d59aa60cdbfb9ec2849630aaaef85e66ac939eae644cbbcd332a73627d53e360b620614e857356a4984011ab837d9e85104c746fba76ef84ff25e690fd345aa201d9b1d124777dc6e8d2b20cdc5a1500377815f3f71393fabb83f04e6654843474c2d506fdf1d07883cce04c33d74bfed0ce757ddaf336648ed143d9fbbcce45f5958ad7a65e00f599e93e131a0aa20abdf6a1705f6b8fde0f8837ec24f17512f0726c07f3d5e6bd4be98b3dcab136c524f7fac1df3f19e0b2dec565b8cae5a9ce97805371cd183adb9e0c1192ca2642019633e6a6f9941d6a3ff10ac5c5978bc5547e4f949d106ca4969bb24bf7c23b06d43bba5ff373864df6dc2f6cd8fc6e7cd7153a4e4a3cd98b229b95fb31f8f2ffcb78b413becf58cfae82b49fa9612b4bccfb42c857e6e1af7fc4831025c84437d84dc3b7151975a716fe256f4fd9e52b44a3fed0a6995a7cce7d56133bef9ec07579a69056f4767911fbe7c9573e6bd949395f405041ec6f1d7e30f300e4f9cda7f209aa77a42b8781920460c6eb6dd549c2740eee76de5c19545b67bdb7bdca644d8c83b2da2745a28331abe962d89eaa21fb095bf7f805d3598ff68a136ac3ce8737271b82c807c09b1a775484b93fe0cad741e24ce4181c7f3be182b0fe52faa7eb6ea1bce90eabe9032dd72d56e441ab0b43a4d50731f2764bc299f9e87c9e76d84dccb6f6dc0b01fa604ab8f16a3c313b5844c2fc825d623d9f631bb6620800220940d023a1fddff191def7aca034a61d730a5fef5286255d7910764acb4fe8f688c814f0a8b581fe650a81a65df0647907993036eb549ba69a3bb3ca6bd33766522dddfd538660ff97135de6c8da2f4b14fb2e20adccd796e6352a7db493d1ed4f40df3af296db5f084223750f082a36d045e9c51e9679ec250bb6ce0305f2b0274961fc5be5f77c360e1fc42aaa95b4bcc7b2756bd74b2b1792e06f7991cc5b41c74b40700b0da9d9fe035c30b26d1b53407033cdadc4aab6b52c7d5583e525057c709673b551f0ad80f6906e6d3b0036232c1a4716d19fdf013c737190d537d644895cc5691bc1d67ba19e2038367ea640381021481d6fb65019a572bca637401d464f27deec9a3af30194a0d93781270cfa7210bf3d4606c2ff08b85ea3356a194f27d5682ab2d59bd91cd9139cdb77d247af110ab5b00df3dc87da57b7601ddb63986ebad17d5bc9e1fe0b046d61910138593e4c8bb0db74f896d3df7dba1501abef7ea06d5c561b431d4809630034664e65c889c11425515bd56bde41999e7ab53b68d69382be048e92af6d38cc659b75d848f86deac6a7e16496a9a405481340226ddf5c1abd32c5a08f36b41519ffce7222d0895fb4df2dced5166906586b1b612f15d8601d7840adf9452b4b80abe92bab8d4ec0e456abce6cee55d94763770e3849ae0f7df0131bffd39bebde735bc65f1bd9fc86bcee8c146f6cac4b599b1d540a85eee502e332cb42a83bd8d7dfa20f06ff8e941c05d0a6b1aaf5e50f324139576fa3185222af2500fafebdda18281a43c37b1185b9b253d23dd28f4da3e7938c10a5c67bf4bbce06e641199299571ec8969ad0a93336efcd334f4d3bd99d01d2bfc278ee77c0a0fbc2fad61a7cc893b9dfe3704ba946aef84dd70312e0fb33976dec86acde757a2fc5e98f06bd26d406268b827c709013524a903d2f67f596b03f77bbc76e11d262bd9b5afa79930cff96fe129e569f416a78f469ac13777ed607187aa9d357f7f7a380395a5b5b43dfa13e7f91cad6393ee765c648793024cbcb0ed261e7d2a393e6ef726fe7092679e7cd7e7aba48238f6279aafba50b96a02e0cb1ed941266b6210acd0da28a387e01c5d5fa7f5c70df21b292f5de0e968be9ab388d6a11efde7ab236b014ee17f2626edab465e18f5dd0aef5f525fbae7ed8f4c16927f19580849c247ff3c56f9453956a759b8aa895dc43a463ba48cc4be6930af2aebe0c7e51a2d4ff7265713a3112be44b29fc8d76392c46015db280efa9c7357594f14bb4b2e2712b867b5726b0c7908f81e9a4f7db1afd1d2fc772a63f18dae17e94a9c8c555334c61c45a1d6b74fbce3bdc3a7a2a6f0611a8dabd27307e03a9f61fcd6d5bb3c168771977ae4071591805ecafdb2a12ae83a963b54988b46e06edb4770c3bd2338039e9d1c39d01994b70562aa5e74254c2cc29e00fa0d87e70666997de7c6a736b4e668a5c8b22439ec88ddd51e23bc0d1fd65fdc7a191eb84dedda0705dc6d640c4e8c257d397c85002d32e2b446fdb66ff2347060434bba73e5f6ac38032c6f4950c70fffbfed1db90dcb04e6f0587e87d769743776b94349f968aaf73ed4f85182a7b0777d8fc9971fc9a1a8c3ac157bb21d7ab2d00b7d9d68556869b301bcf2da9a30353f62b22910d89ca6cfaa19a47ae9d1d1e2333b4dbc93beafcb3e577c1781cf4759cd9fea4f883ac3355fc30e7fd74ed0cb9e1377f33168eda57a8f1919aa6083fd6388ebb62f1cdb982ed625a87e74e415021f6088989c15fffb51a02b749913dca98c81d1d7ea5b6df1eb3bda3e16bbb26cc73c39d74c984344afad46bcf993e8d8bbd0498af73c87d7efdf67a6f86e487fb4e0fdcf444997481cc34b493625de6e816bae53503f4543b0a3254954e428107844e8ea7f2cfca361a907d0d70392d800b80ae814f2f6de5195550632bb30db946e4eaf453b3f7a465ed9a0394319420c780c2a3bfbe06d357165f0b2b3b9d94f6b22a8ac8e2801a06191455a0ced3594de8c19bbb321d7ce5a45d6bdd857a36c0ce2c4e0b4a8965f3e32e9acfaf2ee896ed795a6b128b5417bfcb9851f8b63b2e2a137760782eda1a8aeb5b1465e2cbc8393606b711fee35546076ee1a14f1e7ae1f5aa523f49b6f869afe248bb73a211b2389c55254c69ad38ef215535c0fc6bf0dc02d6b63106b9d59732253162c514f433ad37f584fa160e96ff3d1764db318d4b9fe3e7a448baac3a2becdd1cc0f349efad6f283da93bc3b130b16c3d36efcfa6f7677513be698de20751dce24ab6a324a6c6152cdb2e77ee3212ae529232fb22ed3b545b96593642ec07e176122a30051ef462fa7947511af763d25ee229464c8bf049797f9fa18051f6b9be34f2936ec277a04256fb6e3bef8534a1765873defdc9bcef2ddf50314a2a45246d9bd0dd0f483aa1756a5b33f35cc8580f292b78df84535dad32d03f1d496e587fe247de0558c91927c979d4f46dfc6bc59ee5ad5e8405657541b5f9066e23c14f0e616686024aa3bfb5f142f3d5a3ffa31571e966f09d5e6c9e662aadbc3f0a793b7e443ca23d56a4ff38f23b65a51f4a9b45c6614cdae17e843ed95c82fe738127fcc4938496d2eaaa33dfe0ab51340ad87056ac66d166b95585348b6094e4c908734b75f8180dcf5beaf269f574dd3e0c6aa32b31be082bb465bde2adaa55ea3cfa842dab0944e80505e2af993b86c6b765ad5590a60eda8770e3c5467f8af5f535a84c47fbb177f97e3537023bd57471cf13ebafe1b0d39c8f2ec542e459d19fd68cab8e6498fcfcc8a8e2b515a33d0c3b95bd7b6df428889ba71c13bb1c8092e189af3a56f6d13069bccbfa6cfd1728f821d6d90cb54c30aae8206895e1dee2d9d1b72b36f35e90ad4de013703a71009625b6c9770f273a5dfe289f4f50733fd1208824d1df73d5a407e63280a528489f1d1798a64908a54d2b8cad3301e4691a9197ac58abc8aa2e3a69f8ed265cca0ec8e5e7ae90dd61a8fc7a1789cbc4f1d1f61e6195635f857ddf32bf8df4527ea56669352199ae8d6b7d90a381d3ad3bb364d6a8dbaf800061fc271a4ecbc6989c6399330b30e5613b8acece77fc54d0f141e00851e92410ec78feec3b1ffc7d58273358a0cbe2a43b3d1eba08b2e544068f3b379d53905b6cfd3eb497562e87384ef650705d75f887db7e95e456c6b8ac457421956df4364651474ecff029abf67bfde23b557c5f9d5b1cf76093c548d5fd5705a7b454e7d503a363ad70f56f683bdb8b6c2b13765e405ab6e9bb1925793e9077699c1e512192c19a62d16852d1cbc50dc48511739b1bdac8f9cf17f18385fc97b9889c218432a9532ec1c18b36778bc5ac2a5a918da91d4f3b48c422c65fa68d7ec4c7493be1415f4a9d9ee64b230bf0b025455f928f2b99c065d4c7fdf2e5fc078533d29233014076b4fa02aa1cff1a74399c38f628c3bdceb178588da95e137073bcf6d4f8b927df93022a2b1da2ee175cb6d0521978a9ae613a3731e55fd1b4b6dc96af8768427547938a5b87e1a9e358ce8b627c537acfcf1538b98322a9575755fbab562609ed62e6071a2575dc8b9f79d75150e1b1205b2ab29515e38fe5f011988a6ab27964fcbe5bf53b83fabdd75de090a62790c76a041e2269361d5080e521279a033d9a0fe04ee519f489090761c6bf56fa014d8cda2bf77abea557662a0706dd6d3ca09eca03087cd521e72a1351e9226935cfc3aff7ac00e9f3b1e15cbcc0a76288e171c421dad579864ef0c18f5d968cb2081b6c52da14d1351cc75096815f010c2c5a7bc7a5835b02cdfa1c96c985669b9840a90f8fbf806fcb453957a7fdff080fdb8a73289fa6c0a8cfe29f0c07023bf398c6f630533a4b5d000b53c7b7753ff206337a022600f0b425741146f83ba18d2327a5233fa71f2ce870e1ae429da7772a2d241172abb2fa80ed730d60f15364c7e8347512d44c298d0763b92e256981212166d1e7a90390a73166e4dfc19b85250b0f7f914ea8c3f3ec57e8ebce370af1ff0363102a458f73081a6ec94797f53ff630988ec26023d9fd66117bf1c9c8ad43a066116302389a1667964f4ac8d63eba97c2e30887b4daefea41c4891d6c6ecf63ab2e60da7a82cc6de10304800e40dc265df7adc5cc3ce1688f2cafa63f4bf7c95818c4179703af16fb0bca097042a83c3fc123304be59788cf59008fd3221a306e3daf21d3048537f298388ad1221646983078c58e38cdc97064cab83af3201eb60853ae90a51c50aad059a1879f0daca7ff34981a93b7d98ca5095f1a979965aad5dd792e4c89bf6928f5df62981d0c90293c2e9ff38cf13a5762b4960bdd82659301c2dbee1a9b8101fc8d3bcac54eeac9db4584732de15e6829a9ff4cba40f34b6bef65b29b9e3bce3eced1fb320400eb9b3c0739ddae917f6f8640ffed7c26f65a078ad893a16a7bddc923efbc0c1ae6ddbf1895963ed8bffae4a3a9edef515a12ef3bc8d9b3a33ff43828ec58cba33521cd5c0b7d2913a0aa9b5cc508753637477327b9c529301c01c68e7298fe15432bc9923c6aea52033aba4ba6ad85fc9def187e867cdbe889838fbb7bff031ad2665f961d796959eb10a9377c745ca7cc15e06cdad96055bf7665b8ca9cc783161fe5884db4b90adb9e7d7901727e2304f4e4bab5efb2171f9a26ff47aef5be142b2d733a0ef507f8badb703a07fde505c571e7bf44589fd2bc18e32543209c13ba29ef066396ce2f80ba11eb5b2ae74ef9dbc42494c720541930c0115c2c7252e289232dff968faea2df2079d9e8b3443ecb85fc3940df69c240238f35f9c461687197c7496f426c93d226fc10cd370a7f406fd6c0151b17efb9429b59de6084e565fbea8a936287ed83e08a5379f9b15ada047ad2bc551d7db2e554078d0eab92e9970bb9a03bcb912e6523dbbda51d03ac762a8617a5e890a2804d141d61bfb3b0c53b1d1d5b28062f3bf24686dcb0befc8f3e3d13bd3843484db90e8e6a999683a4d6ff02467d0ecc3e9d8f79b3d47841aec605e18a32ec0b032e60d74f1a0cfa39233b6c3be08f360d8465fa35fdf7942eb121b54fede4295dc929d491e31593e88eca562d464ae855dfab3312be0248ff3b45f37bdf18e6db8871b211218efc2f84d508bca6bf354c0b61f6e0df194225c3b36f1defeef997961c959da767d93c3b46c360a12280a8eb89a82500c223558f9a4a3f8e8427ba92ee7b27e0d182aad3e0fb5bab4980e5700227bbfaa43181bdd3782927e5e6305aa7d59fd665d772b8dd44e94c99a603bcdb45843c810ec3ddff130072abdc50de7793e46e200fd4196e278e180e266eadd4e9669ab8ea64130048edf0731b64b7b85ba5cc211bd93b0ee1a9376fad9caaae364a7bf6d8fad9172dbf946b1ee547c69b328660c27a076aa9eb766706b509fca957a2a48e1ced1f0f5afccba3f53fe62e52938368d0652f4d5db5267edba36d1fec7ec00ff2572d58bcff09d5c4f19ff6fc8c1798d8f4a064a0d885732708880748a04bfe3e6036fbf7ff18c2fd55329e1066ed60d86a29c99b567f6f0e92b79bffcfb6d180f3441e87a7ee0f755cdc203a5ee4f3524e5e820fb74ef122fa9b04dde1ea771624a7d7be385416a21832ddb2c6fe32f5bce0dad33ba6d8f296b3f01b5857c8e1bf5745c5b8830480346f4fc74f4af408c52d27551212016508c67e1fb2b934a820e5b6a51f225dc5f938f88d288de940e7fc3b17faab7ac23e11a291d505252916fa780d7d21b3ae48dd4ce470f3dabeb610d61f24198863e4213abd11c6c7febfd576994ef9961ee25ba4d01a0087a23de6e9e8437a84971b762e349a47ac86363b6664070d995efc6e0d0e68b8cfcea66c7d0f83bb43089854aeae9acd8bc7779e39620b2232df042257f072bc95c659d706728b8d1688824ed37c4d715f7ba0981471d33b08c85ce936754c3ce76d2e753ce132cc944fd79b35a7cf6e69a731a771a8ab6637bfae7834f35dc0eaa0e34dd3628d3115e287f86418b002278d56193664a90ec55cfc307440d776c15387a20f7da1be1fe088d0de1f726b9d62c31b55bc6b6f5832bdd78b6140e9f885b4c4c9a4a84601029c52b686dc8015f5087673870b398fa5f864f1a7d128fa3dff8a4f527c260a5c7d3f685a284d34bc5d725ffc3b7439256c80fc02291a6e64426e2a581f029869af97f2edec229afe317ebaeaa66fecb49d07f4107da28bcbb4f767079e867714c4307eec30a811caef6c43ba4b2054f5df1f6f139e5b913008f6d2b0814704fe59f3c65fbd75a1a7aeb251f484a697bb4d4ae1448e4cba64564e9c0a18117063a00f5275ef00da00b87322d9786dacf34ec68339eaf477da02c267b165e010ad446c7c3ce0645f158aceeb2109bf8666185cb5c95199bc6178c84b6516e3ce905b9bf682cbfcbb00c8a09c64d959768235dfb390f9b9493443e10cb5512c11ffb357ae5a04f9a1ce4838a7c662e98c6e6a36cdd1eb37a9fe04ba925ed6ef24ee44cbd85992f4cf3a82d60de98b6779efa379f01d6d9c8b690c303beedfe70d2dfa122c1ff9cde866f57860602d5a1a148fb1946c12ad9bf633b54626be3d7051ef6a6ff263c68a8db718f71b55154cab0442cb2c552150be383c0f814eac0a36b1108f05be1791e07607282c5df4c44bc47fb996bfcec916d5bba25d1c263eae3d81cc365462f1ad8baaacab6d16825bcb98404390475c28b36791ee69a38f6271bff5307d0e644175d8cad881b5a2c21afdef707fd405d6514b7b60d5a1c52a227f53821f5fe9c34f1f98527f98a5c44bc4f401aeca54d54aa47467e8146f4c611c7bb6b5f50f9faf4f5a47edc9ec21000bd0cdf7577fc0cc0ceb7d9daf489f2891bd0f758dc99f23677b9f8838be06f439d42d6999f4ca24b77ae00c6c44711aa9c8dc206b6ffc8b009a1bf80d2e7b8ce85114d22179c58c4a2c85f5a158637f791514a69b6ce8f571876ba20c6c8ebff09dbb2aeee4545d8e9fbce881ccecc149feb08f40ddcf282b293e7e302ccb3ab994e59a9bd581ca5bdab0ded5e5f5af4d09ecb7bdeea81ded093e5b82d1520b9df74a8490e14b3f0a4d285c40a8bb4e1bd379cc6b9b47558291dc94ce097033ecc0da4ef3ea8c07915c480ae0f5c7dc8462d633b34d10b590dd0c0409b198ba271d8455d73087b99917db5d1ae9310bffde2476ad008abe09172283b2fa367ecb6c5cc5e7b5b97b03de5371fd9ad1587a932416c3ecb90b55ef9c995a745fef1f971ec64ff5970810b69232ac9962352e93f0291168a4722bce57ca0464ca21ba2bf0ffeb7d2637ef32b1239d0a172ace9dd25069f1d73006baffa9dbc5dbe29d6784e340cf897d9274a83dc14c7cf8ca6d0985d8eba9ef5e75d8b2237ee955f7bb50abccc8fe88bb85f83657be3bbed30111b86742999f84922b35487d1e90a34e1c35e4273d5f924086406e0b018f88efdd45260261de8e589439aea2367013c3d7381e6f5c7b92fa5fdfe02a3ccb861da8d36b3b345eaab4cdccfa20bfe6aaceb9cf4b1a761d170ca4a2d1cbd25d096702fb7f77822dbbbd22e56527964c5d52311ae2c6f2274e765994e571bc102d27d76e351ca4f8f79cefb3ced623665b77c0cd9a6d70e75708f4d91478fd31779a08785fda70b2ae75d4fcb525eb0dc7099f0149ede3ae5134848feb6db9c1ec9f2548f50bf5e60ffb34c461499a3fc32f6b9711d83812a7b4d88a27f36d3968410df50c78dee1678c044054b14d2f173e020afdb7d9f69328429e30d8439d3cfce6b145fa526cdae3e35abee7341f0e815611f325e4390562cd0a6d04b82162ba46958d0468d68f2f64948f7b49bd2560fe0bfa5cf0ceaccbbdc5247fa9405f7d68c78fbf6d69cef562662900a630f90cedce4284604269239a793922df8097cea693ba456158073a9567eed7b00df901cfce517c436c8c1f6a4c0dc70634b99299e2d91c102e942ab03b30ef5b2bf712031a42dc4217f466cbd6974e89eca3e59aa231c97d93271e38c5421f8143137ea0ea81dc2735cb3c709a8912068f4d5b32e9c630d50b5c372502d237b6cad2bcf1e70346c5cfad3e9d49d33ff3ef5527451ab33faa97e8c7b80027f0315e776ee0ab1e6cc810b7a7b030f011ee11264f2647e755dd7eec9ddd3afc462d188c9565ecbf121cb1b1768c3daa96d45948fc88a695fab00dd49e7f5ec6e24f215a2c2fed3d1dbe6a7989ce3c6c571adc38280d0e27b7d3649aed63e8bc02bdfcae7535c252b9b9c092857d07805ebfe3b637f58d439e7d730c7d4e89b267ce329da0895412763486d630dbc1374e87376fbbe531bf9e2ea6970db1958a1d9e6a0f53703eeb733b440fb8e88464b805b0735a23b7cf85b6426bd32ed36aec9ea984aeaa6037cfc3b761bda540e93d16b0dbda6974eb139ed162177f920743b59ac86d617c606d11256ef8ef4086d58a8f7c1bf91102dc24b947a34cb0ff15010ff621643a2fa5bab2f9822da8eb5aa1aa425fee86067515fc785acd23f948076b7e6fb92afd5508d8001b284dc394617d441763cf8fdfff216859f2649c7f6361171c685a8f37f8f1ed92c2ace00880d7268db81c0eb42e3c03521b762ed464ac5fc258dfdcf70eb6df93f67c9ccb0ecc338747a0ccb8d30055172a20b382c4c528fba91a46065a54e267cf49315c776f9dea7d7eff873dbd3f32619877132319287b52d19439668138e43f073318eb00bf77f13ff86508b8693c58c66514858c69e1d3bd49fb82e02acb8dba7b3f2eea869da3093bf0488f92f2e451c2ceabb672812681ec54b905c0ebaa2af5898486f8550ca9c8333eb43300a15374bc20e8fcf6edd690e5da7fa900f890b6dd18136d12cd18a3b404a6489a0b66a045b78a3bfc250d6b6c6745b02445d82ee81397cc5a7a57e469b8783bede1dfc5b16e171eed6ccdbfbd744b457de2bedbd377c8282f9c1baf54cd117a880ae1d6c11e7bd9d91c1bfe5d26f59e4ac558e29e19b4a544eb418e4643b16664a6236c668191cb1c84b405f27999627c69b82c3571e89fb727a93bbc3f709d517ce8fd555c14c69e737bac4b62c3d0eae750ac8532fbf7c82b18df4599ea23a317319e322d1bcfc99b8ebb4fee21dd87a74d572e10b898fd82e418befb24e9a9342924b6279f7b16e4f457dd123e9fddc9188fed087a8646423a1ab8028ad06aff94dab29a4b5f9061339d997f1b1649c33dfeb09be43c1e3737a45b4f4f639ff260dcae2f28cc3c2ac45863fb3ceca778335b9097fbdcf56d32abf244136cf7ec67070c62ed010770eca0587fa27cd2b334935de9eed77fe3be548edb79073980650168151d1edb4faceb7180d9ca2847389ec87297dedd30c35faa067669778049290c33eef05221a743acea142ad101c37f67c9445d5dd19cac9bd930c5e0ea8c4b46a83a25bf97f0371b9f6793bb69356a2e2acd5a38a15a464654f515acff163f21e251f450d3f705f83c99949b1c8ef4671af9d496daae7f7a195f333775b88c60cbfff91ae0b4dd47129a068bfb1f8a6d3ef6179eaf32a27e6dd376daa1b04a1ddda19856cbc1e08f7cfb597d8f9d87e2039d935af1eabab43f29cd2f9cdc4f1ac66dcfd1fdb28f049764bcc3e3cce735735661f50fb555f232c4afbbdca3047c9be1e46203e7fe9efe2e888feb2393f4188f23f65643f45a7568613699c8f01dfe44b3eb09587c995390b6a926b6d0a62fd66cfc62e74f78179e528f8b69d5e873dca67d16b7bab1f6fd64e66a87dfe9676bbea713435968b73fcd5168f056aba9a4a08a66692c053b5a5bb4b8607a60bb7e99ef76e2e547c3e360fc441c7ef89ecd106f1ac6c99126418f56984aeb251e8eb2b37aea8f376a40bdb5465d67baa6e069b155ba7cfc0cc084e8e46b26d0f0e5e8f50775223b33cd09a28446bcde3dd6abb9c7acd83c92d1868f8fe88a6ecf8aeb6c72fb82269abddd5576733169a4c9cf4a99b182eb71f95bd797a518adea49df81f0ab8ac5593dc36b9f573aba355875f9d2c3c9b8330d2f1d4dcaf0f74b7882b4e571a2caaf84d7a7a5c8af36a3d8ecaf2659c5e3f92dcac96416bf9b61cefb56ba248183659a0610c732021fc917caff8bf760e142d8f67ce0492dff70e71cd67f6f7b2ac69decdda140d6b9bbe46b07c64654b63ac4755a40fd1143e93d0a61bc77a2a1856b44be2bc6b86948df81a6e5f6e4b5a4ecdf43631e45bc3af8f98ffed0f3b28ee4b8834652401b446f557bbc9b17df8e9cdb6c87dd4211061601b7507dc78fdb3e1e9968a12c429820d010ea713359338c230b08a28a209eb5fa7f37e7b86831ccd14cd1f10d2ee63ee3987f1299be3d1e4b19a4ef759c60a5d18576c127666a45275697f1379f87f6c8239b4fabc890dafe581ff0091c91ba37a3cc80d08b571b2cd8a5129d30dc027ef81d7466c74f263107283b6cb27b31a3b6e8bf9c9d9167c8fd12dc00820261fe3e5d5a7dc95eeb576c5f8b61fad9723caec0901efbd92082b73030923cf6558a5e48fe24b9378d52d044a72722d957d39d3d7732b26d1614784ad9a0d7969723b0a93fd49b93ae42a39237c027af2afeb1b56b7ba1c7f4aef308e05b74267626a3add0086c656558a766036b6adde997cff4f95ea361a462bf7aa43544d4b2637efb477cb2d29c986b274a95fc2e53eecd84e29fadfef4940d10a6856d1b83b6a6e84948a0c7a376411dbfeadd768cb9de2ea20fa1e7c5657f8a8fe904eb4e093faa388a55a00d38dbf664f31397e90d3496b174ddcd4d8876913c6075ea29b7d82f3e08231e458f0f98c707e434ba82a8e1efc84ffecdaf9b6571abf36da25b19c79ccade75a40a5fc6dab4432cccaf6ce364aad39cf191fafb276b239976f24f1ea6a3142fa171f6b8d4fd24ab5e36cc278211a4865672e6b204cdcb5e2ec29401d064cc85cc24ef0828dc3715caf1fa9191d8c92c6c783dbdd4256a9b9d1452623a271fd60e012b44c3fd1000647dc880dd3686adc184352dc363ee0ab4eb635e0e1f3ba58485370c1dbd0cb115fb034d88c69d2e01c85c09a48d81be235cb14e56c6ee74511abeff81c71237f60d0c6f11442b07350408986e65dba6b7677c94aebcc8ef01afda4e323f9dd141c856ce23d441712905a8129dbbca8f39f6104595250c6c03eba0b4a282faf79b1f8f9f62ec539e8e34d78f5c94ef8fdbb08b475c1794218a007f0e0c3a47ee77dbc918f8a9b55bb9d04be1b28c86b0e811c4e870bf9ce908faed4becf14a2c0a84ae57783ddd56940dc326e8e1b79f336c209cf64def2f029f0b44b86c5e42bbf7a7d954cfbd971d13535774f9fb44c2e29ee46b3b0dd79f5079916ba53d2df081d44cff87e5b1efe3831da94827fc6cef31e07b7616f5fcb0e685401923c9f20867a0a94c2a213f5e0dcfdadf5a3a31dd57d8c25dabe995f6c5139498e3d76e5b0640deffc548c90bf74ca9678d73fb8fd945249a40b7af5042b28f122ebb91db8da681c0fe30cbb5066b026ae0cb63b7686259769d18db5d7ac9073ca44f4e1e500615b45f040075cde153ff5ee2e54524c791a20ae688627b33e301cb4b0c071baf5feb667b782b2ed50a85dfa38c7e1947dc6ab8d76c4b3e08c59d740baed6efc5601d493bfcccdafdcb255a60b1a49bde24d664595c25eba67f082953b485f78a5b5fe3ac2e620191993c37d361a3f761cfe757254128f6d413a86bcc6938808771934539c9c84c99cb4c296bb7dae344941d1b337ce961d1a932446ac2731244ddda470279f93e7da8af2b6b17eabe5d38e7ca55046a04859a1e430e5fc6ac1ef7fbf2dcf707c096a9bbdcca444aa977d39aed707db9f9ac984c5ff20286db1af6e45aefacda3ad57546667a5b33c92ce58e282232454bdb726d22be6aea0683f99f489959c27fc20a24439519f3de58814e362eb7962575c3ac7e67a388d17f12d83d57ac6730ce683b0419b254d5c32265f471d8651d74d3c1d7fc708dad85282fe495a7d278b01198d0603924faa492bc5104af55212d6f4897d1920bdff3fc2aa7eff81b039de738cec6473fd4eb7f68e392f432cc7375cf5c8ce8cad535cf1d1075af9d534f596e0ce97dc6be7299c2c93d048301c87903ee9353b7cefd7ff1618edff3bcb81aa895f30005c010b1f9467b1581dc9a89bf13ce9bed84f183033fcd89aa8224275252e48457735cb9b1d2de544599e45457799e4571342dc8e52eabbb2d1321adcdce9edef2278c5083fc2e63a5aa6bd1f1c05cf2693a33728e85c1d3b02a01d9526dbeeb858a2e44b84cb30fdf3b27c5ee7e0ed905944276bd228f58a3aaabfaa115599b8d0c1e708c299c3d5bd78428f4599f11c94fb8c1f1c02d4f2b8e7ae88137bf473aa7ced4217c60a21e5686dc63a8eacddea68a185a338d84e3f27eeabbd3eb67142183cb8393faa65af008c5d0f118959287acc087fd3e258f6aa9128f56e12061c2de4a1f42097bd10bda5fce5956d604d5cf03067639747142fb7e71845af4bfc0e1b51e4c6f340e4c320096e9062a2e5d72aaff23ef17be96578c0e8d1c3669e6b71370bf0e545a81d4df19a116847c6e22be68e96a1c8bb252a70bc6ca3d5b1c2dd5633121ca6a893335aa0543cb90472c0eb0a2d8ebdc997cb58fb8fc77dc83bd373521420c1a9d3766629b3fa5d83f4e293cd74a43f55ed23b0ce1855cd4c9fdd4fc1438829e1dad33325ca6d66ab958117538cc0dc3af42ed3fbb7740e1123720355b7d4e5a88fd9fb0e8b4eaafa327510d44d224959f613c6e30c9869cdc7eef3f9aa320b90e3528a5dd3e5fd3d3799d44812fa738d2dec755a34aefc4712ae4795ea06f3640bf76a410d87eaf658da4f9aea9d2aecb5b977b6116750b5e778f4964b9644aee4d5abba707834806ebd74e499789eec8f79c2c7e5f63cce1ec823fe00e4efd940ca44339704af3a16793685744dacd97d8bec2f098a221f2931e8f5c5db35b191088bf31c106f25f869f7600b99a260d9519ef4d45051e8cab9e5a5f8faf416d7f83ab5912063c1a068d499d3a49850dafa1dc51308b7dcbb1ae799673431453e31e6ab9847b4433e5c3f4ec9e27d4048d99e8c6ec637abdbbc337ce3883bc8e4832fd8d9adcc4539bd8a1497f1f8ec26d64984595ec401aa7de04c633f6308573da4f43871a0ede47502b254cb992665e3cfa22301bcf5ea64ecb9014872f3b731efaa523fd67561f2177f7be5e07ed63d4dafa34f67b5bf2465d09ce1df003f9006251b0208fadb25d53b700d126785e3761483c6cd13d81fcd3395b76a2a4d789b8933d1896a6bb6aeb6ff71e5e24ed8a8c129fd53ed1fcee0b7eb522459d25ddfcf7ac4eb006a719d5622b0ef002b365fcedc3788e43dfc9219cd439f7fc6cfab09e1cd5adbc2fe632f40d73fbd39bdfa802349a73d2cc023238c8657cb94457abef322fdc3b5ac84456208cd430b1de10e4e760727c349c1558c526942571ad84ab09570ade2174710b50c177ba7aaa3f310aeefdef23bb29c75f02153f50015f449d8acbcb79e8aff1d502fcf31738770aa25dc4c7b4a49fc79730bbd933a2cd2683d831663d04c5b1c461ef436a3204e1787851bc6c433355b9223463b682d5ad5fafc0de25710acbad5eff692d0235af64c74a66c4e0b7aa13a4414fa428b1cae6587b1b10612a02dadd7a1be2889d18b075fe5456c79c70a1c90501801f5c9d13662c6a251293ac6faa5fd3173ca26bd457d9771c671203bb0c505c3214ec7748b10f933482b6a29fe78727809181a229981046b2c854aa62a8d7e419f1d55237688c6ddb64cf5033c09d12bc139018d32ddb1c04f638405f705d40ddbbf53ec40d9fffe3e05fc90c12811db5c0c05b79a6640bd3f327c4533d911101861bba41744670448a56e138582ad5901db2d4de03f5b231194ebfa43225da57bb80229b72534ec6eb977862cf754bda987bc621119a93dad7a8958323d9aaa2554b67ba0f58616f1fccf69378900464756b1663fbefbdf9f10608a096fce93e01113f1e945ea4bb066d38b98bfea0f38da9168737da13c5f6d554c5afb6f3658075c7950b10a2e9e940b69744718feba685c303f19315d6e7b59b547d48e02d9c99fb630c6fb31e2ebd54cf24fb80220360e37f61f79c6be904bd634a652677c65908bbe8e7deca1d6b7c3361569c04a71f29b0f03f8e9f5dfdc3690ab384f41fb61f641328e5c0583f28feaeedbf8eae731b261064c6a83ecadf6ec42617ea01ff94b6e247c3dc0f4e92a0315084d5f16962cea721b49a73ed67e39daca083a386b18347bf9f9f05c0bfecc9b43ab7ef3d5bd39f28c736b22714ca59b7e67e5707675dec22e7b15ae41b588c4f0e9487efb342484fe389d75db6e1193df12a75c2ce44abe633e78b8ba4596a79e68ee7eccfea14ac381cdc1b35d117fbc19cc2097b86bad7def8a6564a84d17e10ce000bf82a704fca2a29f73456680418f843749be261b71da1ee75c52fed369f6334b1a3bffa3af6062e3c473f90c2a74038cfadecf45d1427bf380274a355808d9c3d46c6fc7b4591bf1ff696665568ea3648362d4dfdfe57cf4677662f18004ebe1bf17b65ae1bb2371f09cef7eab1ba8114f5259e2ab3b111e4f6d3d88d7534ae9b9de6e28daeb4329ad320ca61db464561b7f7509e20535aa907b98b0c3757325dd50379fe90f8a053cb67fdcfa0ba4cb7d63326266ea99585fdcd3400b215725e756fef0dd98f74691fb9cd08f6121e0d571ef5d8c93c601c721cba8f8e967b3b7490be68996b02be492f0aecfc959247f0b27e1d49fdb1dab5227ef2585e1669de2477700d469f414d39f9cd7726cee0eb622a80226711da280fa8ce85341a714c590b2920d3a397a434ddd3a18dcfe3a18af8fc98e7b3b2c430c5a751e3096dd53fac7ae326a16b2b78def03957b465c9602e634a84c53261e9e03c7af7820706490a0d564874d4802bcca6c09d41c7b825bda4b9786c25a4a0de6c9992055a0984af6d1d0a4bb8b241e5e2fc8db507c7c25d0c22d75259a71e98a5964108ea240e9251ed6836a865d26a6f3d080b193b289149a17038e7e21493612ac05a3e0e678c38a549bb9daa6f54296aa059f0d941298cc99eb81dd9ed0f20ce39307734d4c415c0eb45aff035d80259eca4df0ccf3e67e31a083ad93df38b90e2402cc52927598486af8312514421d3c95d32216a38ee2ccd117d41e6e185d9885998c89ad195a7ea5c7918dcb881a42b71160572bed8ff40044e15ec0c77d6c64ef744d703344ef7943d9099736887f5ad7e09cd0c262a22129357d5fc4d8236e236a1732363c0156ec9098e55590594d9c552a6cbfc8a2cc30ddee2a1ce429292a867526e8f4d5507932e0f03ceb21bbeab13206ddc6ebdd90954d57368793ba0cb45263e503f629d7c4fa98007cc65f19f6d343fa9f07ca888546d94343fa33b8d76ceb941e5685c9f6f95f4d7083c1c4682c8f014f9899ab09d04891d5ddf2ac13b1d0a3816223fdca5d87e053278120882d22d6dd1787212a029cf2a7415ef3fd9f1b8041519accb919dd07a92e75f37521d846fe3496c0b506be8719750bfb3f80c493da1166c81686057f9654f2d05a7c92c48a838fa09f357349c6a7f005cf162cc4e28d01712ab98c6c0dafebe1a57b0dcde2f90d6c12cc6e178e3d37a189183e9031d915d4be13ae5b7f67edc532e397b43b9cf8cf39a6d37af75f332fea3c106f68d82eff96265e46d17ebd057b6bc2043f835d6a17c09ebbc18ae9ab5c0450686b42502f1fcfac65d1046c8054ec022bd10c19173a63ec0020ed3bb53a1ff9404c42099be17a9515fce148336bcadef9e4fa8e990e618ecbd0dc1c80930ab415bcd30e912add034f9788b0be5d69aacec8b98552318c7e83fa4cb4e44367b4659b2fb88a4659fa9667f792ac88a0e87dbfd5b0f00296512d1241a7a6cfa8476ce1d20421bebfd460ae8b5f59db10671bc57fbd13e483ae6f0e4a4736660917d72d398feaadc319421e797f0f6c97a743187517183d535793529db917df7dad1988d452120f0d30909c8c7236b2e0d510d7993500f6c840c0e03c37b2dd60ffd31d675c742af11daa383b64f6b39c3631a52c3bd400df270e8d4666957a8597fdc51235fae426fa6c03cc871bc8f9a84312b46ff6effdd91439c8649117cdc556519118007bd964b2ad34ae48f0c8185a51b4af082ca951c42bd32d8cc96e6b7bf6967f6241ab6840dcd5d83d0ebc1cf1e4e5c9cae284c7a4bc48c15abaaaa729803a1d76eb561e3a0a293fdb351b6e7fca0eb048fd33e50087f99eecbecbb22c5606bb2a7bec0eadc089890d82a81639706b5fa7019ab65aa83f5235f8e0f5fc64daee3c8e01b5fb3cc75619718191e5b0a5fc613c5caedd9b1a0cb577451e0776785d1a948c630ecd08dc2fadb8112cc132e07c8a619b7458dbb683be5d2d88224731f8002b2886d198de9d82dd1666e1c05701c87546ab0d2fd5c458cfac53dea421bcd3ff24d3f22a5e077e29ae92faea591ccffc62ce790413b51195512270b3d395f529a6f2579cebbd80943a53d0731ce961fcf2477646f52c8edc6f4486ee0038bdd50fd26eaa98775c944f20f852b804f21869e883616041851add69d4769c76da16abc87534ca4b5ed5b01bfb4650cfae391fa3e7c0bf9a86bf840eafc1f8b462f31e1e58a6a12c6236ea4ad802282b5c391c66bbbbd4cad408687bca016588db3c136e64d53f2dd9478d00382865a8ddfca86d49e2fdec25a371060674eac1942c36db7a0f1ab6e9576429503784fdd6dcfd83884a8d3dfdc3a29ca699b648aa1fc73984d6657a7bf2691059798148721e3daa91f948e99cb51b59019507adc1226d5ac13e0144c450a2d9debf026a48c7f5c58123e6e999683c56c4303e697dd9c42b15214e3b3a5c32c5cfb4274cbbc2ccb9d0a31cccaa9354eca1b0e9b51be62dfd47e5c620cdd91e0925c63c552deac9b2300733469dcd473d3fb6758d7ffdf51e2484ed7325247cd0c5a06881190fa1b7b69df0d886a0f1235380223238e840f83fb478b50f7150f6f111d5e1e9e3ce5d12c05a0e054803939bebdafb107026a8f1ab640f6558ac46660accaa4a5c2c1d0f4b53e682929a64333b37c6264952ceceeaf94b7a00e4762e19a689c9818bd92dfddd083119b2cb0c55a3d4cfed4825736e886dc659482fd36ba7c29e6d4cdea2d921300776bdcaf4bcedd465d7bec0503822cb984da166e7205c9df4d64ec4ef80768c40125b23a185b87d67acc3644d14e82204ef5fab5eab9f3961e7bd6b862e5455e21606d2c0951ed8b2b61d98485ad0f40e3045f5ddadfbdb7042f4997d81d50889797b83ba45f04f8466ad1e0e6ab00431eea31839532d8ecdfbafe944b9847b37495a8717c1a6c5e2fc48df8b9054bdbe74a4d211d0841a28a60c16292d86213b9b6566c9cc3a06d65d6c356b0cf62c6e51ea457c83ba769a2b1739ac36bba1b2868f76b445da7164fdd07e2e78d9fe2e5703b2d693c3baa258229a80369bc0d0c2b17d676c5fbad16a2fc565584e7ba959aec5bad8a8a1e02a458f4dcb1c7efa67133a6bf5958694fd45c1cef16433dc7455b9fdb7eca3726be1ff52deaaa4a617463b0aa0cbb7dee458fcdcc1fd1cf0b3f02ff3a9d6d86b9289da4283c06fa967c016fdfcf060be91b6d7f293db2178ab2733c315eb44e0b540e13dc5130a45a0b5eb48ffc0f561ad1f464832b7904e1f1d7c232671dc8b11190a54d8d337bbc14946d4e53743a5808ae682c3adc492df3b3752ebb4e8820dc7ea5e70ad6514f1ab38f90d08163c0afe0208361fa8186c3f3bf87bf2a101bfb3a199cd29a4e709a877122b6104bdd0c688db32e7cc258e5053814bdbbc820ef2207821f3d69e1f84c3fb8cf83360d10218d67106f5755fafbbc78727a496605e6699168dd4379fe07d7623f9afe9abe8bf07d751f1b815693440b9cbfeb59d352030ee4f76d8fca7a931e91e8f9b0f293ed5b25ce093f70b78d78f3ea9d3f5a4727649314682497ac005463712d4f12f510c74d15f7e0ccda9954a3e7564ee9faed084dc7dbedaa946a1370846e2920126cedcad14de3b3fe70be3b7aa8602d26e8c15fdd796cdc2ee0aa04352ad731a5937814b843f2e4250778f58c3bf2de7bdd636a31c2e7ca61f5cc32867957658d9bcf878eee6d348bf7774d4b87079e688e942a258dfc8b54cb892888101c44114c0287ff363f2c4a53a12b1ae5d7939582e70f3dbde763acf52aad7f2bc3883ba8c93d77072f327eb85d7285565ab6b4b49bb23eba36649f7f32de2334b7df1b137c3406ace71fd362ed9fcfcfdb7c9a59db3248d0c22e19860bf90cb8726cbfdbc32fbbbd145794664aa630d4a63adf17af52dc50bcfcf7644ab5d9667ff20e6896fd40539290305913fef4ceec578d73043000b03a2b062fe6c1c8211a9fb8d2ebbe5c4daba5f992fea124221da36ce1645862d43db5dd9209895c444a401ae43f7c45482850bba891107da76afcfbea4bb52bb1b4847114a3bc3a5e3998a992e15f6f1772cd00a510845aa6a71c7b2c1fb9acf750ce160fd31ec65df51d9dac79df6b780690fe3044bdcc81118444f06dd58973b1710d9e15869b42403f327e26b42d4faee21fe3a07db62b02c6519adfc2f4e7d0487378c94a1d6dfe9f03ffcb47ddbb1a169ae528597faeddcb7abf9fdf8e8a1ccf36ac74776cc40f5bdd4b0f9671a86bff2f012bc6c18a15c766f02ba1fd5d9f8ae2a1f56c3033cbc0f8651193a52989c1c224fb75a11d3dc9a28b3bb947bdf23e39a22fa42ff4fdfe77e35a3afedcdc97eae281995d735655fc5eed63649f2bd4e738927a5a9562f8b65a924621449d1b468b03f2074699832c88c599990bd80333bf23bff3c42ae2ab751f3dd851eaac0d9409f0019f3aa70266af0bd0cf81c97b4fc56f41ed565985928703bcf09c3d3d9f0d93de0ebf3782254f2f235568aa294d67b6aa0edeeb6b184ddf48e54907e5a4b13d767fa6d8240c26b04053379dd072814663d767c52abd48ca61fa293e0085b60bb0f2d554573e50c820c7b2a939840f12af80949cf5c9c2161758dea4b3ccd61532b0bb2d9e6cfcfaa149a233a7ae8f8f50b06b7f1dfa1cbe1c665a267cdfe49e0208f988dcbfa25fadad42d7e126c652a3d5bb35e7160431538e953f4cdbf9fea2d774ed4315263ebff11fc96409da9a91a849e2ed5cee2d4bbaf56534d4f5d41254aba4a9629b49f18b5b1abd833a07ffad843f8b55ff126436574fa2887aedf14ccbd88cbd419944fb05e7a3eb153ef1c2feaf635ecd50c81814ddf8c5ee58687a1116b5b5feb4fa47062ab5aa5a1b152ef321230cc346edfec787c7fe99d12662b3e820ffacced8c17f573f1f892975d849f0cbbbb836a8121f74fca059b329427b6bb523d63f048c909d3974e64bb3b15ea40435f836615741e3cb7265ea3483e971e8a7a47a97e906ce3c97e41d07d2afaf63a73385950fe6af875f1a3cc9f237150524bbad1dc6d33d494803e3cb3f84221039b009ad40d18989579be54818f309277b4f8f8a289f58d9ea7aba91ac288ddbaa5d29257b12d58a1f10377f5527ef12edffd08191321d17d99e02e13167a102790f07787fd71609c2337569fa05affc2c63b4e7cf8366071b34713dacd0b96e59fc19916c606f1de767b65fc4c8ff9a849c2c92b806d312700e9be42c97b6edf1c89903e22d88b766ce843c5860a035f3bce7e84618727ec67d8c5a8003333d5cb4526e5498601ecc6274665d9149e33cb45fd48186e0ac73a09cfd02fd2278751e63d3e931e7bc2a36ff030cf31bbb9ae80fc7f72a600e405834bde0774b018dddad5ff1ee434e48079ea8749c5292331170e3f024e92521b5970c208029a8c1d7f86a719cc53e7e7f84ed788662e99ea782f37e997a29eed901881efc1963037bb0f1982be71accbdbc4d8af825714fed2bab289f3e26669", "trigram_counts": {"^aa": 65, "aa$": 44, "aaa": 14, "aal": 81, "al$": 12716, "aas": 41, "as$": 2730, "aab": 12, "abe": 673, "ber": 2855, "erg": 1338, "rg$": 556, "aac": 8, "ach": 2728, "che": 4168, "hen": 1718, "en$": 3588, "aae": 3, "ae$": 3403, "aee": 16, "ee$": 1401, "aaf": 15, "af$": 142, "aag": 11, "ag$": 218, "aah": 6, "ah$": 730, "ahe": 222, "hed": 1364, "ed$": 22829, "ahi": 125, "hin": 2485, "ing": 24399, "ng$": 19236, "ahs": 146, "hs$": 656, "aai": 6, "aii": 18, "ii$": 149, "alb": 388, "lbo": 182, "bor": 1128, "org": 664, "ale": 2747, "les": 5067, "esu": 475, "sun": 346, "und": 4283, "nd$": 1656, "ali": 7130, "lii": 53, "iis": 30, "is$": 5212, "als": 863, "ls$": 2134, "lst": 200, "st$": 7023, "alt": 955, "lto": 247, "to$": 644, "aam": 34, "am$": 892, "ams": 351, "msi": 54, "si$": 244, "aan": 42, "and": 4488, "nda": 1468, "dah": 102, "ahl": 71, "hl$": 45, "ani": 4425, "ni$": 520, "aao": 2, "ao$": 128, "aap": 14, "ap$": 317, "aps": 511, "pss": 8, "ss$": 12694, "aaq": 1, "aqb": 1, "qbi": 1, "biy": 12, "iye": 27, "ye$": 207, "aar": 72, "ar$": 2681, "ara": 3147, "ra$": 1834, "rau": 390, "au$": 272, "arc": 2225, "rc$": 63, "ard": 3372, "rdv": 22, "dva": 75, "var": 639, "ark": 828, "rk$": 568, "rks": 297, "ks$": 1287, "rdw": 58, "dwo": 135, "wol": 139, "olf": 147, "lf$": 84, "olv": 294, "lve": 580, "ves": 978, "es$": 19400, "are": 2013, "ren": 2515, "arg": 962, "rga": 710, "gau": 212, "rgh": 85, "gh$": 321, "arh": 100, "rhu": 76, "hus": 515, "us$": 11276, "ari": 5610, "rik": 206, "ika": 214, "ka$": 465, "aro": 1291, "ron": 2980, "on$": 12849, "oni": 5963, "nic": 3592, "ic$": 13228, "ica": 9540, "cal": 8503, "nit": 2583, "ite": 5317, "te$": 11022, "iti": 5285, "tic": 10037, "ons": 4930, "nsb": 73, "sbu": 311, "bur": 1424, "urg": 1016, "nso": 851, "son": 1750, "arp": 745, "rp$": 134, "arr": 1649, "rrg": 5, "ghh": 32, "hh$": 4, "aru": 212, "ru$": 93, "asv": 16, "svo": 10, "vog": 38, "oge": 1757, "gel": 759, "el$": 1648, "els": 662, "aau": 8, "aup": 108, "up$": 243, "auw": 15, "uw$": 13, "aav": 1, "avs": 18, "vso": 3, "so$": 191, "aax": 1, "ax$": 193, "^ab": 1307, "ab$": 153, "aba": 785, "ba$": 340, "bab": 334, "abd": 157, "bde": 105, "deh": 199, "eh$": 100, "abu": 388, "bua": 13, "ua$": 186, "bac": 1183, "ac$": 440, "aca": 962, "ca$": 601, "cay": 80, "ay$": 864, "cas": 1099, "cat": 3853, "ate": 11815, "cax": 7, "axi": 584, "xi$": 18, "aci": 1675, "ci$": 171, "cin": 1793, "ina": 4640, "nat": 3938, "ati": 15619, "tio": 11943, "ion": 14628, "cis": 1038, "isc": 1788, "sci": 1047, "scu": 883, "cus": 816, "ist": 9390, "ack": 2238, "ck$": 1843, "acl": 229, "cli": 981, "li$": 610, "aco": 1183, "co$": 389, "cot": 804, "ot$": 964, "act": 2754, "cte": 958, "ter": 14543, "eri": 7662, "ria": 4391, "ial": 3851, "cti": 3821, "tin": 7529, "nal": 3729, "all": 8383, "lly": 5245, "ly$": 16869, "cto": 1779, "tor": 5497, "or$": 2463, "acu": 610, "cul": 3054, "uli": 1213, "ulu": 349, "lus": 1209, "use": 1584, "ses": 3203, "bad": 211, "ad$": 1079, "ada": 885, "da$": 984, "dan": 997, "an$": 8184, "add": 688, "ddo": 78, "don": 1342, "ade": 1938, "dej": 34, "ejo": 84, "jo$": 43, "den": 2755, "eng": 701, "ngo": 613, "go$": 303, "adi": 1863, "dia": 2561, "ia$": 6598, "dit": 1222, "baf": 34, "aff": 913, "ff$": 405, "aft": 620, "ft$": 347, "bag": 267, "aga": 842, "gae": 115, "ael": 157, "gai": 197, "ail": 1296, "il$": 957, "agt": 21, "gth": 81, "tha": 1539, "ha$": 545, "bay": 128, "aya": 341, "yah": 57, "bai": 228, "ila": 1254, "lar": 3481, "rd$": 1703, "ais": 601, "isa": 1501, "san": 1345, "anc": 3323, "nce": 3838, "ce$": 2870, "ise": 3772, "sed": 2001, "ser": 2394, "er$": 16510, "iss": 1707, "sse": 3036, "se$": 4092, "bak": 136, "aka": 322, "kan": 333, "kas": 190, "aku": 76, "kum": 79, "umo": 368, "mov": 170, "ov$": 94, "bal": 1349, "ala": 2496, "lat": 5372, "lie": 1521, "ien": 1325, "ena": 1833, "ted": 6064, "alo": 1675, "lon": 1302, "one": 4316, "ne$": 6559, "nes": 12955, "bam": 99, "ama": 1609, "ma$": 1425, "amp": 1494, "mp$": 232, "mpe": 1164, "per": 8269, "ere": 4944, "re$": 3219, "res": 6086, "mps": 217, "ps$": 981, "ban": 1163, "ana": 2616, "na$": 1998, "ndo": 1297, "ona": 4710, "nab": 1112, "abl": 7053, "ble": 8221, "le$": 11311, "ned": 1871, "edl": 1066, "dly": 1230, "nee": 456, "ner": 2789, "ers": 8715, "rs$": 6721, "nin": 2992, "onm": 405, "nme": 550, "men": 5838, "ent": 11745, "nt$": 5937, "nts": 1189, "ts$": 4700, "ns$": 4997, "ndu": 759, "dum": 223, "um$": 3086, "ane": 2154, "net": 1760, "et$": 2098, "ang": 2973, "nga": 745, "ga$": 409, "ann": 1713, "nni": 1166, "ant": 8439, "nte": 6095, "tes": 2947, "bap": 108, "api": 800, "pic": 1562, "apt": 589, "pti": 1641, "tis": 3258, "sto": 3665, "ton": 3188, "stu": 894, "tum": 581, "bar": 1789, "ram": 2099, "amb": 1213, "mbo": 682, "bo$": 143, "arb": 1102, "rba": 674, "rea": 3544, "ea$": 1002, "ris": 3727, "art": 3190, "rth": 1511, "thr": 1725, "hro": 2105, "ros": 3104, "osi": 2578, "sis": 2988, "rti": 2189, "icu": 1274, "ula": 4149, "bas": 942, "ase": 1339, "edn": 1040, "dne": 1368, "ess": 16025, "sem": 2146, "eme": 2243, "asg": 20, "sgi": 27, "gi$": 137, "ash": 1260, "sh$": 2408, "she": 2168, "hes": 1744, "shi": 2754, "shl": 431, "hle": 494, "ssl": 360, "sly": 1606, "shm": 254, "hme": 337, "asi": 1634, "sia": 1364, "ias": 986, "sic": 991, "sin": 2991, "sio": 1896, "io$": 415, "ask": 311, "sk$": 217, "ass": 2675, "ssi": 2794, "sie": 577, "ieh": 31, "in$": 4270, "ast": 4793, "sta": 4247, "tar": 2227, "rdi": 1411, "diz": 384, "ize": 5115, "ze$": 2836, "str": 4899, "tra": 6581, "ral": 3027, "bat": 1077, "ata": 1985, "tab": 2129, "tag": 775, "age": 2602, "ge$": 1937, "tem": 1066, "atj": 5, "tjo": 14, "jou": 167, "our": 1672, "ur$": 695, "urs": 756, "ato": 4546, "ors": 1717, "ats": 469, "att": 1732, "tta": 843, "tti": 1200, "tto": 588, "toi": 571, "oir": 152, "ir$": 395, "irs": 291, "ttu": 121, "tu$": 81, "tue": 62, "ue$": 838, "atu": 1064, "tua": 686, "tur": 2737, "ure": 2614, "bau": 153, "aue": 50, "bav": 21, "ave": 1540, "ve$": 3635, "bax": 17, "xia": 200, "xil": 171, "ile": 1911, "baz": 53, "aze": 342, "abb": 625, "bb$": 35, "bba": 191, "acy": 331, "cy$": 1273, "cie": 814, "ies": 6007, "com": 3325, "ome": 4017, "mes": 1284, "did": 412, "ide": 3626, "de$": 2241, "ai$": 260, "aye": 391, "ono": 2555, "no$": 507, "sid": 1021, "id$": 3295, "tia": 1866, "tie": 2027, "ie$": 1792, "bbe": 510, "be$": 367, "bey": 71, "ey$": 1196, "eys": 280, "ys$": 802, "yst": 1160, "ste": 5591, "tea": 857, "ead": 1854, "ede": 1357, "bes": 721, "est": 6019, "bev": 70, "evi": 1057, "vil": 1854, "lea": 1588, "ean": 1440, "ill": 5454, "lle": 4458, "lli": 3668, "lia": 2201, "ian": 4955, "bbi": 432, "bi$": 122, "bby": 118, "by$": 277, "bie": 328, "bye": 51, "byv": 5, "yvi": 114, "bbo": 106, "boc": 137, "occ": 596, "cca": 361, "bog": 164, "oga": 738, "gad": 201, "bot": 457, "otc": 151, "tcy": 16, "tci": 17, "otn": 18, "tnu": 12, "nul": 438, "ull": 1349, "liu": 204, "ius": 462, "otr": 915, "tri": 5893, "ric": 4807, "ots": 389, "tse": 116, "sen": 1854, "tsf": 14, "sfo": 117, "for": 3760, "ord": 1440, "tsh": 181, "hip": 1811, "ip$": 1302, "ips": 472, "tso": 131, "tsu": 105, "un$": 309, "ott": 1025, "tt$": 355, "tts": 131, "tst": 212, "tow": 576, "own": 1025, "wn$": 565, "bou": 703, "oud": 209, "ud$": 179, "boz": 29, "ozz": 64, "zzo": 60, "zo$": 100, "bbr": 61, "br$": 10, "bre": 1008, "rev": 1060, "ev$": 66, "via": 546, "iat": 2189, "tel": 2061, "ely": 2247, "ory": 1373, "ry$": 5067, "bro": 1566, "roa": 757, "oac": 384, "chm": 311, "abc": 11, "bc$": 15, "bce": 18, "ces": 1495, "bci": 16, "ssa": 1102, "sa$": 524, "bco": 166, "cou": 1789, "oul": 458, "ulo": 873, "lom": 708, "omb": 747, "mb$": 127, "bcs": 2, "cs$": 1057, "bd$": 10, "bda": 29, "dal": 1313, "dar": 790, "dat": 917, "at$": 946, "del": 1379, "ell": 4807, "lla": 3094, "la$": 1968, "der": 5317, "erh": 474, "rha": 487, "hal": 2676, "ald": 616, "lde": 762, "rit": 2971, "eru": 491, "rus": 1384, "des": 2293, "bdi": 98, "dic": 2029, "cab": 601, "can": 2165, "tiv": 4295, "ive": 5994, "die": 921, "iel": 603, "ito": 1295, "bdo": 136, "dom": 1101, "om$": 841, "ens": 2668, "omi": 2297, "min": 4340, "ino": 2391, "noa": 96, "oan": 425, "rio": 1829, "ior": 450, "noc": 890, "oca": 1503, "car": 3439, "iac": 988, "oce": 1182, "cen": 2307, "esi": 2179, "ocy": 650, "cys": 369, "sti": 6227, "nog": 658, "gen": 3410, "eni": 2960, "ita": 2737, "tal": 3477, "noh": 61, "ohy": 197, "hys": 750, "rec": 3350, "ect": 4422, "tom": 2893, "omy": 1183, "my$": 1090, "ero": 4529, "rot": 2401, "oto": 2500, "nop": 1017, "opo": 1524, "pos": 2181, "ost": 3616, "nos": 1415, "osc": 1128, "sco": 2334, "cop": 1765, "ope": 1758, "pe$": 1014, "opy": 468, "py$": 618, "not": 1020, "oth": 2008, "tho": 2634, "hor": 2668, "ora": 2719, "rac": 3522, "cic": 324, "nou": 1322, "ous": 10931, "nov": 342, "ova": 509, "vag": 323, "agi": 1178, "gin": 1670, "ove": 5403, "bdu": 81, "du$": 56, "duc": 971, "uce": 388, "ced": 816, "uci": 538, "uct": 862, "ct$": 620, "ore": 3083, "cts": 230, "dul": 605, "ul$": 1216, "bea": 730, "eam": 526, "ear": 2134, "ran": 5238, "beb": 67, "ebi": 276, "bec": 371, "ece": 836, "eda": 671, "dai": 277, "air": 986, "ire": 1408, "ary": 1964, "ans": 2575, "rie": 2715, "riu": 602, "ium": 1808, "bed": 763, "edg": 337, "dge": 824, "neg": 378, "ego": 443, "beg": 278, "egg": 210, "gge": 757, "eya": 112, "yan": 643, "ncy": 985, "nci": 1398, "bei": 106, "eig": 497, "igh": 1727, "bel": 1311, "ela": 1827, "ele": 3793, "eli": 3550, "lic": 3067, "ice": 1561, "cea": 1165, "lit": 5370, "ll$": 1615, "elm": 317, "lmo": 327, "mos": 852, "sch": 1451, "chu": 690, "osk": 96, "sks": 53, "lmu": 57, "mus": 844, "usk": 213, "elo": 1288, "nia": 2605, "lso": 148, "elt": 467, "ltr": 407, "tre": 2064, "ree": 2052, "ben": 971, "enc": 3136, "cer": 1934, "err": 2117, "rra": 976, "rag": 1349, "ges": 1079, "end": 3003, "nds": 687, "ds$": 2094, "ene": 6262, "nez": 32, "ezr": 8, "zra": 11, "beo": 21, "eok": 14, "oku": 52, "kut": 42, "uta": 757, "ta$": 1851, "bep": 79, "epi": 1552, "pit": 1148, "ith": 1831, "thy": 1094, "hym": 404, "ymi": 220, "mia": 1088, "epp": 154, "pp$": 60, "erc": 2349, "rcr": 154, "cro": 2488, "rom": 2737, "mby": 44, "mbi": 575, "erd": 1067, "rda": 526, "dav": 100, "avi": 1085, "vin": 1227, "ine": 8390, "rde": 964, "dee": 331, "een": 1030, "nsh": 653, "hir": 681, "dev": 496, "rdo": 432, "rdu": 127, "duv": 23, "uvi": 221, "erf": 1086, "rfa": 184, "fan": 412, "rgl": 128, "gla": 662, "lau": 632, "aub": 136, "ube": 568, "ery": 1468, "rys": 434, "stw": 95, "twy": 7, "wyt": 8, "yth": 574, "th$": 1354, "ern": 1948, "rna": 1042, "nan": 1270, "ath": 2806, "hy$": 1326, "rne": 1072, "eth": 1601, "rno": 303, "non": 8160, "rr$": 97, "ntl": 1147, "tly": 1152, "rat": 5955, "rro": 842, "met": 3704, "ete": 3065, "ert": 2501, "rt$": 1210, "run": 613, "unc": 3147, "nca": 809, "siv": 1104, "bet": 529, "etm": 74, "tme": 398, "ets": 715, "ett": 1998, "tte": 2748, "beu": 21, "eu$": 61, "eva": 704, "vac": 283, "cua": 85, "uat": 706, "abf": 8, "bfa": 19, "far": 521, "rad": 1772, "ads": 426, "bfm": 2, "fm$": 14, "abg": 3, "bga": 9, "gat": 1306, "abh": 35, "bhc": 3, "hc$": 8, "bhe": 27, "enr": 196, "nry": 96, "nri": 234, "bhi": 17, "nay": 67, "ya$": 267, "his": 1410, "sek": 42, "eka": 96, "bho": 50, "hom": 1064, "orr": 1234, "rre": 1608, "red": 4116, "rer": 1389, "rri": 1478, "rib": 897, "ibl": 1424, "rin": 5509, "rso": 593, "abi": 2178, "aby": 117, "bia": 747, "har": 2748, "bib": 224, "ib$": 87, "bic": 529, "ich": 1608, "chi": 4345, "hit": 837, "bid": 286, "ida": 2913, "idd": 451, "dde": 517, "ded": 1508, "idi": 2283, "di$": 260, "din": 2738, "ngl": 2746, "gly": 2176, "ngn": 451, "gne": 960, "idj": 7, "dja": 53, "jan": 193, "byd": 3, "ydo": 109, "dos": 556, "os$": 1697, "ied": 1113, "yed": 276, "ieg": 129, "egh": 37, "yes": 220, "iet": 533, "eta": 2145, "tat": 3209, "ten": 3333, "eti": 2913, "nea": 792, "eae": 829, "neo": 609, "eou": 1362, "ini": 3550, "tit": 1617, "iez": 28, "eze": 134, "zer": 739, "big": 195, "iga": 841, "ils": 392, "lsh": 137, "gal": 1360, "ige": 795, "gea": 430, "eat": 1885, "gei": 112, "ei$": 175, "geu": 56, "eus": 492, "bih": 15, "ihu": 44, "hu$": 57, "byi": 25, "yin": 987, "bij": 19, "ija": 51, "jah": 48, "byl": 57, "yla": 542, "bil": 2549, "lao": 40, "len": 3609, "ili": 4722, "lim": 1005, "ime": 1568, "ily": 1039, "lyn": 274, "yne": 446, "ity": 4174, "ty$": 5118, "ilo": 1081, "lo$": 431, "bim": 95, "me$": 1554, "mel": 1540, "lec": 1749, "ech": 1158, "ch$": 1481, "bin": 1057, "ri$": 574, "ngd": 50, "gdo": 64, "nge": 2272, "ger": 2375, "ngt": 256, "gto": 144, "oam": 142, "noe": 184, "oem": 120, "em$": 269, "int": 4945, "bio": 787, "iog": 379, "ese": 1216, "eny": 296, "ny$": 870, "nis": 3928, "eno": 2306, "iol": 1069, "olo": 4497, "log": 4193, "ogy": 1141, "gy$": 1322, "ogi": 2541, "gic": 1576, "ios": 687, "ose": 2262, "iot": 688, "oti": 1851, "tro": 4443, "rop": 3788, "oph": 4413, "phy": 2588, "phi": 3500, "hic": 1918, "bip": 101, "ipo": 508, "pon": 1075, "biq": 38, "iqu": 523, "qui": 2337, "uiu": 9, "iu$": 26, "bir": 507, "irr": 688, "tan": 2744, "bys": 65, "bis": 496, "sag": 319, "ish": 3928, "sha": 1708, "hag": 794, "isi": 1886, "ysm": 74, "sm$": 4202, "sma": 1069, "mal": 1887, "sms": 360, "ms$": 1567, "yss": 86, "sal": 1658, "sso": 878, "sob": 160, "obe": 558, "nth": 2072, "hon": 1715, "sol": 1358, "oli": 3375, "sop": 764, "pel": 907, "lag": 846, "ssu": 467, "sus": 484, "bit": 1031, "it$": 970, "tib": 629, "ibi": 868, "biu": 83, "iur": 135, "ret": 2694, "bix": 10, "ixa": 70, "xah": 28, "abj": 43, "bje": 173, "jec": 399, "ctl": 83, "ctn": 62, "tne": 690, "bjo": 17, "joi": 178, "oin": 959, "bju": 70, "jud": 294, "udg": 300, "ged": 867, "dgi": 161, "udi": 797, "jug": 161, "uga": 348, "jun": 263, "nct": 628, "jur": 235, "ura": 2113, "rem": 1680, "uri": 2416, "abk": 14, "bka": 5, "kar": 435, "bkh": 7, "kha": 243, "has": 699, "haz": 141, "az$": 51, "azi": 407, "zia": 95, "bl$": 17, "bla": 1129, "lac": 1876, "cta": 932, "laq": 26, "aqu": 368, "que": 1756, "uea": 96, "las": 2475, "emi": 3853, "mic": 3032, "tou": 902, "iou": 2159, "iva": 922, "val": 1214, "vel": 2060, "aut": 1335, "ut$": 579, "uts": 664, "laz": 190, "lee": 659, "eez": 137, "leg": 921, "ega": 1119, "lep": 768, "eph": 1546, "pha": 2984, "rou": 3178, "eps": 288, "psy": 584, "sy$": 447, "psi": 597, "ept": 1342, "ler": 2901, "let": 2014, "lew": 299, "ewh": 82, "wha": 215, "hac": 405, "cke": 1529, "ket": 627, "bly": 1385, "bli": 1065, "lin": 6663, "ngs": 1373, "gs$": 1515, "ins": 2487, "blo": 631, "loc": 1342, "ock": 1718, "loo": 679, "oom": 520, "low": 1193, "ow$": 622, "bls": 3, "blu": 347, "lud": 310, "ude": 696, "lue": 396, "uen": 549, "ush": 832, "lut": 877, "ute": 1170, "uti": 1491, "nar": 1769, "luv": 140, "vio": 320, "abm": 14, "bm$": 17, "bmh": 2, "mho": 43, "ho$": 166, "hos": 961, "bmo": 24, "mod": 626, "oda": 611, "abn": 63, "bn$": 12, "bna": 28, "nak": 169, "aki": 766, "ki$": 269, "kis": 385, "bne": 71, "erv": 1345, "rva": 477, "neu": 1125, "eur": 1222, "bno": 54, "nor": 1149, "orm": 2291, "rma": 1910, "alc": 796, "lcy": 36, "lci": 251, "lis": 4070, "ism": 4916, "liz": 2255, "zed": 1586, "izi": 1267, "zin": 1701, "aln": 610, "lne": 1062, "rmi": 1405, "mit": 1622, "rmo": 993, "mou": 1144, "bnu": 19, "num": 477, "ume": 816, "mer": 2790, "era": 5221, "rab": 1645, "abo": 830, "boa": 563, "oar": 593, "dag": 236, "bob": 153, "obr": 295, "bra": 2304, "oco": 1182, "coc": 979, "bod": 272, "ode": 1497, "dem": 1213, "ody": 463, "dy$": 581, "odi": 1621, "ado": 871, "do$": 458, "boh": 64, "ohm": 40, "hm$": 60, "hms": 18, "boi": 209, "oid": 2647, "dea": 928, "eau": 361, "aus": 712, "aux": 169, "ux$": 163, "oil": 493, "oit": 249, "bol": 862, "ole": 2192, "hab": 742, "her": 4972, "sts": 1104, "niz": 1665, "oll": 1785, "lae": 582, "bom": 175, "oma": 3406, "mas": 1397, "asa": 513, "asu": 308, "sum": 514, "usi": 1107, "bon": 801, "ond": 2232, "ong": 1618, "onn": 755, "nne": 1697, "nem": 752, "boo": 602, "oon": 747, "ori": 4089, "rig": 1152, "igi": 601, "orn": 1294, "rn$": 642, "rni": 1026, "rse": 1346, "rsi": 843, "ort": 3099, "rte": 1449, "ici": 2881, "cid": 1029, "tif": 1089, "ifa": 147, "fac": 702, "ven": 2501, "rto": 506, "tog": 843, "rts": 384, "rtu": 414, "tus": 573, "bos": 364, "ote": 1632, "ouc": 341, "uch": 717, "hem": 1669, "dik": 83, "ikr": 16, "kro": 109, "ro$": 588, "oug": 623, "ugh": 842, "ght": 1711, "ht$": 595, "ouk": 63, "uki": 93, "kir": 200, "oun": 2134, "nde": 5170, "ndi": 2742, "rez": 53, "ezk": 4, "zk$": 3, "out": 2975, "bov": 64, "veb": 27, "ebo": 583, "ved": 519, "dec": 1580, "eck": 695, "veg": 81, "egr": 605, "gro": 842, "vem": 115, "nti": 5809, "vep": 18, "epr": 864, "pro": 5751, "roo": 1507, "oof": 607, "of$": 465, "esa": 406, "sai": 337, "aid": 361, "tai": 1015, "bow": 312, "box": 210, "ox$": 172, "abp": 3, "bp$": 7, "bpc": 2, "pc$": 30, "abq": 1, "bqa": 1, "qai": 5, "aiq": 7, "iq$": 8, "abr": 412, "cad": 454, "dab": 512, "hia": 1006, "rah": 323, "aha": 293, "ham": 1023, "ami": 2220, "mid": 1009, "dae": 1611, "mse": 75, "han": 2426, "ray": 451, "rai": 1417, "mis": 3517, "amo": 1045, "mo$": 188, "mso": 64, "nch": 2305, "hio": 631, "ras": 1754, "sax": 67, "iom": 438, "tol": 1343, "ol$": 873, "aum": 183, "rax": 171, "axa": 110, "xas": 46, "raz": 268, "zit": 74, "azo": 409, "zos": 65, "eac": 821, "eas": 1301, "eed": 1131, "reg": 1126, "ege": 565, "rei": 1204, "eid": 290, "enu": 551, "nun": 295, "cia": 1592, "rep": 2041, "reu": 245, "euv": 47, "uvo": 6, "voi": 169, "bri": 1435, "ico": 1792, "rid": 1927, "idg": 273, "dga": 44, "gab": 321, "eab": 742, "gem": 337, "dgm": 34, "gme": 215, "rim": 1178, "im$": 380, "stl": 497, "tle": 1540, "oad": 488, "roc": 2288, "rog": 1476, "oms": 244, "ood": 1647, "od$": 1170, "ook": 789, "ok$": 335, "ota": 1108, "anu": 529, "bru": 443, "rup": 400, "upt": 324, "pt$": 283, "pte": 943, "ptl": 34, "ptn": 28, "ruz": 19, "uzz": 152, "zzi": 111, "zi$": 73, "abs": 413, "bs$": 280, "bsa": 37, "sam": 389, "sar": 967, "rak": 268, "rok": 190, "oka": 190, "oke": 684, "kee": 387, "oki": 307, "kit": 297, "bsb": 9, "sbh": 2, "bh$": 9, "bsc": 163, "sca": 1449, "cam": 749, "sce": 1225, "ssr": 25, "sro": 52, "oot": 1116, "ind": 2459, "sae": 71, "sas": 229, "con": 6516, "onc": 2085, "nsa": 754, "bse": 242, "sec": 867, "eco": 1943, "see": 476, "sey": 161, "sei": 314, "eil": 241, "led": 2658, "nta": 3317, "tee": 779, "eei": 109, "eis": 742, "ees": 563, "esh": 717, "ntm": 92, "tmi": 44, "ntn": 230, "bsf": 6, "sfa": 65, "bsh": 26, "hie": 843, "ier": 2634, "bsi": 129, "the": 5356, "he$": 571, "thi": 2206, "hii": 43, "iin": 78, "smi": 625, "hiu": 112, "hol": 1880, "ths": 381, "bsy": 12, "syr": 92, "yrt": 51, "tos": 1198, "sit": 1770, "bsm": 15, "smh": 2, "bso": 172, "soh": 38, "soi": 160, "olu": 804, "tiz": 941, "iza": 1685, "zat": 1318, "uto": 1149, "lva": 339, "vab": 300, "vat": 762, "ver": 7779, "lvi": 275, "vit": 890, "sor": 1202, "orb": 501, "rb$": 90, "rbe": 488, "bef": 177, "efa": 357, "rbi": 668, "rbs": 42, "rbt": 4, "bti": 59, "orp": 1150, "rpt": 62, "pta": 403, "etr": 2567, "ivi": 1237, "bsq": 3, "squ": 1050, "qua": 1749, "tul": 534, "bst": 494, "ain": 2272, "inm": 109, "mio": 210, "usl": 1399, "usn": 1264, "sne": 1832, "rge": 1008, "rgi": 737, "nen": 741, "tr$": 35, "ict": 970, "tru": 1332, "rud": 369, "sel": 1313, "bsu": 50, "ump": 688, "mpt": 467, "sur": 1302, "urd": 274, "dis": 4691, "rdl": 225, "rdn": 62, "rds": 608, "bsv": 2, "vol": 825, "olt": 329, "lt$": 414, "abt": 16, "bt$": 19, "bte": 123, "erm": 3274, "bth": 15, "hai": 485, "inr": 64, "nag": 583, "btr": 104, "bu$": 44, "bub": 84, "ubb": 444, "bbl": 400, "buc": 321, "uca": 339, "ucc": 317, "cco": 486, "bui": 110, "uil": 530, "ild": 451, "ldi": 344, "buk": 58, "bul": 1043, "ule": 907, "lei": 548, "eia": 140, "ulf": 339, "lfe": 86, "fed": 286, "uly": 48, "lye": 71, "yei": 12, "eit": 387, "man": 5290, "bum": 245, "umb": 997, "mbr": 704, "rel": 1705, "bun": 322, "una": 1631, "une": 1359, "ozu": 7, "zu$": 8, "gir": 257, "iri": 988, "urb": 495, "ury": 280, "rst": 744, "urt": 490, "bus": 713, "usa": 395, "sab": 751, "sef": 125, "efu": 634, "ful": 2153, "uln": 409, "but": 598, "til": 1591, "utm": 75, "utt": 852, "buz": 35, "zz$": 35, "abv": 3, "bv$": 6, "bvo": 11, "lts": 130, "abw": 11, "bwa": 20, "wab": 148, "wat": 535, "^ac": 2308, "caa": 17, "cac": 458, "tec": 673, "cho": 2777, "ace": 3339, "cet": 681, "cii": 17, "emy": 73, "ics": 872, "mie": 599, "miz": 382, "emu": 320, "cae": 189, "aen": 270, "caj": 42, "ajo": 106, "ou$": 141, "lcu": 123, "ph$": 668, "hae": 615, "phe": 1733, "pho": 4083, "hoi": 276, "phs": 88, "aly": 641, "lyc": 466, "yca": 134, "yci": 106, "ycu": 34, "lyp": 423, "yph": 541, "ypt": 354, "rae": 410, "ptr": 95, "tae": 240, "mar": 2347, "mpo": 761, "po$": 92, "nac": 1101, "ceo": 774, "ano": 1774, "had": 328, "hi$": 323, "hoc": 416, "rpo": 577, "pou": 512, "cep": 1265, "lan": 3734, "lou": 1549, "ocl": 365, "cla": 1461, "lad": 693, "dou": 508, "hod": 460, "dei": 253, "dii": 60, "imo": 686, "mon": 3510, "oly": 1647, "lys": 643, "ysi": 822, "hop": 840, "opa": 1157, "pan": 1709, "nax": 68, "oro": 1868, "pod": 863, "odo": 1237, "pom": 335, "mat": 4554, "por": 2215, "opt": 802, "ryg": 153, "ygi": 139, "gia": 792, "gii": 19, "hot": 1187, "hou": 1080, "thu": 523, "hur": 583, "uru": 317, "hut": 189, "uth": 809, "cap": 1165, "apn": 63, "pni": 28, "app": 1658, "ppe": 1269, "psu": 104, "sul": 1131, "apu": 236, "pu$": 30, "pul": 1031, "ulc": 274, "lco": 301, "rap": 3456, "pis": 965, "rar": 584, "atr": 1393, "ido": 773, "tiu": 155, "ids": 416, "rif": 1095, "ifo": 1034, "rm$": 1224, "arn": 804, "cec": 94, "eci": 1161, "diu": 409, "rod": 1023, "roi": 816, "rol": 1655, "gis": 1259, "hil": 1790, "hob": 632, "obi": 1120, "tox": 401, "oxi": 719, "xic": 265, "rpe": 486, "llo": 2224, "tam": 779, "tap": 640, "aph": 3207, "apo": 1121, "tas": 952, "teg": 306, "gor": 567, "ars": 1186, "rsy": 49, "cau": 460, "aud": 504, "uda": 386, "esc": 1367, "aul": 567, "los": 1566, "caw": 32, "aws": 166, "ws$": 374, "acb": 8, "cb$": 15, "cbl": 3, "acc": 1300, "cc$": 51, "cce": 355, "edi": 2096, "cel": 1239, "ogr": 2263, "gra": 4758, "dib": 244, "nse": 1447, "nsi": 1712, "nto": 1645, "ntu": 541, "uab": 126, "ual": 1087, "tav": 159, "pto": 814, "pts": 72, "ril": 1173, "ysh": 94, "sib": 726, "sle": 527, "rii": 147, "uso": 112, "riz": 1414, "cci": 470, "ngi": 1163, "cip": 569, "ipe": 723, "pen": 2056, "ipi": 454, "pie": 811, "itr": 885, "ipt": 476, "smu": 217, "cit": 1230, "ciu": 111, "ccl": 167, "lai": 693, "aim": 243, "ima": 1317, "mab": 270, "med": 1335, "imi": 1211, "ims": 163, "lam": 1357, "sat": 1242, "zab": 270, "zes": 585, "liv": 429, "ivo": 363, "vou": 260, "clo": 967, "loy": 190, "oy$": 267, "coa": 665, "oas": 230, "coy": 65, "oye": 151, "oyi": 80, "coi": 477, "cok": 39, "eek": 234, "ek$": 177, "col": 2334, "ola": 1833, "mac": 1197, "omm": 1045, "mmo": 584, "omo": 1801, "omp": 1571, "mpa": 884, "any": 251, "nie": 831, "nyi": 65, "yis": 245, "nim": 675, "mpl": 1012, "ple": 2078, "lem": 1063, "pli": 1174, "sht": 92, "cor": 2508, "dio": 1133, "cos": 1053, "heu": 130, "uns": 2645, "unt": 2480, "ntr": 2421, "oup": 250, "upl": 325, "rme": 956, "utr": 459, "cov": 275, "ovi": 667, "ccr": 78, "cra": 1859, "cre": 1615, "itm": 92, "its": 384, "cri": 1549, "cru": 647, "rua": 95, "rue": 195, "ued": 223, "uem": 46, "uer": 439, "ues": 498, "rui": 267, "uin": 940, "ccs": 11, "cct": 7, "ccu": 435, "cub": 226, "uba": 461, "ubi": 547, "itu": 1035, "cue": 65, "uei": 36, "ult": 1731, "ltu": 289, "cum": 789, "mbe": 797, "umu": 164, "mul": 1296, "lab": 1216, "iv$": 43, "cup": 314, "upy": 26, "cur": 1295, "urr": 696, "rta": 809, "rix": 201, "ix$": 295, "ixe": 149, "xes": 300, "ust": 1834, "cut": 681, "acd": 13, "cd$": 31, "cda": 11, "nap": 764, "pht": 446, "hth": 639, "hre": 521, "neq": 110, "equ": 940, "eca": 1151, "caf": 90, "ffi": 798, "fin": 1016, "edy": 83, "iam": 300, "cey": 46, "cei": 261, "tun": 485, "eld": 674, "lda": 248, "dam": 527, "llu": 827, "lul": 213, "cem": 340, "mil": 1229, "nyl": 234, "yl$": 466, "hyl": 984, "yle": 589, "sth": 633, "nsu": 1145, "sua": 312, "uad": 401, "dor": 907, "eol": 645, "alu": 615, "epo": 627, "pot": 893, "ceq": 6, "uia": 93, "iad": 210, "erb": 1210, "tya": 52, "yac": 114, "tud": 381, "rbl": 166, "rbo": 744, "bop": 81, "dol": 631, "erl": 1474, "rli": 933, "tim": 1221, "rvo": 142, "vos": 58, "rvu": 31, "vul": 367, "siu": 88, "eso": 968, "sod": 229, "dyn": 355, "yno": 317, "ame": 1956, "nus": 468, "lif": 894, "ife": 986, "fer": 2131, "lum": 1256, "ums": 586, "tac": 1082, "ehy": 135, "hyd": 1166, "yda": 112, "das": 317, "yde": 189, "ydr": 1050, "dra": 1321, "ldo": 215, "nol": 959, "nil": 389, "lid": 976, "nio": 718, "taz": 30, "zol": 150, "etb": 47, "tbr": 95, "mam": 277, "zid": 38, "ify": 779, "fy$": 615, "ifi": 2000, "fic": 1521, "fie": 1197, "fyi": 326, "ety": 347, "tyl": 808, "eto": 1160, "nob": 476, "enz": 396, "nze": 136, "zen": 300, "icy": 206, "cyl": 181, "yli": 698, "ylb": 14, "lbe": 232, "nzo": 188, "zoa": 162, "oat": 636, "zoi": 154, "oic": 349, "lbi": 131, "ylc": 18, "lca": 317, "lce": 103, "lch": 228, "cya": 282, "nid": 805, "ein": 1432, "ylf": 4, "lfl": 50, "flu": 770, "luo": 236, "uor": 216, "ylg": 21, "lgl": 13, "ylh": 12, "lhy": 14, "lio": 1157, "iod": 356, "ylm": 21, "lme": 339, "ylp": 25, "lpe": 120, "rox": 258, "xid": 242, "lph": 688, "ylr": 2, "lro": 80, "osa": 786, "yls": 97, "lsa": 122, "lol": 229, "ylt": 18, "lta": 305, "lth": 185, "ymo": 318, "mol": 853, "pei": 130, "ylu": 69, "lur": 431, "try": 899, "etl": 173, "tla": 224, "etn": 56, "tna": 50, "toa": 183, "dop": 400, "tob": 246, "oba": 692, "toc": 1111, "och": 1681, "chl": 799, "hlo": 606, "lor": 1661, "oci": 658, "inn": 878, "nna": 847, "nam": 818, "lyt": 411, "yti": 429, "mor": 2168, "rph": 859, "nae": 517, "aem": 386, "ony": 680, "onu": 334, "nur": 333, "uro": 1116, "top": 1534, "tid": 869, "opi": 1557, "pip": 326, "pyr": 643, "yri": 561, "oso": 791, "lub": 288, "ubl": 449, "tot": 688, "lui": 168, "uid": 330, "tov": 102, "oxy": 518, "xyl": 248, "xim": 135, "xyp": 31, "etp": 20, "tph": 15, "etu": 311, "acf": 5, "cf$": 19, "acg": 3, "cgi": 8, "cha": 3761, "aea": 289, "nod": 444, "aet": 217, "aeu": 83, "haf": 175, "afe": 182, "fe$": 185, "agu": 442, "gua": 597, "aia": 90, "hak": 208, "akz": 2, "kza": 3, "zai": 38, "mot": 954, "hap": 524, "ape": 975, "haq": 9, "rya": 143, "hat": 667, "nel": 1048, "hea": 2198, "hec": 507, "hee": 740, "eer": 721, "hef": 55, "eft": 116, "hei": 556, "eir": 235, "iro": 662, "iru": 145, "hel": 1502, "niu": 370, "ont": 3068, "sou": 462, "het": 1158, "eul": 87, "hew": 145, "ewe": 404, "wee": 781, "chy": 740, "iev": 314, "eve": 1483, "hig": 271, "gan": 1251, "lob": 545, "obu": 226, "lod": 491, "yni": 215, "ylo": 703, "him": 326, "maa": 37, "ira": 831, "hyr": 345, "yra": 350, "yro": 614, "chk": 41, "hka": 29, "hla": 136, "amy": 326, "myd": 58, "deo": 251, "orh": 226, "rhy": 259, "dri": 1080, "yll": 682, "ops": 635, "hlu": 25, "uop": 23, "hok": 111, "ke$": 2209, "loe": 172, "oe$": 165, "maw": 41, "awi": 131, "wi$": 32, "ndr": 1177, "dro": 1928, "opl": 891, "pla": 2840, "hoo": 1020, "oo$": 214, "chr": 1444, "hra": 567, "cyt": 537, "yte": 427, "dex": 151, "ext": 1069, "xtr": 518, "nas": 979, "ogl": 420, "glo": 958, "oio": 48, "ioc": 405, "pia": 482, "myc": 310, "mob": 210, "iea": 20, "mop": 457, "nyc": 126, "ych": 704, "oou": 25, "chs": 177, "hsa": 34, "sah": 60, "cht": 382, "hte": 476, "teh": 59, "eha": 274, "rve": 624, "ld$": 722, "hua": 124, "uas": 152, "hue": 56, "uet": 292, "obl": 552, "cyc": 438, "ycl": 454, "arl": 943, "rly": 496, "asp": 579, "spi": 1636, "idh": 31, "dhe": 169, "idy": 160, "dif": 533, "fia": 263, "iab": 612, "dyl": 221, "dim": 367, "idl": 237, "idn": 165, "dog": 400, "doi": 147, "ilu": 227, "opr": 491, "teo": 470, "eop": 575, "hyt": 606, "dot": 313, "idp": 14, "dpr": 29, "idu": 297, "dur": 372, "cye": 29, "yet": 85, "cif": 430, "lal": 211, "dob": 125, "cil": 649, "loi": 627, "lox": 84, "xy$": 163, "xym": 17, "yme": 432, "cim": 166, "fol": 531, "cio": 865, "nif": 835, "otu": 224, "tub": 466, "ubu": 161, "inu": 642, "uni": 1960, "cyr": 58, "rgy": 120, "key": 321, "ker": 2206, "rle": 781, "ley": 579, "anv": 72, "nvi": 675, "ckl": 651, "kle": 725, "ckm": 135, "kma": 185, "kme": 48, "ckn": 158, "kne": 231, "new": 297, "ew$": 273, "kno": 308, "now": 344, "owi": 331, "win": 1421, "owl": 413, "wle": 244, "ckt": 103, "kto": 119, "ckw": 167, "kwo": 106, "wor": 1766, "cl$": 31, "cle": 1509, "emo": 1785, "cly": 133, "lyd": 82, "cls": 4, "clu": 448, "lu$": 69, "acm": 33, "cm$": 26, "cma": 31, "mae": 175, "aei": 62, "aes": 272, "cme": 16, "cmi": 13, "isp": 986, "spo": 1528, "cmo": 12, "acn": 26, "cne": 135, "nef": 235, "efo": 425, "nei": 273, "eif": 104, "cni": 53, "cno": 50, "asm": 548, "ckb": 175, "kbi": 45, "otl": 142, "tl$": 49, "coe": 561, "oel": 391, "mi$": 192, "ti$": 471, "oen": 403, "cof": 146, "lap": 561, "old": 987, "olh": 37, "lhu": 6, "uan": 424, "yct": 84, "ytu": 24, "cag": 125, "ndy": 330, "coo": 385, "ool": 684, "op$": 394, "rns": 233, "oru": 259, "osm": 338, "oty": 504, "edo": 599, "oua": 73, "oum": 106, "ouo": 2, "uom": 20, "upa": 189, "pa$": 234, "upe": 2125, "usm": 31, "toe": 203, "ctr": 888, "acp": 5, "cp$": 22, "cpt": 5, "acq": 181, "cqu": 203, "uah": 21, "uai": 104, "uav": 49, "viv": 244, "va$": 251, "uie": 147, "uir": 256, "uis": 663, "uit": 764, "itt": 1255, "quo": 216, "acr": 1009, "asy": 226, "spe": 2065, "ped": 1331, "raw": 446, "awl": 205, "wl$": 101, "eag": 249, "eak": 525, "ak$": 371, "edu": 535, "ema": 2211, "taf": 141, "iid": 242, "cry": 547, "ryd": 46, "ydi": 62, "gus": 296, "ifl": 228, "fla": 1070, "lav": 542, "ryl": 245, "yly": 71, "lyl": 86, "iny": 153, "isy": 116, "crn": 3, "oa$": 164, "oae": 25, "hri": 716, "sph": 905, "hyx": 18, "yxi": 44, "tax": 324, "rob": 947, "oby": 50, "bry": 172, "ryo": 378, "you": 142, "rpi": 301, "pi$": 106, "aun": 516, "ctu": 770, "dac": 546, "cty": 284, "odr": 180, "odu": 371, "dus": 369, "roe": 364, "oes": 410, "gam": 700, "gyn": 266, "yna": 497, "gie": 549, "ogu": 298, "gue": 718, "meg": 344, "alg": 466, "lgi": 261, "icr": 1074, "mim": 287, "vic": 651, "myo": 310, "yod": 85, "ioh": 30, "hyo": 209, "yoi": 69, "ohu": 56, "hum": 706, "yot": 161, "amm": 1047, "mma": 724, "mph": 928, "rco": 923, "yc$": 10, "nym": 287, "ym$": 62, "yms": 21, "nyx": 24, "yx$": 52, "nom": 1392, "par": 3643, "pat": 1973, "pet": 1002, "pol": 2348, "ror": 478, "rrh": 578, "rhe": 536, "eum": 478, "uma": 781, "rca": 529, "rcu": 849, "scl": 315, "som": 1264, "osp": 1221, "elu": 342, "pir": 753, "oss": 1260, "leu": 670, "eut": 508, "rux": 16, "crv": 1, "rv$": 20, "acs": 106, "cse": 10, "csn": 3, "csu": 2, "su$": 75, "aeo": 365, "eon": 465, "ctg": 3, "tg$": 15, "cth": 16, "nau": 337, "yma": 409, "iae": 153, "iar": 678, "ohe": 324, "lot": 1179, "ocr": 660, "ocu": 536, "ogo": 408, "gon": 1154, "noi": 583, "gou": 357, "yce": 362, "yco": 400, "myx": 104, "phr": 671, "hry": 212, "pra": 952, "xis": 303, "gio": 513, "aer": 488, "reo": 793, "eos": 339, "peu": 99, "apy": 189, "oxe": 175, "xem": 85, "noz": 40, "ozo": 345, "oal": 259, "zoo": 417, "onl": 418, "nle": 484, "tip": 670, "ipy": 65, "pyl": 115, "vis": 1069, "viz": 90, "yos": 148, "rsh": 770, "ctp": 3, "tpu": 37, "ssy": 170, "uar": 611, "tuo": 177, "uos": 66, "tup": 207, "tut": 354, "cu$": 42, "uae": 35, "cuc": 89, "ucl": 223, "osu": 300, "cud": 90, "udu": 32, "cui": 117, "leo": 633, "mbl": 582, "umi": 796, "upr": 353, "pre": 6742, "upu": 88, "pun": 477, "shn": 403, "hne": 500, "ngu": 978, "gul": 643, "ipl": 561, "rav": 829, "acv": 1, "cv$": 5, "acw": 7, "cw$": 6, "cwa": 6, "wa$": 150, "cwo": 6, "cwp": 2, "wp$": 11, "acx": 2, "cxo": 1, "xoy": 2, "oya": 264, "yat": 104, "atl": 199, "^ad": 1539, "dad": 119, "agy": 118, "sim": 646, "day": 227, "aih": 11, "iha": 71, "rsv": 84, "svi": 479, "irv": 38, "rvi": 684, "ays": 445, "aiz": 48, "alh": 38, "lhe": 93, "iah": 98, "tli": 635, "awa": 429, "mbu": 399, "mec": 283, "ec$": 122, "mek": 36, "amh": 21, "mik": 66, "ik$": 236, "nah": 134, "mok": 106, "msb": 22, "sba": 124, "msk": 16, "ski": 799, "mst": 133, "msu": 9, "msv": 8, "gle": 1180, "dao": 8, "dap": 178, "apa": 685, "pid": 567, "arm": 1071, "aty": 224, "dau": 148, "aur": 854, "daw": 88, "aw$": 259, "awe": 165, "we$": 61, "wlu": 2, "awn": 249, "dax": 16, "daz": 61, "azz": 134, "zzl": 203, "zle": 204, "adb": 71, "db$": 16, "adc": 53, "dc$": 26, "dcc": 6, "ccp": 2, "dci": 3, "dco": 33, "dcr": 28, "raf": 551, "dd$": 56, "dda": 138, "axe": 162, "ddc": 4, "dcp": 4, "ddd": 4, "deb": 358, "ebt": 34, "eem": 316, "rfi": 269, "fis": 949, "rsp": 259, "erw": 642, "rwo": 295, "ddi": 462, "ddy": 133, "iso": 1466, "dys": 275, "ddl": 546, "dle": 1263, "leb": 455, "ebr": 686, "leh": 159, "ehe": 508, "epa": 837, "epl": 381, "plo": 834, "dli": 490, "ddn": 6, "dn$": 29, "dnl": 2, "nl$": 20, "doo": 239, "ddr": 50, "dr$": 27, "dre": 664, "ssf": 63, "sfu": 105, "sog": 201, "dds": 20, "ddu": 49, "cib": 215, "eba": 512, "ayo": 118, "yo$": 54, "ems": 134, "eep": 586, "ep$": 179, "dey": 34, "laj": 13, "aja": 108, "ja$": 87, "tad": 266, "elb": 152, "elg": 51, "lge": 233, "elh": 60, "za$": 183, "cod": 401, "lop": 1111, "elp": 236, "hog": 455, "oi$": 150, "elr": 54, "lri": 47, "emp": 1141, "lgy": 15, "nec": 697, "lpy": 8, "ncr": 541, "rci": 564, "nof": 120, "ofi": 255, "fib": 276, "ibr": 551, "hyp": 1829, "ype": 1235, "ypo": 834, "pop": 620, "yse": 224, "sea": 627, "eal": 1439, "lym": 378, "ymp": 534, "yof": 21, "lip": 803, "yom": 98, "yxo": 83, "xom": 47, "xos": 73, "ncu": 527, "ryn": 339, "yng": 271, "git": 569, "phl": 285, "egm": 156, "gmo": 96, "alm": 739, "lmi": 316, "typ": 732, "phu": 227, "vir": 399, "eod": 184, "dep": 770, "ago": 933, "deq": 36, "uac": 105, "esm": 385, "smy": 18, "esp": 871, "det": 564, "deu": 154, "eui": 33, "dew": 165, "adf": 70, "df$": 20, "dfe": 16, "fec": 483, "dff": 2, "ffr": 183, "fro": 573, "roz": 175, "oze": 174, "dfi": 93, "fil": 640, "fix": 163, "dfl": 38, "lux": 140, "uxi": 87, "xio": 135, "dfr": 16, "fre": 707, "ezi": 102, "frf": 2, "rf$": 73, "adg": 67, "dgl": 12, "glu": 342, "adh": 151, "dha": 139, "mh$": 10, "dhi": 54, "hib": 285, "dho": 139, "ady": 225, "doc": 607, "kin": 2519, "dok": 28, "oko": 128, "kok": 63, "iag": 267, "agn": 596, "gno": 497, "iap": 223, "pne": 357, "iba": 345, "ieu": 81, "eux": 53, "dig": 475, "dyg": 20, "yge": 137, "ghe": 263, "ygh": 5, "igr": 472, "igu": 372, "gun": 240, "dil": 457, "inv": 579, "nve": 782, "dip": 540, "ipa": 650, "pes": 527, "iph": 668, "pin": 2258, "poc": 382, "pof": 23, "pog": 232, "poi": 503, "pec": 1058, "pex": 89, "exi": 558, "pso": 223, "dir": 252, "cks": 918, "dyt": 42, "yta": 88, "yto": 607, "ytt": 41, "div": 561, "vas": 415, "iz$": 42, "adj": 251, "dj$": 4, "jac": 416, "jag": 78, "dje": 41, "dji": 26, "jig": 55, "djo": 37, "urn": 843, "rnm": 83, "djt": 2, "jt$": 4, "dju": 156, "jum": 86, "jus": 206, "stm": 195, "jut": 58, "juv": 59, "uva": 60, "van": 860, "adk": 14, "dki": 24, "adl": 224, "dla": 97, "lay": 566, "egi": 592, "dlu": 20, "adm": 345, "dm$": 21, "dma": 205, "mah": 184, "max": 161, "dmd": 3, "md$": 19, "dme": 128, "mea": 502, "vey": 130, "eyl": 61, "dmi": 240, "icl": 324, "nst": 1520, "mir": 433, "lty": 118, "lti": 1046, "tty": 182, "mix": 145, "xed": 169, "ixi": 132, "xin": 326, "ixt": 90, "xt$": 43, "xti": 109, "xtu": 93, "dmo": 133, "dmr": 1, "mrx": 1, "rx$": 7, "adn": 83, "dna": 61, "asc": 864, "nep": 423, "nex": 704, "ex$": 224, "exa": 574, "xa$": 33, "xal": 134, "exe": 394, "xit": 110, "exo": 351, "xop": 72, "exy": 64, "dno": 41, "poz": 23, "oz$": 27, "dnu": 9, "obo": 377, "dod": 219, "lfo": 154, "fo$": 43, "olp": 129, "nai": 362, "nij": 8, "nir": 142, "noy": 48, "oor": 383, "orl": 302, "rl$": 146, "dow": 554, "owa": 266, "dox": 150, "oxa": 204, "xac": 109, "xie": 88, "oxo": 103, "xog": 46, "doz": 53, "adp": 41, "dp$": 18, "dpa": 57, "pao": 15, "dpc": 2, "pcm": 2, "dpo": 32, "adr": 601, "mme": 1151, "amt": 29, "mt$": 27, "dry": 190, "enn": 971, "ift": 335, "rip": 1069, "itl": 204, "itn": 72, "oop": 430, "row": 910, "ows": 431, "wse": 76, "dru": 311, "dsb": 28, "bud": 155, "dsc": 43, "scr": 1393, "dse": 79, "dsh": 136, "dsi": 64, "sig": 703, "ign": 1072, "gni": 511, "dsm": 95, "dso": 107, "dsp": 60, "sp$": 81, "dsr": 3, "sr$": 26, "dst": 202, "ipu": 206, "dsu": 17, "adt": 51, "dt$": 85, "dte": 12, "tev": 70, "adu": 276, "dua": 202, "lte": 591, "ods": 299, "ltl": 43, "lik": 1777, "ike": 1939, "ltn": 12, "dun": 283, "nc$": 51, "nco": 2511, "duw": 3, "uwa": 22, "adv": 367, "dv$": 8, "vai": 136, "ait": 467, "civ": 208, "geo": 650, "dve": 199, "vec": 96, "veh": 51, "ref": 1277, "rsa": 530, "sif": 346, "rsu": 318, "zem": 71, "dvi": 132, "cef": 95, "dvo": 39, "voc": 327, "aat": 17, "voy": 62, "yer": 421, "vok": 76, "vot": 142, "vow": 82, "owe": 983, "owr": 53, "wry": 33, "wsa": 9, "wso": 43, "dvt": 1, "vt$": 7, "adw": 95, "dwa": 195, "war": 1476, "dwe": 99, "wes": 224, "adz": 17, "dz$": 7, "dze": 12, "dzh": 16, "zha": 15, "dzo": 16, "oks": 148, "^ae": 573, "aec": 135, "hma": 311, "mag": 861, "hmo": 135, "iof": 43, "ofo": 116, "aed": 204, "deg": 208, "ilb": 151, "rct": 98, "doe": 155, "oea": 109, "oeo": 74, "eet": 558, "aef": 30, "ef$": 108, "fal": 413, "ldy": 27, "ldn": 49, "fau": 180, "uld": 165, "aeg": 76, "gag": 158, "agr": 627, "gri": 739, "pil": 734, "gru": 300, "gil": 477, "gim": 93, "miu": 112, "gip": 47, "egy": 64, "gyp": 80, "ptu": 258, "gyr": 208, "ogn": 417, "gna": 694, "giu": 126, "egl": 213, "gop": 144, "gos": 377, "eip": 47, "aek": 4, "eke": 159, "nig": 471, "igm": 241, "gma": 459, "eea": 52, "lha": 97, "lig": 1214, "ols": 335, "lsk": 33, "skl": 33, "kla": 128, "vie": 381, "aep": 24, "epy": 36, "pyc": 57, "pyo": 94, "yor": 99, "hid": 507, "hif": 182, "pyt": 54, "aeq": 11, "eq$": 8, "ui$": 77, "uic": 173, "uip": 116, "pal": 1612, "alp": 421, "lpi": 236, "bee": 310, "lpo": 96, "urv": 233, "emb": 949, "rof": 533, "ofl": 162, "flo": 948, "foi": 92, "eog": 294, "osy": 243, "roh": 184, "roy": 195, "roj": 72, "oje": 66, "jet": 91, "eor": 357, "pau": 225, "ank": 881, "nkt": 48, "opu": 366, "uls": 427, "lse": 179, "spa": 1155, "pac": 737, "chn": 405, "hni": 224, "rov": 715, "iew": 142, "rug": 214, "ugi": 142, "ugo": 81, "sac": 641, "sc$": 74, "hyn": 127, "piu": 67, "sep": 564, "epu": 353, "pus": 352, "esy": 107, "sye": 16, "sir": 215, "tet": 695, "ciz": 315, "tii": 34, "uou": 404, "ioi": 186, "heo": 520, "iop": 571, "sau": 484, "tek": 48, "aev": 44, "evu": 48, "vum": 14, "^af": 596, "afa": 91, "fa$": 67, "fad": 83, "fai": 246, "fam": 186, "fat": 331, "atd": 16, "tds": 2, "afb": 5, "fb$": 9, "afc": 5, "fc$": 16, "fca": 15, "fcc": 3, "afd": 7, "fd$": 20, "fde": 1, "fea": 290, "feb": 46, "fen": 388, "fet": 226, "ffa": 145, "fab": 163, "ffe": 905, "ssn": 412, "fee": 221, "eeb": 95, "ebl": 167, "fei": 124, "enp": 47, "npi": 123, "nsc": 564, "nsp": 712, "sos": 243, "ffy": 60, "nad": 630, "fid": 287, "avy": 43, "vy$": 100, "fyd": 3, "fir": 450, "irm": 274, "rml": 65, "mly": 57, "rms": 175, "xab": 43, "xat": 120, "xer": 219, "ixm": 8, "xme": 9, "ffl": 370, "fli": 479, "uxe": 63, "ffo": 142, "fod": 30, "orc": 609, "rce": 732, "fra": 1031, "ayi": 188, "htm": 37, "fri": 617, "htf": 84, "tfu": 379, "hti": 194, "hts": 208, "nty": 181, "fft": 12, "ffu": 151, "fus": 477, "daf": 50, "afg": 12, "fg$": 4, "fge": 3, "fgh": 7, "gha": 217, "fgo": 4, "god": 203, "afi": 125, "fi$": 34, "fif": 34, "fik": 12, "iko": 116, "kom": 74, "afy": 8, "fyo": 3, "yon": 271, "fip": 8, "afl": 68, "fl$": 14, "flc": 3, "fle": 836, "lex": 461, "ick": 1896, "loa": 394, "wer": 805, "luk": 69, "afm": 3, "afn": 13, "fno": 2, "afo": 80, "foa": 46, "foc": 103, "foo": 414, "goi": 171, "reh": 535, "rew": 556, "ewa": 531, "fou": 313, "afp": 1, "fp$": 5, "afr": 90, "fr$": 15, "cah": 82, "kaa": 15, "kah": 79, "afs": 32, "fs$": 292, "fsc": 12, "scm": 7, "fsh": 16, "hah": 74, "fsk": 17, "fta": 62, "fte": 379, "irt": 351, "rbr": 184, "rbu": 286, "rch": 1952, "urc": 298, "rcl": 179, "rdr": 156, "eff": 430, "rey": 213, "eye": 389, "rfe": 331, "rfo": 209, "rfr": 83, "fru": 266, "rfu": 246, "fut": 120, "utu": 203, "gas": 792, "gli": 668, "rgo": 352, "goo": 267, "rgr": 174, "ief": 167, "owt": 98, "wth": 52, "rgu": 196, "atc": 620, "tch": 1543, "lp$": 59, "rho": 464, "rye": 61, "yea": 100, "imp": 1793, "mpr": 591, "erk": 252, "rki": 336, "rkn": 32, "fes": 351, "rlo": 303, "lov": 294, "rke": 440, "ilk": 152, "lk$": 166, "noo": 176, "erp": 1376, "rpa": 299, "pai": 347, "pas": 890, "pea": 658, "iec": 208, "rpl": 234, "rpr": 431, "ake": 1388, "cko": 136, "kon": 185, "rsc": 143, "hav": 218, "sho": 1109, "pee": 316, "eec": 195, "spr": 624, "pri": 1611, "etc": 208, "udy": 39, "sup": 2391, "upp": 478, "rsw": 62, "swa": 462, "swe": 414, "wel": 659, "ink": 848, "nke": 603, "rtr": 311, "atm": 145, "rwa": 255, "wal": 566, "was": 374, "rwh": 47, "whi": 743, "rwi": 194, "wis": 755, "isd": 179, "sdo": 61, "wit": 540, "ork": 827, "rld": 61, "rwr": 40, "wra": 176, "wri": 317, "ftm": 25, "tmo": 130, "fto": 38, "ftr": 6, "ftw": 33, "twa": 294, "afu": 28, "fun": 349, "fuu": 1, "uu$": 9, "afw": 6, "fwi": 15, "wil": 450, "afz": 1, "fze": 2, "zel": 157, "^ag": 902, "gac": 80, "inb": 163, "nbu": 258, "buy": 31, "uy$": 59, "say": 154, "inw": 139, "nwa": 291, "law": 278, "awo": 54, "woo": 934, "lax": 156, "axy": 48, "lma": 430, "alw": 71, "lwo": 127, "mem": 287, "emn": 154, "mno": 179, "agl": 232, "mog": 442, "moi": 311, "rmy": 89, "nip": 310, "ipp": 865, "gao": 27, "aon": 43, "gap": 114, "pae": 185, "eic": 205, "pem": 76, "gar": 1135, "rum": 884, "arw": 144, "siz": 185, "sty": 623, "tew": 127, "hau": 461, "yrs": 26, "kak": 41, "ako": 124, "kol": 156, "gav": 77, "avo": 462, "gaw": 62, "wam": 100, "gaz": 112, "agb": 39, "gba": 49, "gbo": 85, "agc": 4, "gc$": 7, "gca": 9, "gcy": 1, "gct": 3, "agd": 32, "gd$": 9, "gdi": 16, "gee": 184, "aiu": 8, "ois": 710, "eom": 304, "get": 429, "agg": 852, "gga": 220, "ppo": 471, "ggi": 533, "ggy": 93, "ggl": 378, "ggr": 159, "ava": 714, "gre": 1166, "gry": 41, "upm": 21, "pme": 86, "ggu": 11, "gur": 355, "agh": 127, "stn": 106, "ghl": 62, "gho": 146, "gib": 253, "gyi": 18, "yie": 50, "yio": 6, "lel": 364, "ilm": 184, "inc": 2162, "itp": 21, "tpr": 111, "unk": 470, "kt$": 23, "agk": 3, "gki": 15, "gl$": 10, "aos": 36, "aoz": 1, "zon": 342, "eaf": 237, "imm": 716, "pay": 158, "luc": 518, "uco": 370, "agm": 194, "gm$": 27, "gmi": 59, "gn$": 97, "oet": 286, "toz": 114, "gnu": 35, "gog": 232, "og$": 196, "goh": 13, "oho": 155, "gom": 228, "omu": 160, "uty": 101, "agp": 21, "gpa": 10, "gr$": 21, "map": 138, "anl": 267, "nly": 274, "ibu": 377, "hoe": 310, "oer": 226, "ypi": 214, "ypu": 26, "ryp": 370, "ypn": 136, "iai": 23, "pno": 165, "ppa": 281, "ppi": 634, "olc": 98, "myz": 25, "yza": 51, "yzi": 28, "emm": 261, "hny": 16, "ruf": 133, "ufe": 34, "uif": 53, "if$": 77, "ags": 128, "gsa": 19, "gst": 141, "gt$": 27, "gtb": 1, "tba": 134, "gu$": 36, "uay": 46, "uaj": 7, "aji": 48, "ji$": 49, "uam": 135, "gud": 34, "uey": 31, "uel": 363, "uep": 26, "uew": 14, "eds": 260, "gug": 20, "ugl": 96, "gui": 725, "uij": 5, "ilt": 369, "hly": 399, "guj": 9, "ujo": 10, "jon": 101, "ulh": 13, "ung": 1237, "agw": 37, "gwa": 68, "way": 625, "^ah": 112, "haa": 31, "amk": 10, "mka": 6, "nka": 163, "ntc": 20, "huy": 17, "uyu": 8, "yuk": 24, "uk$": 122, "av$": 44, "sue": 109, "ahc": 4, "hch": 33, "ahd": 21, "hde": 23, "eap": 282, "hey": 92, "hep": 338, "tok": 151, "okl": 61, "ahg": 10, "hgw": 2, "wah": 53, "ahh": 5, "hhi": 11, "hiy": 6, "iya": 82, "yaw": 51, "aaz": 2, "msa": 33, "ahy": 55, "hlg": 4, "lgr": 44, "luw": 3, "ahm": 79, "mad": 411, "diy": 11, "dpu": 4, "pur": 768, "mee": 145, "ahn": 28, "hnf": 1, "nfe": 622, "fel": 432, "aho": 146, "hoy": 39, "oys": 148, "lah": 169, "lds": 216, "seb": 157, "kie": 527, "ouf": 62, "ouh": 13, "uh$": 18, "aht": 13, "ahq": 1, "hq$": 6, "ahr": 35, "ndt": 43, "ahk": 6, "ahu": 96, "ueh": 25, "ehu": 83, "hul": 172, "hun": 388, "ngr": 601, "huu": 4, "uul": 5, "huz": 22, "zza": 97, "ahv": 12, "hva": 21, "vaz": 3, "hve": 10, "anm": 50, "nma": 463, "ahw": 18, "hwa": 209, "waz": 16, "^ai": 417, "^ay": 80, "iaa": 7, "hui": 60, "usc": 449, "huc": 100, "yal": 302, "yap": 42, "iaw": 5, "won": 113, "aib": 29, "ibo": 211, "aic": 221, "icc": 134, "ayc": 31, "iff": 648, "ayd": 48, "nn$": 263, "idf": 12, "dfu": 82, "ydl": 3, "idm": 28, "aie": 36, "iee": 8, "eee": 5, "yeg": 13, "yel": 316, "yen": 96, "enb": 182, "nbi": 119, "aif": 20, "aig": 194, "igl": 208, "esq": 265, "ih$": 22, "ayh": 22, "yh$": 3, "ayy": 11, "yyu": 1, "yub": 8, "aik": 96, "ken": 746, "iki": 260, "kid": 107, "iku": 28, "kuc": 10, "ayl": 153, "esb": 153, "yn$": 226, "llt": 53, "lsy": 18, "syt": 17, "lss": 13, "lsu": 32, "lsw": 16, "swo": 304, "ylw": 1, "lwa": 106, "ilw": 57, "lwe": 52, "aym": 130, "mak": 736, "imf": 22, "mfu": 53, "iml": 51, "mle": 169, "imw": 18, "mwe": 20, "mwo": 32, "ayn": 116, "inh": 296, "nhu": 136, "lls": 499, "nsl": 244, "sli": 584, "nsw": 147, "ynt": 230, "nu$": 52, "aio": 59, "aip": 34, "ayr": 33, "yr$": 34, "irb": 69, "uss": 667, "irc": 617, "tma": 385, "fts": 120, "tsm": 115, "sme": 472, "tsw": 68, "wom": 360, "two": 245, "ewm": 30, "wma": 84, "wme": 39, "ews": 191, "ird": 467, "opp": 662, "yre": 248, "irf": 24, "rfl": 172, "irg": 117, "irh": 30, "iry": 59, "irl": 270, "rla": 429, "fti": 143, "mai": 485, "irn": 83, "irp": 84, "ofe": 219, "ofs": 40, "tig": 685, "htl": 106, "htn": 49, "irw": 52, "ayb": 89, "ybi": 58, "wav": 91, "sew": 177, "isl": 342, "isn": 75, "sao": 11, "aou": 30, "eoi": 86, "itc": 438, "chb": 117, "hbo": 131, "chp": 88, "hpi": 28, "ayt": 49, "hya": 159, "itk": 18, "tke": 25, "tki": 40, "tak": 272, "kia": 152, "ayu": 44, "yu$": 20, "yud": 4, "udh": 22, "dhy": 12, "yuy": 2, "yun": 32, "yur": 61, "yut": 12, "tth": 67, "hay": 166, "aiv": 55, "ivr": 10, "vr$": 11, "aiw": 13, "iwa": 64, "wai": 255, "wan": 469, "ayw": 71, "ywh": 23, "whe": 404, "aix": 6, "izl": 3, "izo": 288, "^aj": 36, "aj$": 19, "jay": 54, "jaj": 10, "jar": 174, "jat": 23, "jav": 38, "jax": 2, "ajc": 2, "jc$": 5, "aje": 62, "jee": 48, "jen": 68, "enj": 71, "njo": 122, "ajh": 1, "jha": 3, "jim": 40, "mez": 49, "ez$": 91, "jit": 39, "jiv": 19, "vik": 31, "ajm": 2, "jme": 2, "jod": 20, "odh": 58, "jog": 25, "onj": 191, "jol": 107, "jow": 25, "aju": 36, "^ak": 154, "aak": 9, "kai": 117, "kab": 253, "kad": 97, "kal": 338, "imb": 583, "mba": 638, "kam": 154, "amn": 188, "mni": 406, "nik": 156, "nek": 31, "eku": 10, "kun": 76, "ska": 195, "kaw": 52, "kaz": 35, "azg": 3, "zga": 2, "zgi": 6, "akb": 11, "kba": 46, "akc": 7, "kc$": 2, "kch": 20, "heh": 17, "kea": 90, "keb": 65, "ked": 876, "keh": 42, "eho": 359, "kek": 12, "eki": 154, "kel": 441, "kem": 95, "nbo": 245, "kep": 80, "akh": 85, "kh$": 22, "khe": 99, "khy": 3, "khi": 41, "khl": 10, "khm": 8, "hmi": 159, "khn": 4, "hna": 75, "kho": 160, "khr": 3, "khu": 25, "ndz": 7, "dza": 7, "zad": 40, "khz": 5, "hzi": 5, "ziv": 4, "aky": 45, "kya": 55, "yab": 121, "iak": 50, "kib": 48, "kih": 11, "ihi": 91, "kiy": 13, "kil": 390, "kim": 116, "ovs": 27, "vsk": 51, "sky": 224, "ky$": 456, "ndl": 528, "isk": 275, "ske": 533, "akk": 33, "kka": 44, "kke": 52, "kkr": 1, "kra": 156, "akl": 50, "klo": 68, "akm": 18, "kmi": 10, "kmo": 33, "nsk": 144, "kmu": 9, "mud": 176, "udd": 347, "akn": 10, "ko$": 137, "koa": 19, "kou": 85, "kov": 60, "akp": 8, "kpe": 14, "pek": 29, "akr": 39, "kre": 90, "krt": 2, "aks": 119, "kse": 43, "kso": 62, "soy": 24, "oyn": 34, "ksu": 18, "akt": 49, "kti": 34, "ieb": 63, "lsc": 13, "kty": 10, "tyu": 7, "ku$": 53, "kua": 19, "mmi": 701, "kul": 102, "kur": 100, "akv": 5, "kva": 23, "vav": 15, "akw": 17, "kwa": 129, "wap": 50, "pim": 117, "^al": 3022, "cka": 287, "dfa": 72, "gez": 5, "goa": 100, "goz": 9, "lak": 190, "nuk": 27, "lun": 475, "mei": 119, "miq": 26, "anb": 68, "nbr": 225, "anr": 50, "nre": 1277, "pah": 73, "rmc": 11, "mcl": 20, "asd": 20, "sda": 103, "tei": 293, "rnu": 158, "zor": 87, "lb$": 21, "lba": 158, "bah": 91, "egn": 101, "bem": 150, "ghi": 135, "rty": 195, "rtl": 240, "rtt": 13, "rtv": 11, "tvi": 66, "spy": 38, "pyn": 7, "lby": 22, "bif": 161, "byn": 13, "eo$": 83, "biz": 54, "izz": 227, "lbm": 2, "lbn": 1, "bni": 17, "pru": 219, "lbr": 50, "tsv": 58, "onz": 73, "lbs": 5, "lbu": 197, "bug": 145, "nii": 63, "iiz": 1, "buq": 4, "uqu": 57, "erq": 54, "rqu": 169, "lc$": 25, "cai": 164, "alz": 26, "lza": 11, "zar": 294, "aza": 207, "cav": 280, "caz": 16, "zav": 10, "imy": 60, "ymy": 45, "cyo": 43, "lcl": 13, "lcm": 8, "mao": 20, "cog": 436, "coh": 245, "oha": 139, "olm": 114, "noq": 6, "oqu": 344, "umy": 24, "rcy": 57, "env": 176, "fly": 174, "efe": 523, "nli": 597, "ney": 457, "ldh": 30, "ldm": 32, "doh": 44, "hex": 337, "dov": 102, "ldr": 101, "ldu": 44, "dui": 46, "ldw": 50, "dwi": 147, "ebe": 575, "rry": 532, "ebu": 386, "ecs": 48, "ecu": 806, "lef": 209, "efn": 5, "fnu": 1, "efs": 38, "efz": 1, "yar": 306, "eyd": 42, "eik": 45, "eyr": 32, "eix": 9, "xan": 262, "lej": 25, "eja": 73, "joa": 25, "lek": 54, "ekh": 27, "ekn": 71, "kna": 107, "gik": 4, "kni": 157, "eks": 47, "ksa": 34, "yev": 22, "evs": 27, "ksi": 53, "tej": 9, "eoc": 307, "phz": 4, "hze": 6, "rtn": 81, "euc": 358, "euk": 80, "uka": 76, "kae": 16, "uke": 150, "lev": 452, "tsa": 98, "ewi": 415, "wif": 110, "wiv": 49, "upo": 79, "xei": 1, "xip": 65, "xiu": 4, "lez": 21, "eza": 59, "zan": 230, "alf": 197, "lfa": 141, "faj": 6, "je$": 35, "fak": 31, "fas": 337, "faq": 11, "feo": 47, "feu": 118, "lfh": 23, "fhe": 12, "eim": 237, "lfi": 205, "lfy": 6, "fio": 37, "irk": 159, "fon": 178, "orj": 15, "rja": 57, "jas": 86, "lfr": 48, "lfu": 162, "fur": 473, "lg$": 14, "lga": 197, "syf": 10, "yf$": 1, "arv": 294, "geb": 74, "gec": 48, "cir": 599, "gef": 69, "nib": 139, "lgh": 13, "gid": 197, "gif": 72, "giv": 123, "vor": 393, "lgo": 93, "goc": 90, "goe": 122, "gol": 385, "gny": 16, "onk": 177, "nki": 410, "onq": 68, "nqu": 508, "thm": 292, "gov": 100, "lgu": 50, "uaz": 7, "zil": 86, "gum": 207, "lya": 155, "lib": 569, "amu": 247, "mu$": 38, "ngb": 107, "ibe": 677, "bii": 31, "cev": 48, "enl": 306, "esk": 109, "anf": 104, "nfa": 426, "ifs": 26, "rh$": 15, "gnm": 34, "gns": 37, "liy": 12, "yas": 103, "iyo": 13, "iip": 5, "poe": 206, "kew": 84, "luf": 64, "uf$": 27, "ufa": 57, "lil": 256, "ngh": 183, "tao": 30, "ofa": 129, "lyo": 115, "osh": 251, "ptt": 10, "pum": 109, "liq": 216, "uot": 97, "ysa": 159, "smo": 718, "yso": 209, "isu": 265, "nk$": 348, "iun": 44, "lyw": 56, "ywo": 119, "lix": 70, "alj": 12, "lja": 10, "jam": 150, "ljo": 10, "job": 57, "jof": 3, "alk": 512, "lka": 197, "kap": 67, "kat": 310, "lke": 165, "kes": 366, "lkh": 23, "ovo": 165, "vo$": 77, "lky": 61, "kyd": 11, "yd$": 29, "lki": 167, "yds": 6, "kyl": 102, "kyn": 13, "lkm": 19, "lko": 36, "koo": 66, "kor": 143, "kox": 6, "muc": 221, "xai": 6, "rdt": 36, "sot": 300, "llb": 136, "llc": 34, "ecr": 634, "eyi": 93, "yit": 47, "eyn": 69, "eyw": 39, "ywa": 90, "luj": 8, "uja": 32, "elv": 199, "nby": 12, "orf": 140, "enh": 212, "npo": 270, "nsv": 130, "enw": 146, "nwo": 238, "pey": 52, "llg": 22, "llh": 75, "owm": 92, "wti": 18, "lyi": 137, "yic": 11, "tir": 433, "llm": 148, "lln": 61, "hez": 14, "oct": 610, "odg": 145, "eot": 307, "yag": 60, "oim": 49, "mmu": 414, "mun": 617, "lok": 64, "ypl": 33, "syc": 514, "loq": 189, "quy": 29, "hyh": 12, "yhm": 1, "syn": 890, "ynd": 144, "ypy": 42, "otm": 56, "spl": 583, "piz": 70, "oue": 65, "uez": 16, "wed": 390, "ypr": 112, "oxu": 14, "xur": 73, "loz": 54, "ooi": 91, "llp": 53, "llr": 36, "lre": 17, "lru": 17, "lsp": 27, "viu": 47, "llv": 26, "vet": 320, "llw": 92, "wei": 285, "lwh": 10, "llx": 1, "lx$": 9, "lm$": 101, "cig": 44, "igo": 497, "meh": 35, "ehs": 21, "iit": 25, "mig": 287, "hty": 44, "lmy": 22, "myr": 206, "lmn": 7, "mne": 204, "moc": 403, "reb": 874, "moh": 85, "oig": 57, "onr": 616, "vid": 427, "lms": 66, "msd": 9, "sde": 91, "msf": 12, "olk": 152, "msg": 12, "msh": 77, "msm": 21, "msw": 8, "uds": 101, "mue": 35, "erz": 67, "rzo": 22, "mug": 91, "ug$": 114, "ugs": 71, "mur": 372, "mut": 432, "ln$": 26, "lna": 34, "lni": 36, "niv": 249, "lno": 19, "lnu": 16, "nui": 84, "odd": 275, "oed": 222, "esw": 90, "oeu": 30, "oew": 8, "ewo": 418, "lof": 89, "oft": 170, "got": 388, "loh": 73, "yau": 35, "sii": 22, "gsh": 59, "gsi": 16, "soa": 160, "ofn": 10, "fne": 43, "oos": 450, "pii": 11, "lpa": 163, "aso": 462, "aug": 431, "pax": 36, "nho": 307, "ecc": 188, "atz": 50, "tz$": 193, "boe": 84, "pig": 324, "lps": 25, "lpu": 36, "puj": 7, "alq": 12, "lqu": 21, "alr": 71, "lra": 36, "lrz": 1, "rzc": 1, "zc$": 1, "lsb": 43, "lsi": 250, "sik": 47, "sip": 311, "lsm": 33, "soo": 147, "swi": 365, "tay": 103, "haw": 273, "zim": 51, "imu": 273, "ltd": 6, "tdo": 73, "tez": 22, "ezz": 65, "ltg": 5, "tge": 78, "ttr": 188, "nck": 45, "ltm": 34, "too": 390, "tsc": 118, "udr": 48, "mif": 208, "sil": 1055, "umn": 130, "mna": 163, "mnu": 33, "umr": 29, "mro": 61, "uno": 486, "lup": 119, "pag": 348, "alv": 436, "vad": 125, "vah": 39, "vea": 127, "veo": 69, "sub": 3115, "ubn": 51, "veu": 10, "lvy": 9, "ssm": 113, "lvo": 40, "dto": 23, "lvu": 42, "vus": 30, "lw$": 4, "lwi": 63, "lwy": 8, "wyn": 52, "lzh": 3, "zhe": 8, "^am": 1969, "maf": 35, "set": 626, "sak": 94, "may": 185, "igb": 19, "kos": 102, "fit": 275, "mau": 201, "nue": 98, "evo": 530, "ync": 273, "ceu": 48, "teu": 213, "atp": 21, "tps": 3, "axo": 152, "maz": 153, "zef": 13, "azu": 57, "zul": 32, "sad": 310, "edk": 7, "dka": 7, "erj": 159, "xte": 297, "guo": 40, "sex": 312, "exu": 221, "xua": 88, "syl": 326, "biv": 59, "rpu": 168, "lyg": 140, "ygo": 285, "yoc": 92, "yop": 201, "lyr": 101, "yrh": 24, "boy": 233, "xou": 2, "mbs": 70, "gey": 60, "hli": 266, "amc": 18, "mc$": 24, "mch": 29, "tka": 25, "amd": 24, "mda": 28, "mdg": 3, "dg$": 9, "mdt": 5, "meb": 64, "bae": 87, "eio": 119, "eiu": 9, "eiv": 155, "eiz": 82, "zoe": 29, "oei": 78, "elc": 140, "ndm": 112, "imn": 113, "rsf": 10, "vei": 122, "esl": 110, "sla": 700, "esv": 91, "oec": 209, "mex": 24, "amf": 46, "mfo": 74, "amg": 8, "mga": 11, "mha": 18, "mhe": 16, "std": 46, "tda": 35, "mhr": 2, "mya": 59, "nuc": 297, "doa": 139, "oaz": 16, "zob": 24, "apr": 277, "dof": 58, "mye": 176, "suc": 347, "ulp": 631, "iaz": 136, "idr": 140, "idw": 44, "myg": 37, "ygd": 34, "gda": 57, "gdu": 5, "mii": 17, "myl": 128, "ilc": 70, "oag": 111, "ysp": 81, "pep": 231, "psa": 171, "mym": 5, "raq": 47, "nza": 133, "zal": 83, "zam": 65, "nzi": 90, "myn": 18, "nok": 32, "pio": 224, "nsf": 160, "sfe": 74, "nox": 107, "anh": 210, "nha": 464, "shg": 21, "hgo": 24, "ssv": 11, "bha": 83, "myt": 154, "hao": 39, "tyv": 5, "pty": 80, "xor": 92, "aml": 106, "mla": 43, "mli": 127, "mlo": 28, "mls": 2, "meo": 122, "iem": 78, "ioj": 5, "oja": 33, "lyz": 153, "yze": 88, "tyi": 50, "ioa": 75, "moa": 71, "oak": 129, "kuh": 9, "byr": 82, "moe": 163, "oeb": 94, "moy": 42, "oib": 12, "mom": 282, "mum": 137, "hoz": 31, "seu": 925, "mow": 39, "wt$": 28, "pny": 2, "ecl": 457, "oky": 49, "tyo": 47, "rul": 369, "hik": 100, "iox": 53, "xus": 26, "ipn": 43, "ipr": 236, "rhi": 403, "isb": 240, "ruo": 26, "uo$": 13, "hiv": 142, "hiz": 385, "sko": 83, "koi": 44, "mpy": 90, "mpi": 496, "pyx": 15, "yxe": 21, "xif": 31, "ply": 128, "mpu": 294, "put": 465, "amr": 47, "mr$": 16, "mra": 40, "raa": 50, "mre": 19, "mri": 27, "oc$": 116, "msc": 48, "sw$": 15, "mtm": 3, "mto": 16, "mtr": 17, "chc": 64, "hco": 39, "uck": 815, "ugu": 148, "muy": 5, "uyo": 13, "rru": 318, "usg": 10, "sgo": 29, "muz": 42, "uze": 57, "amv": 8, "mve": 20, "mvi": 53, "mvr": 1, "vra": 33, "kik": 27, "amz": 8, "mze": 7, "^an": 5569, "hru": 150, "pse": 1041, "ohi": 133, "tsi": 147, "yps": 147, "dym": 89, "ymu": 40, "rko": 47, "dyo": 19, "dyr": 5, "gep": 34, "gig": 143, "giz": 195, "yt$": 18, "tiq": 71, "ogs": 125, "moo": 414, "apl": 325, "tyc": 90, "tyx": 8, "naq": 14, "soc": 680, "saz": 18, "zis": 18, "pad": 257, "tex": 140, "fae": 31, "cob": 258, "rur": 162, "naw": 101, "xag": 75, "xar": 40, "xib": 42, "xo$": 13, "xon": 162, "naz": 85, "zot": 109, "nba": 230, "orv": 82, "orw": 131, "hov": 116, "ovy": 11, "ncl": 529, "rao": 101, "aop": 11, "daq": 5, "gg$": 41, "onv": 861, "rss": 19, "ndf": 100, "ndh": 92, "dhr": 7, "izh": 3, "oui": 69, "evk": 6, "vka": 14, "rej": 171, "ej$": 5, "wsi": 41, "ryc": 91, "rij": 39, "clc": 3, "ioe": 88, "yny": 22, "ynu": 26, "maq": 25, "ogg": 381, "inx": 54, "nx$": 53, "nxe": 22, "tau": 304, "drs": 6, "ndv": 18, "ecd": 39, "cdy": 9, "cdo": 37, "ytr": 94, "ryt": 233, "eup": 218, "zeh": 7, "nf$": 10, "eel": 594, "nfr": 437, "nfu": 380, "gak": 15, "eb$": 70, "rsk": 68, "eyo": 43, "yok": 67, "gek": 11, "ekk": 37, "kko": 20, "eko": 31, "elf": 210, "lho": 95, "icn": 102, "inf": 1077, "nfo": 602, "gev": 25, "ngy": 94, "iob": 112, "rpy": 14, "ecy": 116, "iok": 15, "oie": 84, "sym": 459, "thl": 249, "ngk": 45, "gka": 16, "gkh": 1, "gko": 13, "inl": 208, "etw": 114, "twi": 429, "grb": 2, "uim": 30, "shf": 56, "hfu": 175, "ibb": 355, "nuo": 74, "ngw": 91, "gwi": 42, "wic": 169, "nh$": 17, "nhe": 395, "edr": 513, "nhy": 107, "nhi": 129, "nhw": 4, "hwe": 76, "nya": 116, "kud": 16, "udo": 913, "nyb": 22, "ybo": 89, "dyd": 12, "nyh": 12, "yho": 86, "how": 282, "nyo": 37, "nyp": 9, "oyl": 82, "sok": 36, "nys": 47, "nyt": 13, "niw": 9, "nyw": 31, "iwe": 27, "wet": 90, "why": 13, "ywi": 46, "anj": 84, "nja": 86, "jal": 45, "nje": 100, "jel": 72, "nkh": 45, "khs": 10, "nky": 99, "kyr": 34, "nkl": 230, "kli": 331, "klu": 38, "nkn": 70, "nko": 55, "nku": 28, "kus": 55, "nla": 262, "nmi": 194, "nmo": 303, "xe$": 36, "xiv": 42, "exm": 9, "nnf": 5, "nfw": 2, "fwn": 1, "nny": 247, "nih": 61, "nnm": 4, "nnn": 7, "nno": 520, "oyf": 18, "yfu": 54, "oym": 35, "oyo": 60, "nnu": 235, "nua": 202, "ulm": 168, "unz": 26, "nnv": 7, "nnw": 8, "nwf": 1, "wfn": 1, "fn$": 4, "nwn": 1, "ccy": 41, "cyg": 40, "oeg": 13, "oia": 50, "ryx": 19, "plu": 564, "pub": 191, "rex": 243, "xig": 35, "ilh": 62, "lh$": 3, "ovu": 44, "xae": 11, "xyb": 23, "xys": 40, "ysc": 52, "anp": 43, "npa": 403, "anq": 101, "auu": 4, "uun": 6, "nsg": 25, "sga": 36, "riv": 677, "hyc": 117, "ntb": 16, "tbi": 46, "ntd": 14, "teb": 149, "mys": 171, "cox": 76, "tef": 147, "efi": 681, "efl": 372, "ehi": 89, "eje": 109, "eju": 148, "nav": 248, "nup": 62, "tep": 308, "epe": 707, "teq": 11, "ioy": 2, "oex": 65, "ofr": 55, "pyg": 77, "yga": 104, "esf": 19, "unr": 1219, "thd": 36, "hdi": 21, "emw": 9, "mwi": 22, "myi": 55, "yia": 24, "yii": 6, "rmu": 203, "hox": 55, "xen": 202, "diq": 4, "roq": 53, "urp": 274, "ufi": 35, "xol": 45, "pob": 48, "eox": 43, "xyc": 39, "tht": 29, "nzy": 61, "zym": 118, "anx": 30, "nxi": 31, "iaq": 5, "iau": 53, "cch": 232, "cki": 656, "oyc": 24, "kag": 107, "ubo": 215, "auc": 332, "ucr": 109, "cyn": 147, "cln": 3, "kwi": 52, "onf": 1026, "nvu": 54, "uff": 764, "def": 771, "ysu": 41, "ogm": 79, "duk": 41, "eav": 348, "esd": 46, "sdr": 28, "iey": 9, "iej": 4, "iep": 45, "thn": 148, "eug": 94, "uge": 230, "iex": 33, "exp": 975, "xpa": 104, "xpo": 143, "xpr": 121, "fem": 157, "eud": 991, "fog": 91, "fow": 63, "ifr": 50, "ifu": 178, "gay": 73, "igy": 31, "yox": 23, "ugg": 406, "tih": 83, "ihe": 102, "ihy": 81, "hyg": 153, "hij": 15, "iho": 61, "huf": 59, "nfl": 480, "tij": 11, "tik": 91, "iky": 8, "kyt": 16, "ikn": 5, "tyr": 272, "asq": 71, "mep": 45, "ceg": 26, "rnl": 68, "rnn": 23, "osq": 38, "oxl": 16, "xly": 18, "xyg": 63, "ioz": 18, "pap": 386, "pew": 86, "pyi": 35, "yry": 20, "poo": 310, "pov": 39, "stc": 110, "tcr": 56, "pud": 112, "quu": 10, "uum": 36, "ttl": 503, "obb": 352, "sav": 235, "ofu": 88, "seg": 153, "suo": 39, "ilv": 152, "ymm": 107, "syp": 86, "ryi": 148, "sna": 337, "sno": 306, "ob$": 96, "dix": 42, "isq": 78, "ubm": 197, "bma": 68, "ubs": 578, "ubv": 90, "bve": 77, "sud": 100, "suf": 194, "sui": 191, "hno": 227, "egu": 324, "ypa": 125, "rnp": 24, "pik": 105, "kei": 58, "itw": 42, "iua": 2, "iul": 17, "usu": 127, "iut": 27, "vib": 105, "tiw": 25, "wea": 452, "iwh": 4, "iwi": 52, "wir": 154, "iwo": 23, "tix": 13, "zea": 69, "izy": 18, "ntj": 10, "tja": 24, "tlo": 71, "tlu": 13, "tnt": 6, "tod": 360, "tof": 128, "fag": 75, "ntp": 17, "tym": 132, "shr": 342, "tsy": 35, "ntt": 14, "ntw": 104, "twe": 190, "nub": 103, "unn": 607, "usv": 4, "sva": 50, "nut": 341, "nva": 277, "anz": 129, "zac": 40, "rub": 382, "zio": 34, "zov": 9, "nzu": 5, "zus": 7, "^ao": 68, "aoa": 3, "aob": 8, "aoc": 18, "ocs": 25, "aoe": 6, "aog": 2, "aoi": 21, "oif": 23, "aok": 2, "aol": 47, "aom": 8, "aoq": 1, "oq$": 7, "aor": 89, "aot": 27, "aow": 6, "^ap": 1614, "pab": 186, "bhr": 1, "yao": 10, "pam": 105, "dya": 52, "jos": 78, "rtm": 110, "stt": 35, "apb": 31, "pb$": 9, "apc": 9, "apd": 22, "pda": 33, "pdu": 7, "peh": 24, "apf": 20, "pfe": 21, "apg": 2, "pg$": 15, "pga": 18, "kic": 67, "ozy": 68, "zyg": 184, "zog": 45, "phn": 26, "ckp": 91, "kpa": 21, "ezo": 55, "tpa": 64, "piv": 26, "apj": 8, "pj$": 1, "pjo": 2, "joh": 71, "ohn": 72, "pl$": 23, "ppu": 97, "apm": 32, "pm$": 16, "poa": 44, "oap": 104, "pst": 180, "rps": 75, "cr$": 22, "pna": 8, "poh": 29, "oik": 26, "poj": 1, "ojo": 39, "jov": 39, "pok": 126, "okr": 23, "kio": 46, "yiu": 1, "npt": 2, "hye": 49, "yee": 26, "heg": 96, "opk": 25, "pka": 6, "poq": 4, "yol": 121, "saf": 153, "gms": 4, "thg": 14, "hgm": 1, "urm": 174, "pox": 48, "xyo": 6, "iks": 68, "ppc": 3, "ppd": 3, "pd$": 16, "ntg": 61, "ppl": 423, "eby": 15, "hn$": 43, "esn": 62, "sni": 200, "ppm": 4, "pmt": 2, "ppp": 8, "ppr": 471, "ceh": 19, "inq": 218, "pps": 24, "ppt": 5, "ptd": 2, "td$": 25, "pui": 29, "pr$": 23, "pth": 34, "apx": 1, "px$": 4, "^aq": 148, "aq$": 6, "aqa": 4, "qab": 3, "aql": 3, "ql$": 5, "uaf": 13, "uag": 86, "uak": 63, "uap": 23, "iia": 24, "iiu": 8, "ueb": 82, "ueo": 18, "ueu": 37, "fug": 169, "uiv": 95, "uoc": 9, "uon": 37, "^ar": 2993, "yeh": 23, "ju$": 14, "sys": 150, "yak": 67, "aua": 18, "auj": 7, "uji": 28, "jia": 10, "wak": 146, "rby": 38, "yrd": 9, "trn": 3, "rcb": 2, "cbo": 4, "rcc": 4, "rcf": 2, "cfo": 2, "eoh": 26, "eoz": 28, "hba": 65, "ncs": 18, "hbe": 51, "hbi": 22, "pry": 47, "hbp": 1, "hbu": 42, "buf": 86, "hca": 19, "hci": 2, "coz": 47, "hcr": 20, "hcu": 12, "upb": 87, "pbe": 38, "chd": 62, "hd$": 14, "hda": 32, "pif": 96, "hdo": 32, "hdr": 38, "hdu": 9, "dux": 6, "heb": 163, "sps": 28, "psp": 27, "eun": 118, "unu": 151, "chf": 77, "hfe": 6, "hfi": 39, "hfl": 15, "hfo": 22, "foe": 42, "hfr": 3, "chg": 26, "hge": 6, "hgu": 5, "chh": 40, "hhe": 40, "hhy": 3, "hho": 52, "hhu": 2, "kod": 34, "hih": 18, "enk": 91, "sby": 78, "byt": 59, "ymb": 171, "vau": 87, "chj": 1, "hjo": 8, "joc": 83, "hki": 24, "hkn": 2, "hmy": 6, "hmu": 23, "ugw": 18, "gwu": 10, "wum": 13, "hpa": 31, "hph": 7, "ilf": 73, "hpl": 18, "hpo": 48, "hpr": 36, "hpu": 10, "hsc": 8, "hse": 26, "hsh": 57, "hsi": 43, "hsy": 2, "hsn": 1, "hsp": 10, "hst": 58, "hsw": 1, "htr": 31, "htu": 16, "rnc": 23, "chv": 18, "vam": 63, "rym": 170, "hvi": 40, "chw": 167, "wag": 229, "wen": 179, "hwi": 55, "hwo": 126, "rkm": 47, "rck": 19, "rcm": 5, "rcn": 4, "rcs": 19, "csi": 24, "icw": 8, "ebs": 26, "nvo": 265, "rdh": 44, "rdy": 95, "dyc": 21, "isj": 59, "sj$": 6, "rdm": 68, "dsl": 29, "duo": 135, "eaw": 69, "wid": 127, "cun": 257, "efy": 42, "eg$": 107, "dts": 8, "ig$": 169, "nzv": 1, "zvi": 6, "req": 178, "sku": 73, "arf": 206, "rfs": 25, "rfv": 1, "fve": 1, "toj": 12, "gyl": 38, "ynn": 81, "guf": 20, "ufy": 3, "usf": 12, "sfi": 112, "gut": 156, "rhn": 2, "yam": 92, "nrh": 26, "ryb": 54, "yba": 109, "ybb": 7, "bof": 28, "yep": 16, "iov": 60, "sbe": 162, "oep": 76, "arj": 21, "rju": 61, "rka": 109, "utl": 211, "saw": 166, "awy": 38, "wye": 38, "rkd": 10, "kda": 29, "rkh": 39, "rkp": 17, "kpo": 48, "sut": 95, "rkv": 9, "kvi": 43, "rkw": 26, "kwr": 7, "rlb": 15, "leq": 13, "rlu": 60, "edd": 279, "mav": 34, "rmb": 15, "mco": 20, "rmf": 24, "rmg": 4, "mgu": 7, "rmh": 18, "lld": 66, "mip": 279, "myw": 6, "hiq": 11, "uoi": 34, "moz": 47, "zee": 43, "ozi": 99, "rmp": 11, "rmr": 8, "scy": 89, "udv": 4, "rnb": 55, "nbe": 394, "rnd": 20, "neb": 241, "rnh": 33, "rny": 45, "dsv": 40, "off": 643, "peg": 101, "arq": 82, "rq$": 4, "oux": 20, "rgt": 4, "zou": 26, "isw": 59, "rrl": 5, "oyu": 12, "yue": 2, "jad": 42, "owb": 152, "wbu": 21, "owh": 81, "owy": 43, "wy$": 67, "wli": 155, "owp": 68, "wpl": 12, "wro": 113, "wsm": 22, "wst": 56, "oww": 54, "wwe": 11, "wwo": 56, "rrt": 4, "rsb": 35, "sb$": 22, "seh": 83, "enf": 250, "syv": 2, "yve": 22, "rsl": 89, "rsm": 62, "rsn": 23, "rtc": 28, "tcc": 4, "rtf": 54, "rtg": 37, "tgu": 35, "pye": 57, "yem": 47, "fex": 11, "peo": 97, "syb": 39, "hev": 142, "tuk": 27, "uko": 65, "rtw": 69, "rtz": 60, "tzy": 14, "zyb": 9, "ruk": 33, "uml": 71, "usp": 216, "rut": 389, "ruw": 8, "uwi": 6, "wim": 65, "vej": 6, "rvy": 19, "von": 121, "arx": 9, "arz": 36, "rza": 42, "zaw": 6, "rzr": 1, "zru": 1, "rzu": 4, "zun": 13, "^as": 1741, "sap": 347, "asb": 54, "sbj": 2, "jor": 110, "sbo": 165, "scc": 5, "ceb": 77, "ebc": 6, "cyp": 130, "yru": 45, "itb": 32, "tb$": 12, "iei": 23, "scq": 1, "cq$": 5, "utn": 43, "sdi": 59, "sds": 9, "asf": 10, "sg$": 10, "hr$": 37, "sgd": 2, "sge": 33, "sgm": 6, "gmt": 2, "shb": 87, "hby": 4, "shc": 30, "cak": 93, "shd": 13, "hfa": 23, "shy": 104, "ipb": 29, "pbo": 57, "shj": 2, "hji": 3, "shk": 49, "hke": 29, "zic": 28, "hkh": 2, "hko": 16, "hku": 3, "shp": 47, "shs": 15, "hta": 70, "hto": 62, "shu": 261, "shv": 18, "edh": 36, "shw": 83, "ymt": 7, "ynj": 1, "nju": 236, "syu": 13, "syz": 10, "yzy": 11, "ewg": 13, "wge": 5, "ewn": 26, "wne": 189, "kip": 82, "skj": 9, "kja": 17, "koy": 9, "skr": 30, "kr$": 5, "skw": 10, "asl": 56, "slo": 350, "slu": 239, "asn": 31, "sn$": 28, "rkl": 109, "spc": 9, "pca": 37, "rgg": 4, "yxy": 1, "piq": 29, "uee": 107, "asr": 12, "sra": 66, "srm": 4, "srs": 4, "ssb": 78, "baa": 36, "seo": 93, "sev": 180, "ssh": 91, "stf": 160, "nns": 34, "ilz": 10, "lzi": 17, "zie": 190, "sss": 13, "sst": 74, "ubj": 106, "uef": 64, "umm": 508, "ssw": 77, "kiw": 17, "tyd": 16, "spu": 287, "tm$": 13, "tms": 5, "ryu": 17, "yum": 23, "vig": 226, "urf": 178, "ruc": 646, "tuc": 115, "sv$": 11, "asw": 29, "wev": 8, "wou": 67, "asz": 9, "sz$": 20, "^at": 967, "bek": 56, "tah": 160, "aiy": 14, "urk": 138, "vi$": 67, "avu": 45, "xap": 31, "zir": 51, "atb": 64, "tc$": 27, "tcl": 47, "tco": 87, "tdr": 47, "atf": 83, "tf$": 14, "tfa": 81, "atg": 9, "tgl": 21, "thb": 71, "thw": 169, "ymn": 160, "tys": 64, "atk": 24, "tk$": 5, "oax": 44, "tls": 9, "xoi": 13, "atn": 57, "tn$": 25, "tp$": 26, "tpc": 2, "pco": 44, "tpo": 78, "hyi": 40, "trs": 8, "sug": 150, "ulg": 252, "ttc": 5, "nwe": 165, "ttn": 16, "trp": 7, "opm": 66, "ttw": 10, "atv": 14, "tv$": 18, "tve": 22, "vee": 55, "atw": 66, "wek": 7, "kk$": 5, "wix": 8, "wo$": 12, "^au": 1493, "uby": 15, "ubr": 274, "rnt": 49, "uc$": 34, "ucu": 151, "dhu": 38, "udl": 64, "dub": 100, "udw": 26, "auf": 67, "ufg": 1, "fga": 5, "ufk": 4, "fkl": 2, "klr": 2, "ufm": 3, "fma": 38, "uft": 38, "ugy": 4, "ugm": 45, "ugr": 25, "gsb": 18, "auh": 13, "uhu": 8, "huh": 8, "aui": 21, "auk": 72, "ukl": 10, "uks": 33, "omn": 251, "ldf": 51, "mmb": 2, "unj": 123, "itz": 195, "pak": 43, "ngz": 2, "gze": 2, "zeb": 58, "rir": 70, "url": 311, "oau": 54, "sgl": 7, "rky": 35, "usy": 50, "oab": 42, "hns": 27, "exc": 593, "xci": 115, "vur": 23, "toh": 92, "hyb": 57, "ybr": 74, "oji": 20, "igg": 430, "oju": 19, "mpn": 21, "oox": 7, "opn": 65, "hoa": 206, "syi": 25, "osl": 63, "ryv": 28, "mn$": 47, "mns": 12, "auv": 44, "uve": 165, "rgn": 10, "uwe": 10, "uxa": 23, "xam": 112, "xet": 11, "uxf": 2, "xf$": 1, "uxo": 50, "xoa": 10, "xob": 18, "xoc": 54, "xof": 7, "xoh": 1, "xot": 71, "uxv": 1, "xva": 1, "auz": 17, "uzo": 20, "^av": 422, "nyu": 7, "tga": 75, "vaw": 6, "avc": 5, "vc$": 9, "avd": 11, "vd$": 8, "vdp": 1, "uz$": 23, "avg": 6, "vg$": 3, "vga": 5, "vya": 10, "yay": 6, "yib": 3, "ibh": 8, "vif": 60, "igd": 11, "vij": 5, "ijj": 2, "jja": 3, "vys": 5, "tzu": 7, "zur": 43, "avl": 18, "vli": 12, "vlo": 10, "avm": 1, "vm$": 6, "avn": 8, "vn$": 9, "vne": 3, "vod": 27, "dup": 146, "ndb": 121, "dbl": 25, "wte": 29, "avr": 30, "vri": 28, "vro": 43, "vru": 1, "vsh": 1, "vun": 4, "^aw": 165, "wac": 59, "wad": 127, "waf": 51, "enm": 98, "puh": 2, "uhi": 13, "awb": 74, "wb$": 5, "wbe": 66, "awd": 57, "wd$": 31, "web": 64, "awf": 59, "wfu": 39, "fu$": 21, "awg": 12, "wg$": 6, "awh": 27, "wig": 147, "wik": 15, "wiw": 5, "awk": 166, "wk$": 49, "wkl": 3, "kly": 107, "wkw": 13, "wls": 50, "wlw": 5, "awm": 38, "wm$": 18, "wmb": 2, "wmo": 22, "wny": 23, "wni": 107, "wnl": 53, "wns": 151, "wok": 17, "owo": 38, "awr": 34, "wre": 156, "wsh": 59, "awu": 4, "wu$": 11, "wun": 25, "^ax": 154, "xaf": 3, "axb": 13, "xbr": 4, "xeb": 3, "xel": 15, "axf": 3, "xfe": 7, "axh": 7, "xha": 86, "xhe": 12, "axl": 21, "xle": 35, "xli": 17, "axm": 14, "xma": 23, "xmi": 3, "xod": 52, "axs": 6, "xse": 22, "xso": 8, "xst": 8, "axt": 27, "xto": 85, "axu": 7, "xum": 8, "xun": 7, "axw": 26, "xwe": 11, "xwi": 5, "xwo": 16, "^az": 232, "zaf": 15, "eah": 38, "zas": 42, "zaz": 16, "azb": 4, "zbi": 2, "zeg": 7, "zei": 27, "faf": 10, "zeo": 14, "idz": 7, "aij": 7, "azh": 2, "azy": 52, "gob": 118, "zik": 9, "inp": 77, "nph": 87, "ziz": 22, "azl": 23, "zla": 6, "zlo": 10, "azn": 5, "zna": 7, "zoc": 43, "zod": 23, "zof": 19, "ofy": 13, "zoh": 15, "zom": 54, "zop": 94, "zox": 18, "xaz": 20, "xya": 24, "ybe": 97, "xyn": 11, "xyt": 29, "azp": 4, "zpu": 1, "azr": 3, "zri": 4, "azt": 6, "zte": 6, "zth": 3, "zue": 8, "zum": 18, "^ba": 4522, "baj": 26, "kot": 74, "beh": 155, "euf": 9, "byf": 2, "yfi": 54, "byh": 13, "byo": 15, "oh$": 40, "nro": 216, "ouv": 72, "cao": 26, "cba": 9, "iuc": 6, "lll": 10, "lry": 40, "kac": 25, "kbe": 42, "kbl": 7, "kbo": 76, "kbr": 24, "ckc": 40, "kca": 16, "kcl": 8, "kco": 9, "kcr": 11, "ckd": 32, "kdo": 29, "kdr": 9, "ckf": 106, "kfa": 25, "kfi": 64, "kfl": 12, "kfo": 26, "kfr": 9, "kfu": 69, "ckg": 17, "kga": 4, "kge": 3, "kgr": 8, "ckh": 124, "cky": 136, "ckj": 16, "jaw": 62, "kjo": 8, "kof": 35, "kpi": 28, "kpl": 16, "ckr": 72, "kru": 56, "ksc": 26, "ksh": 124, "ksl": 26, "ksp": 38, "kst": 157, "ksw": 16, "wep": 21, "kta": 55, "kte": 20, "ktr": 17, "cku": 36, "kup": 52, "ups": 295, "ckv": 22, "kve": 8, "dsy": 6, "onw": 131, "cso": 13, "yi$": 17, "daj": 3, "joz": 4, "dak": 54, "dch": 31, "rwe": 165, "duh": 5, "uhe": 22, "dek": 55, "aey": 5, "etz": 66, "tzn": 3, "zne": 6, "aez": 3, "ffs": 172, "taw": 113, "geh": 44, "agf": 11, "gfu": 41, "gyw": 3, "ywr": 23, "ggo": 52, "ggs": 34, "ghd": 15, "gpi": 19, "gpl": 9, "gpo": 13, "gpu": 11, "uiz": 63, "eef": 93, "tsk": 64, "gsc": 8, "gsf": 5, "gti": 32, "uio": 31, "gwe": 56, "igs": 89, "gwy": 13, "gwo": 80, "wde": 74, "hnu": 19, "huv": 8, "uvr": 35, "rih": 62, "yad": 50, "ybu": 56, "ayf": 64, "ayg": 26, "yhe": 71, "yld": 12, "eyt": 23, "eyv": 48, "fry": 27, "ffw": 11, "liw": 29, "ilp": 62, "yog": 96, "ayp": 25, "rdf": 35, "dfo": 56, "rnw": 42, "itf": 51, "tfi": 129, "ayv": 16, "ayz": 6, "yz$": 7, "jau": 44, "jer": 167, "ajr": 6, "jra": 11, "jre": 1, "jri": 1, "jul": 65, "eie": 43, "keo": 34, "eov": 32, "kht": 10, "kki": 20, "ktu": 9, "kub": 17, "laa": 23, "laf": 62, "yeu": 18, "cew": 63, "bue": 28, "lck": 6, "ldb": 45, "dbe": 70, "ldc": 16, "ldl": 71, "doq": 3, "ldp": 13, "cwi": 5, "dwy": 9, "lij": 12, "ikp": 1, "lkl": 27, "lks": 105, "llf": 90, "gow": 63, "wks": 21, "lyh": 54, "yha": 38, "ooe": 71, "umf": 59, "nfi": 588, "lpl": 34, "lpr": 48, "lml": 11, "ogh": 61, "lr$": 17, "amw": 32, "lug": 158, "ooz": 89, "ozl": 26, "zli": 55, "dbo": 94, "ndc": 39, "dca": 75, "dcu": 25, "ndg": 38, "dh$": 22, "dyb": 27, "dyi": 95, "ndj": 14, "ndk": 9, "dke": 16, "ndp": 66, "dsa": 29, "ndw": 126, "idt": 21, "dth": 58, "rje": 48, "jea": 54, "nff": 4, "ghy": 17, "gta": 34, "gup": 12, "weu": 2, "yai": 10, "niy": 10, "yul": 20, "joe": 29, "juk": 13, "nkb": 27, "nkc": 7, "nkf": 43, "nkm": 26, "nkr": 39, "kri": 107, "upc": 71, "pcy": 1, "ptc": 7, "nks": 190, "ksm": 49, "nkw": 30, "kwe": 54, "eue": 28, "nlo": 182, "kbu": 29, "nuy": 7, "anw": 59, "nxr": 1, "xri": 1, "bao": 11, "pct": 5, "raj": 81, "jil": 40, "beq": 29, "tui": 139, "oza": 49, "rbw": 4, "bwi": 7, "rdc": 27, "rek": 117, "knu": 54, "rff": 16, "rfy": 6, "nwi": 198, "yec": 17, "ygl": 43, "ryr": 8, "rkb": 23, "rkc": 5, "kcu": 5, "kev": 32, "inj": 106, "nji": 27, "ksd": 8, "fum": 142, "ucs": 10, "eyb": 40, "eyc": 18, "eyh": 15, "eym": 55, "mfe": 27, "myb": 2, "rmk": 1, "mki": 18, "nev": 235, "ldt": 24, "rnf": 52, "dti": 28, "nsd": 34, "osw": 29, "ygr": 90, "bki": 13, "mej": 5, "lsf": 4, "elw": 83, "rrn": 6, "owc": 43, "wco": 17, "owf": 64, "meu": 37, "mew": 88, "ewt": 34, "rtk": 2, "tko": 6, "ukh": 22, "hzy": 1, "zy$": 104, "ltw": 21, "jis": 19, "wdo": 28, "lyk": 14, "yk$": 12, "yks": 2, "huk": 37, "sih": 13, "etf": 66, "sov": 92, "taa": 15, "tbo": 119, "tfo": 103, "thf": 118, "thh": 27, "yhy": 13, "thk": 7, "hof": 63, "ub$": 94, "ubf": 97, "bfu": 31, "thv": 17, "efr": 273, "tuq": 5, "tze": 118, "ufr": 19, "ghm": 30, "uha": 42, "ulk": 117, "umh": 12, "utz": 57, "tje": 15, "baw": 55, "wbl": 3, "awc": 19, "wdy": 25, "wdi": 38, "dyh": 12, "wdr": 27, "wds": 8, "who": 174, "wke": 40, "wly": 43, "wsu": 7, "awt": 39, "wty": 3, "zaa": 7, "zig": 43, "zlu": 2, "^bb": 9, "bbb": 1, "bbc": 1, "bbn": 2, "bbs": 21, "bbx": 1, "bxr": 1, "xrt": 1, "^bc": 22, "bcb": 1, "cbs": 3, "bcc": 1, "bcd": 5, "cdi": 7, "bcf": 1, "bch": 41, "bcl": 43, "bcm": 1, "bcp": 2, "cpl": 2, "bcr": 27, "bcw": 2, "cws": 2, "^bd": 30, "bdc": 1, "bdd": 1, "bdf": 2, "dft": 2, "bdl": 3, "dl$": 21, "dls": 4, "bdr": 7, "drm": 2, "bds": 3, "bdt": 2, "^be": 4314, "dsw": 29, "akf": 22, "lsv": 25, "mfi": 45, "hug": 110, "paw": 94, "stb": 78, "tni": 44, "ufo": 33, "lmm": 8, "mey": 51, "obw": 18, "bwe": 17, "cow": 227, "ecq": 5, "owd": 179, "cuf": 46, "uib": 47, "edb": 75, "dbu": 51, "edc": 28, "dcl": 24, "edf": 59, "dgo": 11, "dye": 55, "edm": 69, "dni": 30, "doy": 22, "edp": 18, "dpl": 23, "edq": 2, "dqu": 13, "edt": 26, "edw": 67, "efb": 2, "fbu": 2, "efc": 5, "efh": 2, "fst": 14, "efw": 3, "fwo": 12, "eeg": 36, "eeh": 38, "eey": 11, "ekm": 3, "elz": 20, "lze": 23, "wax": 78, "ewk": 4, "tfl": 52, "eev": 147, "eew": 57, "fav": 126, "fez": 11, "zze": 90, "eai": 16, "fop": 20, "fud": 33, "egs": 50, "obs": 400, "owk": 31, "utc": 276, "lfs": 32, "ehk": 2, "ehl": 22, "hlk": 2, "ehm": 32, "ehn": 23, "hnk": 4, "oov": 56, "vef": 25, "ehr": 49, "hrm": 11, "usb": 45, "eij": 9, "iji": 15, "jin": 100, "ndn": 60, "bej": 35, "jab": 52, "jap": 80, "jaz": 32, "jes": 115, "jew": 63, "jez": 15, "eji": 13, "juc": 9, "elj": 10, "neh": 47, "mca": 34, "duq": 5, "giq": 1, "elk": 80, "lkn": 6, "vue": 10, "lyb": 102, "lyf": 28, "yfl": 31, "inz": 39, "wsf": 2, "wsl": 12, "owg": 27, "wgr": 12, "ltc": 13, "lzo": 6, "maj": 81, "bex": 19, "muf": 44, "ikt": 8, "ghb": 78, "zet": 63, "gnl": 17, "gnn": 7, "nj$": 8, "njy": 1, "jy$": 4, "jie": 4, "nld": 2, "tgr": 44, "mbm": 13, "bme": 79, "nz$": 28, "nzb": 7, "nzd": 5, "zdi": 6, "oxd": 5, "xdi": 10, "oxt": 23, "eaz": 41, "nzh": 2, "zhy": 3, "zyl": 19, "ulv": 127, "zoy": 11, "zoq": 7, "fim": 31, "nzp": 2, "zpi": 2, "zpy": 1, "nzt": 2, "ztr": 2, "eow": 26, "waw": 28, "owu": 9, "wul": 15, "riw": 19, "pow": 151, "puf": 38, "puz": 34, "eqa": 1, "qaa": 1, "eqw": 2, "qwe": 2, "esg": 13, "yae": 26, "ezn": 3, "zni": 6, "rgd": 1, "rgf": 2, "gfa": 9, "olz": 12, "lz$": 11, "rgm": 5, "rgq": 1, "gqu": 1, "rgs": 27, "gsm": 28, "gso": 24, "rgw": 5, "rhl": 1, "ovt": 4, "vts": 4, "kow": 47, "rnj": 1, "rnk": 7, "rnv": 8, "ysb": 21, "rwy": 7, "rze": 54, "eml": 52, "iek": 30, "snu": 103, "nuf": 83, "esr": 15, "tuu": 8, "uur": 3, "eln": 31, "thp": 33, "etj": 4, "jem": 33, "toy": 68, "ezu": 12, "tye": 28, "ttz": 2, "nwh": 49, "evy": 22, "vom": 66, "evv": 10, "vvy": 18, "bew": 135, "wie": 120, "wiz": 33, "ewp": 15, "wpe": 15, "ewr": 74, "eww": 9, "exl": 22, "bez": 55, "zae": 18, "ziq": 5, "ezp": 1, "zpo": 1, "ezw": 1, "zwa": 6, "^bf": 9, "bf$": 5, "bfd": 2, "fdc": 1, "bfh": 1, "fhd": 1, "bfr": 13, "bfs": 1, "bft": 1, "^bg": 5, "bg$": 4, "bge": 28, "bgl": 20, "glr": 1, "bgp": 1, "gp$": 8, "^bh": 97, "haj": 22, "tiy": 7, "bhu": 31, "vna": 10, "bhd": 1, "ikh": 26, "ikk": 34, "kku": 12, "bhl": 1, "hoj": 9, "ojp": 1, "jpu": 3, "bhp": 1, "hp$": 7, "bht": 1, "hub": 80, "hud": 73, "mib": 76, "mij": 19, "ij$": 5, "^bi": 2333, "^by": 186, "iaf": 16, "iay": 2, "iaj": 2, "jai": 53, "bya": 12, "iax": 30, "ibc": 7, "byb": 6, "blh": 1, "ybl": 31, "lpt": 40, "hek": 46, "heq": 24, "thq": 9, "hqu": 13, "ibs": 50, "kci": 4, "byc": 16, "vex": 63, "idc": 11, "ydg": 2, "osz": 7, "szc": 4, "zcz": 3, "cz$": 11, "yew": 22, "ecn": 9, "byg": 8, "gbl": 2, "gbu": 22, "igf": 18, "gfe": 14, "gfo": 15, "gah": 42, "igt": 14, "igw": 29, "ihz": 1, "hza": 6, "iys": 1, "ysk": 13, "ije": 17, "ijo": 35, "iju": 33, "ijw": 1, "jwo": 1, "bik": 37, "kuk": 17, "uku": 50, "boq": 4, "gsr": 2, "ilg": 67, "gew": 68, "rzi": 26, "lih": 39, "yrr": 88, "lir": 123, "gsg": 1, "gsl": 12, "llj": 3, "lji": 1, "nhd": 1, "kau": 38, "oav": 14, "dba": 85, "byp": 15, "yp$": 14, "pup": 139, "upi": 254, "unv": 363, "rdb": 43, "dbr": 52, "rdd": 5, "rdg": 5, "dlo": 82, "dsn": 6, "yrg": 8, "rkr": 13, "yrl": 20, "rls": 67, "yrn": 18, "omv": 10, "rrs": 27, "yns": 23, "twh": 15, "irz": 12, "sdn": 5, "xuo": 14, "isg": 168, "opb": 23, "pbi": 13, "opd": 13, "pdo": 24, "opf": 26, "pfu": 54, "psc": 25, "psh": 99, "opv": 6, "pvi": 8, "opw": 37, "pwe": 35, "kop": 83, "tbu": 67, "olj": 3, "lj$": 5, "byu": 3, "niq": 51, "ivv": 28, "biw": 5, "byw": 14, "ekl": 30, "ixb": 3, "xby": 2, "ixl": 11, "byz": 10, "izc": 7, "zca": 11, "izn": 5, "izt": 5, "^bj": 7, "bja": 6, "bjn": 1, "jne": 1, "^bk": 10, "bk$": 4, "bkb": 1, "kbn": 1, "bnd": 1, "bkc": 1, "kcy": 1, "bkg": 2, "kg$": 5, "kgd": 1, "bkl": 1, "bkp": 2, "kpr": 19, "kpt": 1, "bks": 1, "bkt": 1, "^bl": 1703, "kdu": 1, "kfe": 7, "fig": 256, "kgu": 9, "ksb": 22, "ksn": 8, "ksv": 20, "kth": 22, "aeb": 12, "euw": 5, "aew": 7, "nra": 210, "vov": 20, "ayk": 4, "rsd": 13, "mef": 55, "ncm": 6, "stp": 112, "tpl": 34, "bok": 60, "uwb": 1, "wbo": 60, "blc": 1, "bld": 5, "ldg": 7, "hgr": 11, "yey": 13, "ebb": 94, "soe": 64, "ekb": 6, "ezy": 15, "eib": 39, "mmy": 82, "iif": 32, "blf": 2, "htb": 12, "ijv": 1, "jve": 1, "ntz": 44, "tzb": 6, "zbu": 11, "tzi": 94, "tzk": 16, "zkr": 5, "tzs": 14, "zst": 4, "blk": 2, "bll": 1, "blm": 3, "emf": 18, "omk": 11, "mke": 11, "omq": 1, "mqu": 7, "odb": 38, "dbi": 36, "odc": 37, "odf": 28, "dgu": 13, "odl": 198, "odm": 42, "odn": 18, "odt": 20, "odw": 76, "oey": 25, "omd": 14, "omf": 87, "oml": 50, "omh": 10, "omr": 30, "mry": 8, "omt": 32, "mti": 23, "ttt": 5, "oub": 210, "ntv": 10, "wba": 56, "wby": 5, "wca": 25, "wfi": 36, "wfl": 22, "wgu": 2, "owj": 4, "wjo": 3, "wla": 40, "wof": 22, "wpi": 21, "wpo": 22, "wpr": 6, "wsy": 8, "wsp": 23, "wto": 30, "wtu": 6, "wup": 10, "owz": 21, "wze": 6, "wzy": 3, "wzi": 11, "oxb": 33, "xbe": 9, "blr": 1, "blt": 2, "uec": 37, "ueg": 20, "uej": 10, "ffd": 2, "fda": 6, "ffn": 16, "luh": 13, "uhm": 1, "utw": 168, "twu": 3, "wur": 43, "blv": 2, "lv$": 13, "lvd": 1, "^bm": 21, "bmg": 2, "mg$": 10, "mgt": 9, "gte": 15, "bmi": 64, "bmj": 1, "mj$": 3, "bmp": 1, "bmr": 1, "bms": 4, "bmt": 1, "bmu": 16, "bmv": 1, "mv$": 7, "bmw": 1, "mw$": 7, "^bn": 8, "bnc": 1, "bnf": 2, "bns": 2, "^bo": 2824, "twr": 37, "jaa": 8, "sox": 42, "obc": 19, "bca": 59, "obf": 20, "bfl": 16, "obj": 114, "obk": 4, "bke": 6, "bsl": 18, "obt": 118, "bta": 61, "bto": 31, "bwh": 3, "bwo": 35, "cst": 21, "nzk": 2, "zky": 7, "ddh": 28, "ttv": 17, "tva": 29, "ygu": 9, "dyk": 24, "yki": 21, "dyp": 29, "dyw": 15, "ywe": 36, "odk": 17, "oeh": 36, "oek": 17, "fos": 120, "ogb": 34, "gbe": 43, "ogf": 22, "azk": 3, "zke": 5, "keu": 20, "zko": 7, "gyd": 4, "gij": 3, "gym": 121, "gys": 6, "gsu": 5, "ogt": 27, "gtr": 16, "wsk": 27, "usz": 9, "ogw": 34, "ohl": 29, "ohr": 12, "ohs": 10, "oyd": 37, "eky": 18, "ydt": 2, "yfr": 4, "oyg": 5, "yg$": 6, "oyh": 8, "oii": 3, "oyk": 3, "oyt": 11, "boj": 8, "okc": 5, "okh": 17, "okm": 21, "olb": 55, "olg": 47, "gbr": 4, "tcu": 25, "tsp": 95, "ltz": 47, "tzm": 12, "zma": 23, "byx": 2, "mbp": 4, "bpr": 57, "onb": 227, "ucn": 3, "cnu": 8, "due": 100, "onh": 196, "oef": 29, "fay": 32, "nyf": 4, "sq$": 3, "nyv": 2, "onp": 772, "npl": 202, "onx": 4, "oob": 47, "oog": 120, "ooh": 24, "ooj": 5, "okb": 13, "okd": 7, "kde": 5, "okf": 7, "okk": 20, "okp": 6, "ksy": 6, "okw": 20, "msl": 10, "otb": 53, "tbl": 51, "otf": 29, "otj": 2, "otp": 32, "zif": 22, "ygm": 52, "gmu": 11, "iuk": 6, "uoh": 5, "tno": 25, "ghs": 82, "ghw": 23, "rsz": 3, "orz": 33, "six": 50, "osj": 1, "sje": 10, "osn": 25, "ssd": 15, "otk": 10, "ycy": 22, "cym": 104, "ttg": 6, "omc": 15, "otv": 7, "otz": 21, "tzo": 26, "zow": 5, "ghp": 7, "umd": 37, "mdi": 20, "uph": 214, "ouq": 13, "uig": 30, "urq": 13, "ylk": 7, "ouw": 11, "ouz": 35, "nsm": 240, "ovg": 3, "ovl": 2, "vld": 1, "ovr": 8, "ovv": 3, "vve": 9, "wdl": 26, "wfr": 1, "wya": 18, "wka": 5, "wkn": 10, "wld": 8, "wlf": 5, "wll": 7, "wlm": 2, "wss": 4, "wow": 28, "xba": 6, "xbo": 13, "xbu": 11, "oxc": 22, "xca": 45, "oxf": 18, "xfi": 14, "xfo": 24, "xfu": 3, "oxh": 26, "xho": 33, "oxk": 1, "xke": 1, "oxm": 6, "oxr": 2, "xro": 3, "xth": 7, "xty": 7, "oxw": 10, "xwa": 7, "ozc": 3, "aad": 16, "ozm": 3, "ozr": 1, "zuw": 1, "^bp": 16, "bpa": 35, "bpd": 1, "pdp": 2, "bpe": 39, "bph": 17, "bpi": 7, "bpo": 26, "bpp": 1, "bps": 4, "bpt": 1, "^br": 3127, "yax": 7, "ycn": 49, "ycr": 24, "hyf": 3, "yfa": 26, "ygn": 25, "yhi": 8, "yty": 17, "ytm": 1, "hyu": 11, "dyv": 11, "dyu": 5, "dsk": 21, "aeh": 7, "ajn": 5, "jna": 10, "hjn": 1, "jng": 1, "obd": 34, "dtr": 10, "ntf": 48, "stv": 32, "tvo": 26, "unf": 1002, "voe": 21, "brc": 3, "tuf": 101, "kax": 7, "akd": 7, "tbe": 60, "tfe": 34, "tpi": 40, "ebn": 9, "hbl": 11, "hcl": 14, "sfl": 13, "zew": 16, "gsv": 11, "kky": 4, "kkl": 1, "lje": 6, "mss": 4, "vip": 82, "ezh": 3, "zhn": 2, "brg": 1, "ckk": 18, "iln": 40, "dgt": 1, "dgw": 5, "gsd": 7, "tey": 42, "htw": 38, "imh": 8, "img": 4, "ghu": 24, "gse": 9, "ynh": 5, "ynm": 2, "wr$": 6, "ynz": 1, "yoz": 7, "riq": 46, "sbi": 45, "skn": 15, "shh": 21, "zka": 5, "zsk": 3, "xey": 9, "brl": 1, "brm": 1, "brn": 5, "dpi": 34, "dta": 20, "mcr": 17, "omg": 2, "mge": 9, "mhy": 3, "mhi": 6, "xyu": 7, "mof": 26, "sgr": 88, "mth": 17, "mvo": 12, "hoh": 29, "nwy": 4, "nxv": 1, "xvi": 14, "ooc": 92, "okn": 9, "okt": 20, "okv": 4, "msq": 1, "mta": 10, "omw": 12, "ttm": 17, "cek": 18, "wnb": 18, "wnf": 19, "wnn": 12, "wno": 5, "wnp": 15, "npr": 862, "wnt": 26, "wnv": 2, "wnw": 26, "zak": 24, "brr": 2, "rrr": 1, "brs": 1, "brt": 1, "ufs": 5, "ugn": 77, "ruh": 8, "uhn": 6, "ruy": 14, "uye": 23, "uyn": 5, "ruj": 9, "uje": 6, "ulz": 8, "urh": 28, "unh": 574, "nnh": 11, "hup": 40, "usq": 29, "brz": 3, "^bs": 104, "saa": 21, "smg": 3, "scp": 7, "bsd": 4, "sd$": 22, "sdh": 2, "sf$": 20, "sfm": 6, "fmg": 1, "sfs": 3, "sft": 1, "bsg": 5, "gmg": 2, "sgp": 2, "gph": 3, "dmg": 1, "bsj": 1, "bsk": 7, "skt": 8, "sl$": 20, "slm": 3, "sls": 3, "smt": 4, "mtp": 4, "bsn": 3, "bsp": 38, "spt": 4, "bsr": 4, "sre": 127, "srf": 1, "srt": 3, "bss": 7, "ssc": 41, "stj": 4, "tj$": 1, "bsw": 1, "^bt": 15, "btc": 2, "btl": 37, "btn": 1, "bts": 7, "btu": 45, "btw": 2, "tw$": 5, "^bu": 2197, "cez": 3, "ucy": 15, "kay": 63, "kju": 3, "koe": 39, "ksk": 13, "kwh": 8, "udb": 19, "dgy": 26, "udm": 15, "udt": 3, "udz": 10, "ffb": 12, "fba": 13, "ffc": 14, "fco": 16, "fwa": 12, "fot": 26, "ugb": 17, "gbi": 26, "ugd": 6, "ugf": 8, "gfi": 48, "ugp": 2, "gpr": 8, "buh": 13, "uhl": 21, "hlb": 7, "hls": 8, "hlw": 2, "uhr": 17, "hrs": 12, "uya": 27, "uyb": 2, "uyi": 10, "yid": 37, "uys": 16, "buj": 4, "uju": 20, "kav": 27, "vu$": 6, "yef": 7, "ngg": 22, "ulb": 97, "lbl": 29, "lhi": 9, "yhu": 2, "lsn": 4, "lnb": 1, "ulr": 18, "ulw": 18, "ppy": 101, "umc": 36, "mf$": 12, "feg": 19, "mfs": 3, "fuz": 28, "umg": 13, "umk": 12, "mml": 16, "mpk": 22, "pki": 45, "umt": 22, "umw": 16, "ngf": 75, "ngm": 66, "uny": 54, "yip": 10, "buo": 36, "uoy": 30, "bup": 16, "uqs": 2, "qsh": 2, "goy": 25, "rhm": 4, "rku": 16, "rrb": 4, "rrf": 2, "rrk": 3, "urw": 33, "hha": 16, "hwh": 15, "syh": 5, "syw": 13, "usr": 8, "suu": 5, "uut": 2, "usw": 12, "iyn": 4, "tyn": 53, "ryf": 20, "tzv": 8, "zba": 11, "buv": 1, "bux": 19, "uxb": 5, "xeo": 9, "uxt": 22, "uxu": 46, "uza": 33, "uzy": 4, "uzu": 8, "zuk": 6, "zzb": 6, "zbo": 4, "zzg": 1, "zgl": 1, "zzy": 31, "zzs": 1, "zsa": 5, "zzw": 5, "zwi": 19, "zwo": 4, "^bv": 9, "bva": 9, "bvc": 1, "bvd": 2, "vds": 1, "bvy": 1, "bvm": 2, "bvt": 1, "^bw": 11, "bw$": 9, "bwc": 1, "wc$": 10, "bwg": 1, "bwm": 1, "bwr": 3, "bwt": 2, "wts": 4, "bwv": 1, "wv$": 3, "^bx": 2, "bx$": 4, "bxs": 1, "xs$": 5, "^bz": 2, "bz$": 2, "bzi": 3, "^ca": 7264, "peb": 35, "cee": 79, "fue": 36, "jao": 3, "ciq": 4, "ocn": 16, "xl$": 2, "xls": 1, "isf": 205, "iue": 6, "dmu": 12, "foy": 11, "afh": 3, "fh$": 4, "fiz": 26, "rng": 9, "fty": 39, "yuc": 29, "yug": 16, "yus": 24, "yuv": 2, "jep": 13, "jue": 6, "jup": 11, "vci": 3, "eoa": 47, "eof": 91, "eob": 127, "cyd": 20, "lcr": 29, "lcs": 8, "csp": 5, "lct": 17, "lfb": 10, "fbo": 14, "lfd": 7, "fdo": 19, "fho": 14, "lfk": 3, "fki": 10, "pev": 13, "lyx": 13, "lrs": 5, "mny": 3, "alx": 4, "lxe": 2, "xtl": 8, "mde": 24, "eoe": 49, "mm$": 33, "ufl": 31, "mpb": 23, "pba": 28, "mpc": 8, "pcr": 17, "mpf": 19, "pfi": 27, "mpg": 3, "pgr": 22, "mpm": 20, "pma": 97, "mpw": 19, "pwa": 55, "pwo": 65, "naa": 28, "naf": 94, "naj": 10, "ncc": 6, "dyf": 14, "cuz": 8, "aod": 13, "ivl": 4, "vly": 1, "pef": 52, "pib": 39, "pyb": 7, "pys": 6, "apk": 9, "pmi": 12, "puc": 76, "pua": 11, "apw": 31, "caq": 13, "sow": 72, "iim": 4, "fuc": 53, "olx": 2, "lxy": 1, "xyd": 11, "xyh": 13, "dij": 8, "ijn": 9, "jn$": 6, "ysn": 9, "rdp": 13, "duu": 13, "uus": 15, "fox": 75, "fax": 15, "fuf": 10, "riy": 17, "rlt": 8, "wov": 32, "ryk": 13, "yke": 48, "yov": 10, "zoz": 2, "otw": 57, "nsz": 1, "rtb": 32, "vyl": 4, "zey": 10, "saq": 9, "nv$": 8, "hgi": 8, "siq": 5, "szi": 3, "eii": 12, "ctw": 4, "rhs": 3, "auq": 12, "tca": 96, "hpe": 12, "wau": 80, "tsb": 49, "toq": 12, "tsl": 21, "ghn": 27, "vae": 26, "vaf": 4, "vay": 12, "vdi": 3, "vek": 10, "viy": 3, "vyy": 2, "yya": 5, "vuo": 3, "wky": 8, "awq": 1, "wqu": 2, "uaw": 34, "xir": 7, "zib": 18, "^cb": 12, "cbc": 2, "cbd": 2, "cbe": 6, "cbi": 2, "cbr": 4, "cbw": 2, "cbx": 1, "^cc": 43, "ccc": 8, "ccm": 4, "ccd": 2, "cds": 3, "ccf": 2, "cck": 1, "kw$": 4, "ccn": 2, "cnc": 4, "cny": 1, "crp": 3, "csa": 14, "ctv": 3, "ccv": 1, "ccw": 2, "^cd": 25, "cdb": 1, "cdc": 3, "dcf": 2, "cde": 10, "cdf": 3, "cdg": 1, "cdn": 2, "cdp": 2, "cdr": 4, "dsf": 7, "cdt": 1, "cdu": 2, "^ce": 1652, "egb": 13, "gb$": 9, "eyx": 1, "cej": 2, "umv": 54, "lua": 109, "rbb": 8, "nud": 88, "vix": 10, "sya": 18, "ssp": 67, "tuy": 8, "evd": 3, "vde": 3, "^cf": 17, "cfa": 10, "cfb": 1, "cfc": 2, "cfd": 2, "cfe": 3, "cff": 1, "cfh": 3, "fht": 1, "cfi": 1, "cfl": 3, "cfm": 2, "cfp": 1, "cfr": 1, "cfs": 2, "^cg": 10, "cg$": 6, "cga": 5, "cgc": 1, "cge": 5, "cgm": 1, "cgn": 1, "cgs": 1, "cgx": 1, "gx$": 1, "^ch": 5623, "puy": 11, "few": 33, "fcu": 3, "ffm": 24, "fse": 12, "fwe": 5, "kob": 34, "ziu": 7, "lkb": 4, "lkc": 2, "kog": 11, "lkp": 2, "lkr": 3, "lkw": 23, "uky": 10, "mdo": 11, "mfr": 24, "oix": 12, "nix": 37, "duy": 5, "ngc": 32, "gch": 17, "pfa": 16, "paq": 22, "ybd": 26, "rmw": 11, "poy": 25, "vak": 25, "rz$": 21, "udf": 15, "vez": 3, "wia": 8, "hb$": 7, "pja": 12, "psk": 17, "ekf": 4, "ekp": 10, "ekt": 38, "efd": 9, "hej": 6, "lyu": 18, "uev": 26, "gtu": 5, "emk": 3, "mkh": 4, "yva": 11, "syd": 17, "bfi": 25, "etv": 8, "vyi": 17, "evr": 35, "vre": 40, "ewb": 34, "ewy": 19, "wsv": 3, "hg$": 7, "hhn": 1, "iao": 30, "iav": 24, "yaz": 12, "icq": 8, "fch": 6, "ihf": 2, "ihl": 6, "ikm": 2, "ldk": 1, "imk": 6, "eyp": 21, "suy": 4, "ngp": 31, "ovn": 5, "vni": 6, "ipc": 34, "pch": 26, "pya": 18, "ipm": 57, "pmu": 8, "ipw": 37, "stk": 2, "ivy": 29, "vvi": 21, "ivw": 1, "vw$": 2, "hiw": 5, "hkf": 2, "myp": 3, "euh": 19, "zep": 30, "npy": 3, "hmn": 4, "gya": 33, "oyr": 10, "kec": 27, "stg": 26, "uod": 86, "jej": 26, "olr": 14, "ngj": 6, "gji": 1, "taq": 11, "wch": 16, "chq": 1, "emz": 2, "mzl": 1, "ysl": 21, "isr": 207, "mox": 24, "pv$": 7, "huj": 11, "ukc": 4, "ukk": 24, "huq": 2, "rlh": 7, "rrw": 2, "tzp": 12, "zpa": 15, "uzw": 1, "^ci": 1249, "^cy": 1113, "yaa": 3, "cyb": 28, "yb$": 2, "xad": 27, "msp": 36, "csv": 3, "svs": 3, "vs$": 34, "ydn": 17, "cyk": 2, "yka": 8, "urz": 27, "ylv": 55, "imc": 16, "mcu": 6, "yml": 4, "ymr": 6, "oip": 4, "ewu": 7, "nyr": 10, "ynw": 4, "wyd": 6, "nwu": 1, "xts": 8, "mce": 15, "mci": 22, "mdu": 15, "mfl": 22, "mgy": 3, "umj": 6, "mja": 10, "mjo": 6, "mlu": 8, "umq": 6, "uaq": 5, "mte": 19, "mva": 8, "umz": 1, "irq": 9, "sju": 27, "srh": 7, "isz": 12, "sze": 16, "zek": 15, "tyb": 17, "tyf": 23, "yfo": 43, "tyw": 15, "ltp": 18, "tpe": 28, "ynx": 23, "iud": 4, "cyu": 1, "cyw": 2, "ywy": 1, "ydd": 11, "ciw": 1, "cix": 3, "xii": 13, "ixo": 46, "cyz": 3, "^cj": 1, "cj$": 4, "^ck": 2, "^cl": 2053, "kdi": 4, "yoq": 1, "ysv": 26, "amj": 6, "mpd": 5, "pbr": 39, "pdi": 11, "dud": 40, "uxs": 1, "xsh": 7, "rkf": 16, "rkt": 11, "vih": 5, "vuv": 1, "cld": 2, "vla": 23, "emc": 15, "rcq": 2, "wga": 15, "ffh": 11, "fha": 11, "fsi": 7, "fsm": 4, "drd": 3, "thc": 31, "lyv": 13, "ivu": 49, "ywd": 2, "clk": 1, "cll": 2, "lnp": 1, "np$": 12, "dkn": 6, "odp": 24, "ogd": 21, "gwh": 5, "sbr": 29, "udc": 11, "vew": 23, "wna": 17, "wnh": 15, "clr": 2, "lrc": 2, "clt": 1, "ubd": 198, "bfe": 15, "bfo": 28, "ubh": 63, "ubw": 25, "uj$": 3, "msy": 10, "clv": 1, "clw": 1, "^cm": 30, "cmc": 2, "mcc": 88, "cmd": 7, "mdf": 6, "mdr": 12, "mds": 4, "cmf": 1, "cmg": 4, "cmh": 1, "cmy": 2, "myk": 7, "cml": 1, "ml$": 13, "cmm": 1, "cmr": 1, "mrr": 2, "cms": 5, "sgt": 4, "cmt": 2, "mtc": 2, "cmu": 7, "cmw": 1, "^cn": 52, "cn$": 9, "cna": 20, "cnd": 1, "cnm": 2, "nm$": 6, "nms": 3, "cnn": 1, "cnr": 1, "nr$": 9, "cns": 2, "nsr": 14, "^co": 11879, "hsm": 14, "hwr": 3, "oaf": 29, "oah": 12, "oai": 13, "oaw": 3, "bbt": 1, "obh": 9, "obn": 30, "ydy": 3, "cyx": 4, "yzu": 1, "kyo": 12, "eyf": 36, "yfy": 13, "yiz": 15, "ksf": 4, "cuy": 11, "oee": 11, "joy": 104, "oeq": 13, "oev": 43, "xch": 34, "xcl": 69, "xec": 87, "xpe": 301, "xpi": 57, "xpl": 208, "fey": 18, "gsw": 8, "ohb": 6, "ohd": 1, "hdw": 2, "fef": 18, "nye": 36, "lxa": 1, "xau": 10, "uhq": 1, "oyp": 11, "vox": 7, "sns": 1, "oyv": 1, "coj": 12, "dtu": 4, "cwe": 2, "pyu": 3, "iug": 1, "wob": 29, "uiq": 11, "quq": 1, "oln": 45, "olq": 2, "pix": 18, "ixy": 11, "mnw": 2, "olw": 35, "mbf": 14, "mbw": 6, "mfy": 2, "itj": 5, "unb": 896, "qu$": 7, "moq": 9, "paa": 18, "paz": 25, "exn": 5, "xne": 14, "kry": 31, "epc": 14, "pci": 7, "tgo": 17, "tck": 4, "ncn": 2, "crf": 4, "nej": 11, "rmm": 7, "ctf": 39, "nyg": 1, "nyn": 10, "nyz": 1, "jeg": 5, "jub": 54, "utv": 39, "seq": 174, "nsy": 164, "cuo": 69, "pue": 46, "ptf": 5, "nuu": 5, "uua": 3, "nuz": 13, "ctm": 22, "rvl": 3, "vlt": 3, "pyd": 6, "pyf": 8, "pyh": 11, "pih": 6, "pym": 2, "pyw": 5, "coq": 41, "ypp": 13, "pph": 22, "ryz": 13, "nmu": 120, "rnr": 8, "rpn": 9, "pn$": 14, "psb": 7, "psm": 16, "rvk": 1, "vkt": 1, "ktt": 1, "rwu": 5, "osb": 21, "osg": 18, "gyg": 8, "ygy": 17, "gnt": 8, "sve": 73, "tyt": 18, "otq": 3, "tqu": 20, "sdu": 15, "ttb": 3, "ghr": 16, "dnt": 8, "xco": 66, "tug": 38, "rmt": 13, "ryw": 38, "tby": 5, "wbi": 21, "wbr": 30, "wey": 28, "wgi": 10, "wki": 48, "wpa": 18, "wpu": 3, "owq": 1, "wta": 11, "wwh": 2, "mbh": 13, "oxs": 11, "xsa": 10, "xsw": 4, "zyi": 6, "zmo": 5, "^cp": 24, "cpa": 6, "cpc": 5, "pcu": 13, "cpd": 2, "cpe": 1, "cpf": 2, "pff": 3, "cph": 19, "cpi": 4, "cpm": 2, "pmp": 3, "cpo": 5, "cpp": 1, "cpr": 4, "cps": 4, "psr": 3, "cpu": 3, "cpw": 1, "pw$": 7, "^cq": 1, "^cr": 2810, "bgr": 14, "kaj": 13, "ftl": 45, "igv": 4, "gvi": 7, "kef": 53, "nkd": 5, "nkp": 15, "wda": 9, "wfo": 24, "zyc": 2, "zyw": 2, "crb": 1, "crc": 8, "crd": 1, "mwa": 28, "epm": 21, "pmo": 29, "ssk": 8, "ewc": 18, "wcu": 6, "ewl": 50, "rfc": 6, "rfm": 11, "fmp": 1, "ibw": 9, "yob": 22, "yoh": 23, "spb": 7, "spn": 16, "iio": 8, "csh": 5, "csm": 6, "crl": 4, "rlf": 9, "iik": 2, "jik": 13, "kkn": 14, "sbb": 1, "ssg": 4, "ssj": 1, "sja": 8, "sru": 39, "wdw": 1, "wfe": 5, "wnc": 16, "wnm": 9, "owv": 7, "wvi": 9, "zly": 11, "crr": 1, "crs": 2, "crt": 3, "fyf": 3, "uyf": 1, "yff": 2, "uik": 11, "mbc": 2, "uzi": 30, "crw": 3, "rwd": 2, "rwt": 2, "crz": 1, "^cs": 42, "csb": 3, "csc": 7, "csd": 4, "sdc": 5, "csf": 2, "csk": 3, "csl": 3, "smp": 5, "csr": 4, "srg": 2, "sri": 23, "css": 2, "csw": 2, "^ct": 62, "ctc": 2, "ctd": 6, "trl": 2, "tss": 7, "ctt": 11, "^cu": 1630, "lng": 3, "byy": 1, "yye": 3, "ubt": 327, "ckq": 3, "kqu": 3, "cuj": 5, "jid": 14, "cug": 5, "uyl": 11, "uyp": 2, "cuk": 3, "lsd": 6, "ltt": 3, "jev": 7, "pav": 126, "upf": 44, "pfl": 28, "iub": 4, "oiz": 32, "psf": 7, "rll": 7, "rlp": 7, "isv": 39, "vim": 29, "vle": 9, "utb": 212, "utd": 95, "utg": 105, "utk": 24, "utp": 164, "tyh": 16, "cuv": 10, "uvy": 3, "cux": 1, "uxh": 4, "uzc": 3, "zce": 2, "zco": 3, "^cv": 7, "cva": 2, "cvc": 1, "vcc": 2, "cve": 3, "cvo": 4, "cvr": 2, "cvt": 1, "^cw": 14, "cwc": 1, "cwl": 1, "wlt": 3, "cwm": 3, "wms": 3, "cwr": 3, "wru": 6, "cwt": 1, "^cx": 1, "cxi": 1, "^cz": 47, "cza": 31, "zaj": 1, "evn": 8, "tza": 50, "cze": 24, "zec": 29, "czi": 1, "czs": 1, "czu": 1, "^da": 1830, "daa": 5, "yoa": 8, "wnd": 16, "fna": 4, "ftb": 14, "ftn": 20, "ggn": 4, "dhs": 6, "lny": 2, "mkj": 1, "kje": 7, "mnd": 3, "mpp": 12, "amq": 2, "mqa": 1, "qam": 3, "ncz": 1, "czy": 5, "zyk": 3, "rbh": 3, "ilr": 41, "ktz": 1, "zut": 5, "snt": 6, "swd": 1, "wdt": 2, "avp": 2, "vpi": 2, "vao": 2, "vyn": 12, "vyu": 2, "awp": 25, "wut": 8, "^db": 19, "dbf": 1, "dbh": 2, "dbm": 4, "dbs": 1, "dbv": 2, "dbw": 1, "^dc": 33, "dcb": 2, "cbn": 1, "dcd": 2, "dce": 2, "dcm": 5, "dcn": 2, "cnl": 1, "dcs": 2, "dct": 3, "dcv": 1, "^dd": 22, "ddb": 3, "cmp": 3, "ddj": 1, "ddk": 1, "dk$": 2, "ddp": 2, "dpe": 12, "ddt": 1, "ddx": 1, "dx$": 7, "^de": 6988, "fmu": 1, "eaq": 4, "btf": 10, "emd": 15, "emj": 2, "mju": 3, "mui": 19, "emv": 16, "vii": 21, "ecw": 1, "eej": 9, "epf": 23, "pfr": 13, "epg": 9, "pgo": 4, "epn": 19, "epw": 29, "ehg": 1, "hga": 27, "ehw": 1, "jeu": 10, "lft": 13, "lsl": 2, "elq": 4, "lqa": 1, "qa$": 5, "sgn": 3, "mih": 89, "iow": 12, "miv": 42, "miw": 12, "guy": 32, "xyr": 23, "eqn": 1, "qna": 1, "rfn": 2, "uyt": 10, "gnf": 8, "skb": 2, "skm": 9, "pyk": 9, "ykn": 11, "xye": 6, "fov": 28, "suv": 13, "etd": 11, "waa": 13, "usd": 9, "euz": 11, "vap": 156, "evc": 1, "vch": 1, "evl": 8, "utf": 149, "evw": 1, "vws": 2, "wcl": 4, "ewd": 36, "ewf": 25, "wfa": 17, "whu": 23, "wyr": 4, "wsb": 11, "wtr": 4, "dez": 34, "ezf": 1, "zfu": 1, "kif": 34, "^df": 13, "dfc": 1, "dfd": 1, "dfm": 3, "fms": 2, "dfs": 1, "dfw": 1, "fw$": 4, "^dg": 6, "dgh": 1, "dgp": 1, "dgs": 4, "^dh": 100, "smr": 6, "dhh": 1, "hhs": 1, "krs": 2, "dhl": 2, "ruv": 22, "uv$": 12, "hss": 3, "^di": 6450, "^dy": 378, "dko": 3, "ekr": 4, "yrm": 45, "eua": 12, "zeu": 34, "bbu": 25, "kyb": 8, "yeb": 25, "ifd": 2, "yfe": 13, "ifm": 1, "fmo": 4, "gby": 9, "gox": 6, "dih": 47, "ihd": 3, "iy$": 6, "ikd": 5, "keg": 31, "mbd": 13, "shq": 3, "yln": 4, "gaa": 16, "ioo": 4, "osd": 5, "ylq": 1, "hyz": 3, "yzo": 44, "tdi": 25, "rtp": 4, "sbl": 9, "scs": 11, "sfr": 31, "ysf": 7, "ysg": 11, "sgu": 65, "wip": 38, "ysy": 31, "syo": 10, "sjo": 27, "sll": 3, "smn": 1, "soz": 23, "uix": 13, "ysr": 3, "ptm": 5, "sfy": 21, "izu": 9, "svu": 1, "yvo": 8, "diw": 8, "ixf": 5, "xmo": 8, "izd": 3, "zda": 9, "^dj": 38, "jak": 31, "jeb": 13, "jeh": 27, "jib": 40, "jok": 31, "okj": 3, "djs": 2, "js$": 6, "^dk": 5, "dkg": 1, "dkl": 1, "kl$": 2, "dkm": 1, "km$": 2, "dks": 1, "^dl": 16, "dlc": 4, "dlg": 1, "dll": 3, "dlp": 1, "dlr": 1, "dlt": 1, "upg": 39, "dlv": 1, "^dm": 24, "ovk": 11, "dmk": 1, "mk$": 6, "dml": 1, "dms": 4, "dmt": 2, "dmv": 1, "dmz": 1, "mz$": 1, "^dn": 21, "dnb": 1, "nb$": 5, "dnc": 3, "odz": 13, "rzh": 3, "zhi": 12, "dnh": 1, "nhr": 2, "dnr": 1, "dns": 2, "dnx": 1, "^do": 2227, "obz": 1, "bzh": 1, "kiz": 10, "ocm": 5, "ocq": 3, "ogc": 10, "ogp": 8, "gsk": 11, "ogv": 2, "gva": 8, "ohc": 1, "hnn": 7, "oht": 1, "yko": 9, "doj": 6, "oj$": 1, "gk$": 7, "eyk": 2, "aox": 1, "opc": 21, "lgn": 1, "gng": 1, "lku": 7, "tmu": 14, "bty": 5, "btm": 2, "ghf": 29, "vev": 9, "ovz": 1, "vzh": 1, "wce": 5, "wf$": 6, "wng": 11, "gyv": 9, "wnr": 7, "nru": 110, "wve": 2, "wza": 1, "^dp": 19, "dph": 4, "dpm": 2, "dpn": 3, "pnh": 1, "pnp": 3, "dpp": 1, "dps": 2, "dpt": 1, "dpw": 1, "^dq": 3, "dq$": 2, "dqd": 1, "qdb": 1, "dql": 1, "^dr": 1321, "fsa": 9, "ftp": 10, "wgl": 2, "wlo": 13, "drc": 2, "mwh": 4, "drg": 1, "ftf": 7, "ryh": 12, "ysd": 2, "obm": 10, "nkg": 9, "pcl": 9, "pfo": 22, "pwi": 24, "hky": 1, "tde": 15, "tdy": 3, "drp": 1, "ukp": 2, "uxy": 3, "^ds": 30, "dsd": 3, "dss": 4, "dsx": 2, "sx$": 5, "^dt": 19, "dtb": 1, "dtc": 2, "dtd": 1, "dtf": 1, "dtg": 1, "dtl": 2, "dtm": 1, "tmf": 1, "dtp": 1, "^du": 1212, "uax": 3, "ltj": 2, "ubc": 356, "bhg": 1, "ucd": 3, "weg": 19, "duf": 32, "ufu": 9, "dug": 33, "gsp": 15, "uyk": 1, "duj": 1, "kkh": 1, "ukw": 3, "lzu": 1, "mbb": 4, "mbn": 9, "mky": 1, "mmk": 2, "mko": 2, "pf$": 16, "pfs": 1, "unl": 554, "unm": 870, "unp": 1591, "unw": 481, "noj": 4, "uog": 4, "uol": 31, "uov": 3, "exs": 82, "sza": 6, "dut": 39, "^dv": 15, "dvc": 2, "dvm": 4, "vma": 2, "vmr": 3, "mrp": 3, "vms": 4, "dvs": 1, "dvx": 1, "vx$": 2, "^dw": 60, "dw$": 1, "dwb": 1, "wec": 6, "wyk": 6, "dwt": 1, "^dx": 2, "dxt": 2, "^dz": 16, "zau": 4, "zhu": 3, "dzi": 7, "dzy": 1, "zyu": 2, "dzu": 5, "zug": 5, "^ea": 395, "eaa": 4, "snm": 3, "rlv": 1, "hbr": 37, "hsl": 7, "syg": 4, "^eb": 113, "bbm": 1, "ebd": 11, "^ec": 633, "ecb": 10, "ecf": 1, "ecg": 3, "cgo": 5, "ecm": 4, "cmn": 1, "ecp": 21, "ecv": 1, "ecz": 7, "^ed": 370, "dgr": 34, "dny": 2, "edv": 17, "^ee": 65, "eyu": 2, "eeo": 5, "eeq": 12, "^ef": 230, "fah": 23, "fye": 2, "fuv": 1, "efp": 2, "fph": 2, "fth": 23, "^eg": 212, "geg": 8, "ggb": 4, "ggc": 4, "gcr": 5, "gcu": 6, "ggf": 2, "gfr": 9, "ggh": 7, "ggm": 4, "ggp": 3, "ggw": 1, "egk": 2, "gmc": 2, "egp": 5, "egw": 6, "^eh": 26, "ehd": 1, "ehf": 2, "hf$": 8, "ehp": 1, "hrh": 2, "hrl": 3, "hrw": 2, "eht": 4, "^ei": 158, "^ey": 159, "yck": 8, "yeo": 18, "eyg": 8, "htp": 5, "htv": 2, "ijk": 7, "jkm": 1, "ddf": 6, "^ej": 43, "jic": 15, "joo": 11, "^ek": 44, "ekg": 1, "kph": 6, "kpw": 2, "kts": 6, "kue": 13, "ekw": 3, "^el": 1564, "dwu": 2, "sdy": 3, "lfw": 15, "etg": 5, "trt": 2, "lkd": 1, "lkt": 7, "lkv": 7, "lmd": 7, "lmh": 4, "mhu": 3, "lmw": 4, "zev": 4, "^em": 1208, "emg": 1, "emh": 5, "mhp": 2, "emr": 9, "emt": 4, "^en": 3044, "nbl": 140, "ndd": 22, "oao": 4, "fme": 16, "fev": 44, "gja": 8, "lfm": 8, "ngv": 11, "jeo": 33, "imd": 11, "nlu": 94, "enq": 18, "gnc": 2, "gnh": 2, "gnr": 1, "kye": 9, "kyi": 13, "nsn": 66, "fao": 3, "lff": 13, "nug": 51, "nvy": 7, "nwr": 86, "nww": 4, "^eo": 77, "^ep": 1216, "epd": 17, "xeg": 15, "eub": 24, "php": 5, "yim": 11, "ikl": 12, "nao": 23, "trc": 2, "pns": 1, "xyi": 5, "afq": 1, "fqu": 1, "^eq": 317, "eqp": 1, "qpt": 1, "xce": 133, "voq": 3, "quv": 1, "^er": 771, "kig": 4, "yht": 1, "rlk": 2, "zah": 24, "rzg": 1, "zge": 4, "^es": 826, "rpm": 6, "skd": 3, "smd": 7, "opg": 14, "pgi": 14, "exf": 25, "exv": 2, "ssx": 1, "esz": 9, "szt": 8, "^et": 533, "tce": 14, "tfd": 2, "etk": 17, "tuv": 7, "^eu": 792, "ucg": 1, "gub": 34, "uhy": 2, "ukt": 10, "euo": 19, "yrc": 6, "upn": 8, "oaq": 8, "awv": 4, "euu": 1, "uug": 3, "^ev": 508, "nrl": 2, "veq": 1, "evg": 1, "vsl": 1, "evt": 2, "vtu": 2, "vva": 3, "vvo": 1, "evx": 1, "evz": 4, "vzo": 3, "^ew": 29, "wos": 14, "^ex": 2545, "eex": 159, "xc$": 4, "xcy": 4, "xcr": 55, "xcu": 99, "exd": 10, "xde": 3, "xea": 9, "ixs": 4, "xeq": 4, "xeu": 2, "xfl": 5, "exg": 2, "xgo": 2, "exh": 164, "xhb": 1, "hbn": 1, "xhi": 56, "xhu": 16, "xla": 2, "xoe": 8, "xp$": 3, "xpd": 1, "pdt": 2, "xpy": 1, "xpt": 2, "xpu": 50, "pug": 114, "xpw": 1, "pwy": 1, "exq": 13, "xqu": 13, "exr": 8, "xr$": 2, "xra": 5, "xru": 1, "xrx": 1, "xsc": 12, "xsi": 13, "xsp": 4, "xsu": 14, "xta": 56, "iie": 5, "xtb": 6, "xtg": 1, "aof": 3, "aov": 3, "xub": 18, "xuc": 2, "xud": 14, "xul": 28, "xup": 7, "xuv": 12, "exx": 2, "xxo": 1, "exz": 1, "xzo": 1, "^ez": 24, "ezb": 3, "zeq": 2, "ezm": 3, "zme": 3, "zr$": 2, "^fa": 2010, "faa": 5, "fni": 6, "iyu": 6, "sof": 92, "ltb": 9, "mgi": 3, "cyi": 2, "fap": 4, "aqi": 7, "qir": 2, "rmd": 4, "rmv": 2, "quh": 4, "gde": 12, "ruq": 4, "uq$": 2, "ujd": 5, "jda": 11, "ltf": 19, "ulx": 3, "faw": 34, "awz": 2, "faz": 11, "^fb": 5, "fbi": 6, "fbv": 1, "^fc": 18, "fcf": 1, "fcg": 1, "fcy": 2, "fci": 4, "fcp": 1, "fcr": 2, "fcs": 1, "fct": 1, "^fd": 15, "fdd": 2, "iii": 6, "fdh": 1, "dhd": 1, "fdi": 2, "fdm": 2, "fdn": 2, "fdp": 1, "fdr": 1, "fdt": 1, "dty": 2, "fdu": 2, "fdx": 1, "^fe": 1525, "feh": 10, "ehq": 1, "icd": 3, "wcr": 7, "fep": 5, "ziw": 4, "^ff": 9, "frd": 3, "ffv": 3, "fv$": 2, "fvs": 1, "^fg": 10, "fgb": 1, "fgc": 1, "fgd": 1, "fgn": 1, "fgr": 4, "fgs": 2, "^fh": 6, "fhl": 2, "hlm": 3, "lmc": 8, "fhm": 1, "fhr": 1, "fhs": 1, "^fi": 1875, "^fy": 23, "fya": 1, "ibd": 2, "fyc": 2, "igp": 5, "gpe": 8, "fij": 2, "fyk": 2, "fyl": 7, "lgj": 2, "gju": 4, "ioq": 3, "lmg": 5, "mgo": 3, "fyn": 1, "nnb": 3, "fiq": 5, "iqh": 1, "qh$": 2, "lgs": 1, "fyr": 2, "rmn": 17, "tfr": 16, "fys": 3, "hsk": 5, "fyt": 3, "tzc": 2, "zcl": 1, "tzg": 4, "tzh": 2, "tzr": 2, "zro": 4, "zsi": 3, "fiu": 6, "fiv": 19, "ixg": 2, "xgi": 1, "ixu": 7, "fyz": 1, "izg": 3, "^fj": 10, "fja": 3, "fje": 4, "fjo": 5, "^fl": 2114, "pdr": 16, "skf": 7, "vob": 5, "vop": 13, "xbi": 7, "axd": 1, "xdr": 3, "axv": 1, "flb": 1, "fld": 2, "ldx": 2, "eay": 4, "xow": 1, "yby": 4, "icf": 4, "weh": 11, "hrk": 1, "ipf": 21, "ipj": 6, "ysw": 5, "ixw": 1, "fll": 1, "fln": 4, "flr": 2, "fls": 2, "flt": 1, "luy": 8, "yts": 1, "kyh": 4, "uoa": 5, "uob": 7, "uoz": 3, "uxg": 1, "xgr": 1, "uxm": 2, "uxr": 1, "uxw": 1, "^fm": 11, "fmb": 1, "fmc": 3, "mcs": 6, "fmk": 1, "fmn": 1, "fmr": 1, "fmt": 1, "^fn": 7, "fnc": 1, "fnm": 1, "fnp": 2, "^fo": 2683, "fob": 9, "hnl": 1, "nng": 2, "fof": 5, "foh": 6, "oyb": 5, "yil": 6, "fok": 4, "lcg": 1, "iic": 1, "lkf": 6, "fom": 20, "ooy": 5, "otg": 17, "psl": 13, "wod": 15, "slf": 1, "ucq": 1, "ouj": 11, "rsq": 13, "wlk": 1, "wlp": 4, "oxg": 6, "xgl": 2, "oxp": 4, "xsk": 3, "foz": 5, "^fp": 14, "fpa": 5, "fpc": 3, "fpd": 1, "fpe": 8, "fpl": 2, "fpm": 1, "fpo": 3, "fpp": 1, "fps": 3, "fpu": 2, "^fq": 1, "fqd": 1, "qdn": 1, "^fr": 1884, "sck": 2, "nkv": 2, "udp": 7, "frb": 1, "frc": 5, "rcp": 3, "eeu": 8, "ezl": 7, "eyj": 1, "yja": 11, "mdl": 3, "mdn": 2, "frg": 2, "ydm": 1, "dtj": 1, "hsd": 1, "szk": 4, "hso": 38, "zsc": 9, "frl": 2, "gfl": 5, "cfu": 2, "wzl": 2, "frp": 3, "rpg": 5, "frr": 1, "frs": 6, "frt": 1, "itd": 4, "itg": 6, "itv": 8, "frw": 1, "^fs": 15, "fsd": 2, "fsf": 1, "fsl": 1, "fsr": 2, "fss": 2, "fsu": 3, "fsw": 1, "^ft": 15, "ftc": 2, "ftg": 5, "tnc": 2, "ftz": 1, "^fu": 1138, "fua": 2, "fub": 10, "fuh": 5, "fui": 6, "fuy": 1, "fuj": 7, "jiy": 1, "jio": 1, "jiw": 1, "fuk": 4, "kuo": 5, "uok": 7, "nfk": 1, "fuq": 1, "twl": 1, "rzy": 6, "zzt": 2, "zta": 5, "^fv": 1, "^fw": 6, "fwd": 1, "fwh": 1, "whm": 1, "iw$": 7, "^fx": 1, "fx$": 5, "^fz": 2, "fz$": 3, "fzs": 1, "zs$": 3, "^ga": 2672, "gaf": 19, "ffk": 6, "fky": 1, "gwr": 5, "yyo": 2, "ikw": 4, "ivn": 2, "gaj": 4, "jcu": 1, "ajd": 3, "eao": 7, "wsn": 3, "wsw": 11, "upv": 4, "lvv": 1, "voo": 20, "apv": 1, "rhw": 1, "icm": 10, "wci": 2, "wkh": 1, "wps": 4, "^gb": 13, "gbg": 1, "gbh": 1, "gbj": 1, "bj$": 4, "gbm": 1, "gbs": 1, "gbt": 1, "gbz": 1, "^gc": 18, "gcb": 1, "gcc": 1, "gcd": 1, "gce": 1, "gcf": 1, "gci": 1, "gcl": 2, "gcm": 2, "gco": 13, "gcs": 1, "gcv": 2, "cvs": 2, "^gd": 12, "gdb": 1, "gdy": 2, "gdn": 1, "gdp": 1, "gdr": 6, "gds": 2, "^ge": 1716, "ebh": 3, "ktw": 1, "imr": 24, "tji": 8, "lnd": 3, "eoo": 5, "tmj": 2, "mjl": 1, "jlk": 1, "ncx": 1, "cx$": 3, "wrz": 1, "rzt": 1, "gex": 1, "^gf": 2, "gfc": 1, "gft": 1, "ftu": 9, "^gg": 3, "^gh": 161, "ghq": 2, "ghz": 2, "hz$": 4, "^gi": 941, "^gy": 416, "ntk": 1, "gyb": 10, "yup": 10, "gye": 8, "ifb": 2, "fbl": 2, "ffg": 4, "gih": 5, "ymk": 2, "glm": 1, "kgo": 7, "nnl": 7, "zbe": 11, "gyo": 3, "yrf": 2, "irj": 2, "rlc": 5, "rzf": 1, "zfe": 4, "shz": 1, "muk": 25, "gyt": 7, "tks": 1, "ytl": 4, "ttj": 1, "izm": 9, "^gj": 6, "gje": 4, "jed": 14, "^gk": 3, "gks": 2, "^gl": 1456, "glb": 1, "glc": 1, "gld": 1, "eiw": 3, "nnd": 4, "glh": 1, "lhw": 1, "ibn": 25, "glt": 1, "uhw": 1, "^gm": 11, "gmb": 2, "gmr": 1, "mrt": 2, "gmw": 2, "^gn": 171, "tsn": 7, "gnd": 2, "zno": 2, "gnp": 5, "^go": 1571, "oaj": 1, "jir": 15, "gof": 18, "gok": 6, "npe": 433, "ldv": 3, "wog": 21, "nyd": 6, "nzl": 3, "dby": 8, "odv": 4, "ofb": 6, "khp": 2, "ipd": 5, "ttf": 5, "wdn": 8, "^gp": 17, "gpc": 3, "pcd": 4, "gpd": 1, "gpm": 1, "gps": 3, "^gq": 1, "gq$": 1, "^gr": 2560, "ftd": 2, "nyk": 3, "tsd": 9, "ssq": 1, "grd": 2, "ekd": 3, "ynv": 1, "grf": 1, "hsv": 5, "mgr": 3, "ipg": 4, "grn": 2, "oow": 3, "osv": 3, "szy": 3, "woh": 7, "upw": 36, "wnu": 3, "ozn": 5, "zny": 3, "grp": 2, "grr": 1, "grs": 1, "bvi": 48, "uyr": 1, "grx": 1, "^gs": 9, "sbc": 3, "sfc": 5, "^gt": 15, "gtc": 1, "gtd": 1, "gts": 2, "gtt": 1, "^gu": 1234, "iil": 4, "yaq": 5, "jua": 17, "uao": 3, "uau": 8, "guc": 9, "guh": 7, "uyd": 1, "uym": 1, "uyw": 1, "ujr": 3, "lfp": 11, "urj": 9, "fso": 3, "guv": 5, "uvs": 3, "guz": 18, "uzm": 3, "zmc": 1, "^gv": 1, "gv$": 3, "^gw": 49, "gw$": 1, "ynf": 3, "gws": 1, "^gz": 2, "gza": 12, "gzh": 1, "^ha": 3557, "jip": 4, "aej": 2, "fny": 1, "hnv": 1, "yyi": 3, "ajj": 5, "jj$": 2, "jje": 1, "jji": 2, "jjs": 1, "kaf": 29, "uzl": 8, "tky": 2, "fbe": 10, "lfc": 8, "lfn": 5, "vaa": 18, "mdm": 4, "mfa": 7, "rrd": 2, "kj$": 4, "pde": 6, "mrn": 2, "mck": 21, "mza": 4, "dwh": 4, "dwr": 15, "fiy": 8, "swu": 10, "puk": 36, "dtn": 1, "sry": 3, "rpp": 3, "rpw": 5, "rzb": 4, "npf": 3, "ifn": 3, "ikv": 7, "auy": 3, "vda": 7, "vst": 3, "wiy": 6, "wkb": 3, "wkm": 2, "hax": 7, "^hb": 5, "hbm": 1, "^hc": 10, "hcb": 1, "hcf": 2, "hcm": 1, "hcs": 1, "hct": 1, "^hd": 14, "hdb": 2, "dbk": 1, "hdk": 1, "dkf": 1, "kf$": 2, "hdl": 4, "hdq": 1, "dqr": 1, "qrs": 3, "hdt": 1, "dtv": 1, "hdx": 1, "^he": 4074, "adq": 5, "dsq": 2, "rtd": 4, "rtq": 1, "brd": 3, "vyb": 5, "vyh": 6, "vyw": 6, "ctk": 1, "dgc": 1, "edj": 6, "ydu": 5, "suk": 35, "llk": 9, "lmv": 1, "lpf": 14, "lpm": 7, "lpw": 6, "lvt": 1, "vti": 2, "pbu": 24, "epb": 15, "zip": 37, "ppn": 3, "epz": 3, "pza": 1, "pzi": 3, "rbm": 5, "rrm": 2, "tzf": 2, "rzl": 1, "zl$": 5, "oxn": 4, "wgh": 4, "xak": 2, "xav": 6, "xax": 2, "xok": 1, "^hf": 4, "hfd": 1, "fdf": 1, "hfs": 2, "^hg": 6, "hgt": 1, "hgv": 1, "^hh": 3, "hhd": 1, "hhf": 1, "^hi": 1272, "^hy": 2914, "xyk": 2, "xyz": 2, "ghc": 6, "ghj": 6, "hja": 9, "ghv": 2, "ijr": 2, "hyk": 2, "nbj": 1, "mnb": 2, "mnl": 7, "rfj": 1, "ndq": 5, "yoe": 17, "koc": 25, "pnu": 4, "hix": 9, "^hj": 4, "hj$": 1, "hje": 3, "hjs": 1, "^hk": 2, "hk$": 2, "hkj": 1, "^hl": 15, "lbb": 1, "hlc": 1, "hld": 1, "hlh": 4, "lhs": 2, "hsr": 3, "hll": 3, "hlq": 1, "lqn": 1, "qn$": 2, "hlv": 1, "^hm": 10, "hmc": 1, "hmm": 6, "hmp": 2, "hmt": 1, "^hn": 5, "hnc": 2, "hnd": 3, "hnp": 1, "^ho": 2980, "ctz": 3, "xee": 2, "axp": 8, "obg": 3, "bgo": 10, "dgk": 3, "ofm": 7, "gty": 1, "oih": 1, "ytv": 1, "kyp": 15, "wwa": 8, "lmb": 2, "ltv": 5, "lzm": 1, "eoy": 5, "ofp": 2, "fpr": 9, "ofw": 4, "hoq": 5, "sej": 18, "osf": 6, "otd": 7, "ouy": 4, "uyh": 1, "yhn": 1, "hnh": 1, "nhn": 1, "hnm": 1, "tzd": 1, "wff": 2, "wfs": 1, "mev": 17, "^hp": 8, "hpd": 1, "plt": 3, "hpn": 1, "hpp": 3, "^hq": 1, "^hr": 19, "hrd": 4, "hrv": 2, "hrz": 4, "rzn": 1, "zn$": 4, "^hs": 22, "hsb": 4, "hsf": 2, "sln": 1, "hsu": 3, "^ht": 6, "izw": 2, "zwe": 4, "htk": 4, "^hu": 1058, "taj": 11, "udn": 16, "ufn": 1, "uyg": 4, "ujs": 1, "jsa": 3, "ukb": 3, "muh": 14, "uaa": 4, "nfy": 2, "huo": 3, "rlw": 8, "hux": 8, "uxl": 3, "uzv": 1, "zva": 3, "^hv": 5, "hv$": 3, "hvy": 1, "^hw": 15, "hw$": 2, "waj": 4, "hwd": 1, "hwy": 4, "wyl": 15, "hwm": 1, "hwt": 1, "hwu": 2, "^hz": 1, "^ia": 99, "^ya": 476, "htd": 3, "yaf": 11, "yaj": 8, "jei": 3, "jno": 2, "gku": 1, "gtz": 2, "aoo": 1, "aqo": 1, "qon": 1, "rdk": 3, "vl$": 3, "tvy": 2, "yav": 4, "aww": 10, "axc": 6, "azd": 13, "zd$": 5, "zde": 2, "^ib": 67, "^yb": 2, "iby": 23, "ibm": 5, "ibt": 1, "tcw": 1, "cwh": 3, "wh$": 5, "^ic": 372, "icb": 4, "cbm": 2, "cft": 1, "miy": 7, "^yc": 6, "icj": 1, "icp": 5, "^id": 473, "^yd": 4, "idb": 8, "idv": 10, "^ie": 18, "^ye": 335, "yek": 6, "erx": 3, "rxa": 1, "yex": 1, "yez": 8, "ezd": 3, "^if": 29, "^yf": 3, "ifc": 1, "flw": 1, "lwu": 4, "^ig": 145, "^yg": 4, "ggd": 2, "ygg": 1, "gmp": 2, "^yh": 3, "^ih": 10, "ihp": 1, "ihr": 5, "ihs": 3, "yhv": 1, "hvh": 2, "vh$": 3, "yhw": 1, "^ii": 15, "^iy": 8, "^yi": 62, "^yy": 1, "yy$": 4, "yig": 3, "iih": 2, "iyy": 1, "yik": 7, "yir": 14, "iyr": 1, "iiv": 1, "iiw": 1, "izk": 2, "^ij": 11, "ijm": 3, "jma": 3, "ijs": 4, "jss": 3, "^ik": 20, "kej": 3, "khw": 4, "^il": 430, "^yl": 5, "lgw": 2, "^im": 1819, "^ym": 6, "ymc": 2, "mcn": 19, "cnt": 1, "ymh": 3, "moj": 13, "mpv": 2, "imt": 7, "mts": 6, "imv": 2, "^in": 8335, "^yn": 6, "nbb": 1, "nbd": 1, "ncg": 2, "cgr": 16, "buu": 1, "ijb": 1, "jbi": 1, "slt": 4, "ijd": 1, "guk": 1, "gve": 4, "wae": 19, "weo": 4, "nkj": 1, "nmp": 3, "nnk": 3, "obv": 51, "npu": 170, "nqi": 1, "qil": 1, "rj$": 5, "rjo": 26, "ruu": 3, "rww": 2, "wwr": 3, "rxy": 1, "tnl": 1, "dcy": 1, "nvt": 1, "^io": 200, "^yo": 312, "yoy": 3, "yoj": 3, "koh": 23, "koz": 14, "yoo": 7, "spp": 2, "yow": 22, "wlr": 1, "^ip": 66, "^yp": 14, "pbm": 2, "pcc": 5, "pce": 4, "pcs": 5, "pms": 2, "psw": 20, "ypv": 1, "pvs": 1, "ipx": 1, "^iq": 7, "iqb": 1, "qba": 1, "iqr": 2, "qr$": 3, "iqs": 2, "qs$": 7, "qsy": 2, "^yq": 1, "yqu": 6, "^ir": 738, "^yr": 4, "qi$": 1, "qia": 2, "qis": 3, "yrb": 2, "rbk": 1, "rsg": 3, "^is": 1032, "^ys": 11, "sbd": 1, "sbn": 1, "sdt": 1, "ivs": 4, "soq": 4, "spm": 2, "^it": 221, "^yt": 24, "tcz": 2, "ytd": 1, "tll": 3, "^iu": 12, "^yu": 129, "yua": 6, "yuf": 1, "yuh": 3, "yui": 2, "yuj": 1, "yuq": 2, "yuu": 1, "iuu": 2, "iuv": 3, "yuz": 6, "^iv": 67, "^yv": 8, "yv$": 1, "ovc": 2, "vce": 2, "ivb": 1, "vb$": 2, "ivd": 2, "vdt": 2, "vyd": 1, "vyf": 1, "vyt": 1, "ivp": 1, "vp$": 8, "ivt": 2, "^iw": 21, "wao": 3, "iwb": 3, "wbn": 3, "iwc": 1, "^yw": 3, "ywc": 1, "iwf": 1, "iws": 1, "iwu": 4, "iww": 3, "ww$": 3, "^ix": 24, "ixc": 1, "xm$": 1, "cih": 3, "^iz": 37, "izb": 4, "zch": 3, "zdu": 2, "zmi": 6, "ztl": 2, "izv": 2, "zve": 1, "zvo": 6, "voz": 1, "^ja": 1228, "woc": 13, "kpu": 4, "jae": 13, "jaf": 5, "wij": 2, "mtl": 1, "czk": 1, "zck": 1, "nss": 41, "jaq": 13, "qit": 1, "trz": 1, "wtw": 1, "azm": 4, "zzm": 3, "^jb": 2, "jbe": 1, "jbs": 1, "^jc": 14, "jca": 4, "jcb": 1, "jcd": 1, "jce": 2, "jcl": 1, "jcr": 1, "jcs": 1, "jct": 2, "^jd": 3, "jd$": 4, "jds": 2, "^je": 662, "jef": 25, "jek": 3, "jeq": 4, "zre": 2, "^jf": 5, "jfe": 1, "jfi": 1, "jfk": 1, "fk$": 1, "jfm": 1, "fmi": 1, "jfs": 1, "^jg": 3, "jg$": 2, "jge": 1, "jgr": 1, "^jh": 10, "jhe": 2, "jho": 3, "jhs": 1, "jhu": 1, "jhv": 1, "jhw": 1, "^ji": 287, "^jy": 8, "jif": 5, "jih": 3, "jij": 2, "jyl": 2, "imj": 3, "jym": 2, "jyn": 3, "jyo": 1, "pij": 3, "jiq": 1, "jiu": 4, "iuj": 4, "jix": 1, "jiz": 3, "zya": 2, "^jj": 1, "^jk": 1, "jkp": 1, "^jl": 2, "jl$": 3, "jle": 1, "^jm": 3, "jmp": 1, "jms": 1, "jmx": 1, "mx$": 4, "^jn": 10, "jnd": 1, "jnr": 1, "jnt": 1, "^jo": 785, "dhp": 2, "ogj": 3, "hnb": 3, "ohp": 5, "oyw": 3, "joj": 3, "jom": 5, "jop": 5, "jot": 20, "wpy": 2, "jox": 1, "^jp": 3, "jp$": 1, "jpe": 2, "jpl": 1, "^jr": 2, "jr$": 1, "jrc": 1, "^js": 8, "jsc": 2, "jsd": 1, "jsn": 1, "jsr": 1, "src": 4, "jst": 1, "jsw": 2, "^jt": 4, "jti": 1, "jtm": 1, "jtu": 1, "^ju": 785, "juf": 3, "juh": 1, "juy": 2, "jui": 29, "juj": 13, "icz": 6, "juw": 1, "jux": 17, "juz": 2, "uzn": 2, "^jv": 2, "jv$": 1, "jvn": 1, "vnc": 1, "^jw": 3, "jwa": 2, "jwv": 1, "^ka": 1309, "aaw": 3, "zuc": 15, "acz": 2, "czm": 1, "rcz": 3, "afk": 4, "fka": 5, "lvn": 2, "psv": 7, "nnr": 2, "kao": 29, "aoh": 5, "lsr": 4, "saj": 9, "vub": 1, "mcy": 2, "suw": 7, "uwo": 2, "ncb": 2, "cik": 4, "zuh": 1, "^kb": 7, "kb$": 1, "kbp": 2, "kbs": 1, "^kc": 8, "kcb": 1, "kcm": 1, "kcs": 1, "kcv": 1, "^kd": 5, "kd$": 1, "kdc": 2, "kdd": 1, "kdt": 1, "^ke": 1065, "egf": 3, "wrd": 1, "ekc": 1, "ptk": 1, "osr": 8, "rrv": 3, "vut": 4, "kex": 3, "kez": 1, "^kf": 1, "kft": 1, "^kg": 4, "kgb": 2, "kgf": 1, "gf$": 2, "^kh": 216, "lq$": 1, "vzu": 1, "odj": 3, "skh": 6, "khv": 1, "^ki": 1139, "^ky": 113, "ckx": 1, "kxi": 1, "icv": 2, "fek": 7, "iyi": 3, "kii": 7, "iir": 1, "kyk": 4, "kld": 1, "ykl": 2, "kuy": 3, "lnh": 1, "lnm": 2, "lnr": 2, "lns": 5, "lnt": 3, "loj": 6, "kym": 17, "mms": 9, "gkl": 1, "gsn": 4, "ehc": 1, "kby": 1, "kys": 14, "kyu": 12, "axk": 1, "xki": 1, "kiu": 14, "kiv": 22, "kyz": 1, "^kj": 6, "^kk": 4, "kkk": 1, "kkt": 2, "ktp": 2, "^kl": 183, "ppv": 1, "klb": 1, "klj": 1, "lju": 5, "kln": 2, "ucz": 1, "zyn": 2, "klv": 1, "^km": 6, "kmc": 1, "kmm": 1, "^kn": 430, "kn$": 3, "kwu": 5, "kny": 3, "xda": 3, "oxv": 5, "knp": 1, "knt": 1, "^ko": 666, "tzt": 3, "zto": 6, "hlr": 2, "koj": 4, "gyz": 1, "lkk": 1, "ozs": 4, "zsv": 2, "wez": 5, "opj": 6, "pje": 7, "rfb": 11, "jk$": 3, "ybs": 1, "vno": 1, "wtk": 1, "ozh": 6, "^kp": 6, "kp$": 1, "kpc": 1, "kpn": 1, "^kq": 1, "kqc": 1, "qc$": 4, "^kr": 300, "sny": 11, "wcz": 1, "krn": 1, "krp": 1, "mmh": 5, "krz": 1, "zys": 5, "ysz": 3, "^ks": 10, "ksr": 2, "^kt": 7, "ktb": 1, "^ku": 286, "puu": 3, "kuf": 4, "kug": 5, "kui": 4, "kuj": 1, "ukr": 15, "mku": 3, "tuz": 4, "kuv": 3, "szo": 8, "zok": 7, "kuw": 2, "^kv": 18, "kv$": 1, "kvu": 2, "^kw": 50, "rtj": 1, "znk": 1, "eih": 8, "ihw": 2, "eiy": 3, "whr": 2, "kwt": 1, "^la": 3504, "sez": 3, "luz": 7, "mbk": 6, "pbl": 8, "uex": 2, "mzi": 3, "gsy": 2, "nsq": 22, "nuv": 4, "zkn": 1, "szl": 2, "axn": 3, "zyh": 1, "^lb": 11, "lbf": 1, "lbh": 1, "bhs": 1, "lbj": 1, "lbp": 1, "lbw": 1, "^lc": 27, "lcc": 5, "lcd": 4, "lcf": 1, "lcj": 1, "lcn": 1, "lcp": 1, "csy": 2, "lcv": 1, "cvp": 1, "^ld": 13, "^le": 2353, "fgi": 2, "ebk": 4, "bku": 1, "egt": 3, "hyy": 1, "uwf": 1, "efk": 1, "fko": 3, "hmb": 1, "hrb": 1, "hrf": 1, "mty": 2, "ipz": 1, "ekv": 2, "sgh": 1, "siy": 2, "ezg": 1, "zgh": 1, "^lf": 4, "^lg": 8, "lgb": 2, "lgk": 1, "lgm": 1, "lgt": 1, "^lh": 11, "lhb": 1, "lhd": 1, "^li": 2667, "^ly": 474, "aoy": 2, "ibg": 5, "ibk": 2, "ebf": 6, "ebg": 1, "bkn": 1, "ebm": 4, "paj": 15, "spf": 4, "yfk": 1, "htt": 5, "iuo": 2, "meq": 5, "ynb": 3, "lnl": 2, "lnv": 1, "nxl": 2, "ipk": 10, "zt$": 3, "zti": 2, "ttd": 4, "vye": 4, "^lj": 6, "ljb": 1, "jbf": 1, "blj": 1, "^ll": 39, "pwl": 1, "^lm": 8, "lmf": 2, "lmt": 1, "^ln": 6, "^lo": 2012, "bym": 4, "gke": 2, "pez": 43, "iqa": 3, "ovm": 1, "vmi": 2, "^lp": 15, "lpc": 2, "lpd": 1, "lpg": 1, "lpn": 4, "lpp": 1, "lpv": 1, "^lr": 12, "lrb": 3, "^ls": 15, "srp": 2, "^lt": 17, "tjg": 1, "tpd": 2, "tvr": 1, "^lu": 1080, "ubk": 5, "ucb": 2, "dtk": 1, "ufb": 2, "uht": 3, "luq": 2, "utj": 27, "^lv": 6, "^lw": 12, "lwl": 1, "lwm": 2, "wop": 15, "lwp": 1, "lws": 1, "lwt": 1, "^lx": 3, "lxx": 1, "xx$": 5, "^lz": 2, "^ma": 6364, "cgu": 4, "chz": 6, "hzo": 6, "dhv": 1, "zag": 25, "ahb": 2, "ahj": 5, "ahz": 6, "idk": 6, "ajk": 2, "jka": 2, "ajl": 1, "jli": 1, "ikz": 1, "mdy": 1, "zho": 4, "agv": 2, "gvn": 1, "nxm": 2, "nxw": 1, "rcg": 4, "ryj": 2, "yjo": 1, "iup": 9, "rji": 10, "rjy": 1, "rkg": 2, "rkk": 3, "rxi": 6, "asj": 2, "sji": 4, "kmv": 1, "sqa": 2, "qat": 6, "peq": 5, "ejk": 1, "wyc": 18, "wmi": 6, "xix": 3, "zap": 48, "zdo": 1, "^mb": 20, "^mc": 267, "mcb": 4, "mcd": 13, "mcf": 10, "mcg": 27, "mcj": 1, "mcm": 11, "mcp": 6, "mcq": 6, "mct": 3, "mcv": 3, "cvi": 2, "mcw": 3, "^md": 27, "mdc": 1, "mdd": 1, "mdq": 1, "dqs": 1, "mdx": 1, "^me": 4481, "scd": 5, "egd": 2, "gmh": 1, "mmd": 2, "wph": 1, "uoo": 6, "nuh": 1, "vta": 3, "xyf": 3, "tmy": 4, "tzl": 7, "ezc": 6, "ezq": 4, "zqu": 5, "zuz": 8, "^mf": 13, "mfb": 1, "mfd": 2, "mfg": 1, "mfh": 1, "mfj": 1, "fj$": 1, "mfm": 1, "mft": 1, "^mg": 10, "mgb": 1, "mgd": 1, "mgh": 3, "mgk": 1, "mgm": 1, "^mh": 16, "mhd": 1, "mhf": 1, "mhg": 2, "mhl": 1, "mhs": 1, "mhw": 1, "mhz": 1, "^mi": 4868, "^my": 947, "vax": 6, "yif": 1, "ijl": 2, "jnh": 3, "krk": 1, "kvo": 2, "ilj": 1, "lkg": 2, "ieq": 5, "tmh": 2, "lwr": 7, "ilq": 2, "lzb": 2, "zbr": 2, "imz": 1, "mzy": 1, "tnr": 1, "dsz": 1, "ynp": 3, "nxs": 2, "ocd": 4, "qra": 3, "yrv": 1, "rvs": 3, "yrw": 1, "scf": 4, "sgy": 1, "swr": 15, "yxa": 8, "xbl": 3, "ixh": 4, "xov": 3, "izp": 1, "izr": 5, "^mj": 4, "mji": 1, "^mk": 4, "mks": 1, "mkt": 2, "ktg": 1, "^ml": 28, "mlc": 2, "mld": 1, "mlf": 1, "mlg": 1, "mll": 4, "mlr": 1, "mlt": 1, "mlv": 1, "mlw": 1, "mlx": 1, "^mm": 23, "mmc": 1, "mmf": 3, "mmg": 1, "mmj": 1, "mmm": 3, "mmp": 2, "mmt": 1, "mmw": 1, "mmx": 1, "^mn": 44, "mng": 2, "mnp": 1, "mnr": 1, "^mo": 4049, "obp": 4, "hnt": 1, "ohw": 1, "ojg": 1, "jga": 1, "ltk": 1, "omz": 2, "ooa": 6, "tuw": 2, "phg": 1, "rw$": 6, "skv": 2, "tyk": 11, "ukd": 4, "ieo": 1, "^mp": 30, "pbs": 3, "hps": 1, "^mr": 29, "mrb": 1, "mrc": 4, "mrd": 1, "mrf": 2, "mrs": 8, "rsr": 4, "mru": 6, "^ms": 66, "msj": 1, "msn": 1, "msr": 1, "^mt": 31, "mtb": 4, "tbf": 1, "brp": 1, "mtd": 1, "mtf": 1, "mtg": 2, "mtn": 1, "mtt": 3, "tff": 2, "mtu": 2, "mtv": 1, "mtw": 1, "mtx": 1, "tx$": 3, "^mu": 2118, "mua": 3, "mub": 1, "udj": 1, "muj": 5, "ujt": 1, "jta": 1, "myh": 2, "nmr": 2, "muo": 6, "mup": 5, "usj": 2, "skg": 1, "ahf": 1, "tsj": 1, "suh": 6, "muu": 4, "muv": 1, "uvu": 20, "mux": 1, "uzh": 2, "uzj": 2, "zji": 2, "^mv": 11, "mvd": 1, "mvy": 1, "mvp": 1, "mvs": 4, "vsc": 1, "vss": 5, "vsx": 2, "sxa": 1, "^mw": 7, "mwm": 1, "mwt": 1, "^mx": 3, "mxd": 1, "xd$": 2, "mxu": 1, "xu$": 2, "^mz": 3, "mzu": 1, "^na": 1505, "tmm": 3, "yvr": 1, "suj": 2, "epk": 5, "tyg": 13, "plp": 4, "vaj": 6, "vpa": 1, "vsw": 1, "swc": 3, "avv": 9, "zdr": 1, "zii": 1, "^nb": 12, "nbc": 1, "nbf": 1, "nbg": 1, "nbp": 1, "nbs": 1, "nbv": 1, "nbw": 1, "^nc": 25, "ncd": 2, "ncp": 2, "ncv": 1, "^nd": 21, "^ne": 2266, "tgi": 7, "egq": 1, "gqt": 1, "qti": 1, "ejd": 3, "jdi": 1, "khb": 1, "epq": 1, "pqu": 3, "tjc": 1, "tcd": 1, "ufc": 3, "ugk": 1, "gkr": 1, "vsa": 3, "wsc": 6, "wsd": 4, "wsg": 3, "wsr": 5, "xtd": 1, "xtn": 1, "^nf": 11, "nfc": 1, "nfd": 2, "nfp": 1, "nfs": 2, "nft": 2, "^ng": 17, "^nh": 10, "nhg": 1, "nhl": 2, "nhs": 4, "^ni": 1118, "^ny": 141, "flh": 2, "htc": 12, "htg": 6, "htj": 4, "ihh": 1, "iig": 1, "ijh": 1, "phw": 2, "ymw": 1, "nyq": 1, "gyh": 1, "yhz": 1, "wot": 19, "^nj": 4, "^nk": 6, "nkk": 2, "kvd": 1, "^nl": 9, "nlc": 1, "nlf": 2, "nll": 1, "nlm": 1, "nlp": 1, "nlr": 1, "nls": 1, "^nm": 7, "nmc": 1, "^nn": 7, "nnp": 1, "nnt": 1, "nw$": 3, "nnx": 1, "^no": 9017, "oaa": 2, "nmy": 34, "pej": 17, "npn": 7, "ixn": 3, "nps": 25, "cuu": 10, "nvv": 1, "vgo": 1, "kuz": 1, "tny": 1, "^np": 16, "npc": 2, "pfx": 3, "npg": 1, "npp": 1, "prm": 2, "npv": 1, "^nq": 2, "nq$": 2, "nqs": 2, "^nr": 17, "nrc": 1, "nrd": 1, "nrm": 1, "nrp": 1, "rpb": 2, "nrz": 2, "^ns": 25, "sfn": 1, "srb": 4, "^nt": 15, "^nu": 669, "zh$": 2, "nuj": 2, "unq": 106, "nuq": 1, "nuw": 1, "uww": 2, "^nv": 4, "nvh": 1, "nvl": 2, "nvr": 2, "^nw": 8, "nwb": 2, "wbw": 3, "nwc": 1, "nwl": 1, "wlb": 2, "nws": 1, "nwt": 1, "^nx": 1, "nxx": 1, "^nz": 2, "zbc": 1, "^oa": 132, "^ob": 736, "bcu": 27, "bji": 2, "bjr": 1, "bjs": 1, "bpy": 6, "^oc": 617, "cdm": 1, "^od": 282, "ddm": 6, "^oe": 142, "eax": 13, "^of": 206, "fbr": 1, "fke": 3, "ffp": 7, "fsp": 2, "ftt": 4, "^og": 82, "^oh": 33, "ohg": 1, "ohv": 1, "^oy": 44, "^oi": 128, "dwl": 1, "oiu": 1, "oiw": 1, "^oj": 5, "ojt": 1, "^ok": 87, "ofd": 1, "^ol": 456, "iuq": 1, "lsz": 1, "^om": 322, "^on": 373, "^oo": 207, "ooo": 2, "^op": 785, "pcw": 1, "pdy": 1, "opx": 1, "^oq": 4, "^or": 1530, "rdz": 1, "rfg": 1, "hvr": 1, "^os": 685, "scn": 1, "fcw": 1, "sij": 3, "syk": 9, "srd": 2, "^ot": 286, "tbs": 4, "tlf": 1, "otx": 2, "txi": 3, "^ou": 2017, "uiy": 2, "tbb": 2, "tdu": 4, "tdw": 4, "tgn": 6, "tju": 13, "tkn": 5, "utq": 17, "trh": 6, "woe": 25, "tww": 3, "^ov": 4196, "mnn": 6, "vof": 1, "^ow": 92, "whn": 1, "wyh": 3, "wlg": 1, "wlh": 1, "wtc": 1, "^ox": 401, "xga": 5, "vud": 2, "xyq": 3, "xyw": 1, "xna": 1, "xoz": 3, "xph": 1, "xre": 2, "^oz": 62, "ozk": 1, "zku": 1, "^pa": 6269, "nrg": 1, "abx": 1, "hyv": 1, "paf": 2, "ihn": 2, "lfg": 4, "ocz": 2, "lsg": 3, "nmn": 1, "pke": 16, "ubp": 193, "tux": 8, "cij": 1, "uii": 5, "uwl": 2, "avk": 3, "vko": 3, "wkr": 1, "^pb": 8, "pbc": 1, "pbd": 1, "pbt": 1, "pbx": 2, "bxe": 1, "^pc": 23, "pcb": 1, "pcf": 1, "pcn": 2, "cnf": 1, "pcp": 1, "^pd": 15, "pdf": 1, "pdl": 2, "pdn": 1, "pdq": 1, "pds": 4, "^pe": 4817, "akg": 1, "rlm": 5, "ebw": 6, "ejs": 1, "lqg": 1, "qgy": 1, "vsn": 1, "vzn": 1, "^pf": 31, "pfb": 1, "pfc": 1, "pfd": 1, "pfg": 1, "pfp": 1, "^pg": 4, "pgn": 2, "^ph": 3165, "phc": 1, "phd": 1, "gmy": 10, "gml": 2, "phm": 2, "ixv": 1, "^pi": 2348, "^py": 758, "yhr": 1, "hrr": 2, "pyj": 3, "njr": 1, "nxt": 3, "psq": 2, "qur": 7, "ojk": 1, "jki": 1, "zhk": 1, "uow": 1, "pyv": 1, "yvu": 1, "piw": 3, "^pk": 7, "pk$": 4, "pkg": 2, "kgs": 1, "pks": 1, "pkt": 1, "pku": 1, "pkw": 1, "kwy": 1, "^pl": 2319, "yku": 2, "plb": 1, "plc": 2, "pld": 1, "plf": 2, "ynl": 1, "pll": 1, "plm": 1, "ghg": 10, "ovd": 1, "wwi": 8, "plr": 1, "pls": 2, "ugt": 5, "plz": 2, "^pm": 16, "pmc": 1, "pmd": 1, "pmg": 1, "pmk": 1, "pmr": 1, "pmx": 1, "^pn": 184, "pnb": 1, "pnc": 1, "pnd": 1, "pnx": 1, "^po": 4512, "ohj": 3, "oiv": 6, "cyh": 2, "juo": 1, "pgl": 3, "pgu": 7, "tpy": 4, "tpn": 2, "stx": 2, "txy": 1, "stz": 3, "ghk": 3, "wys": 8, "zso": 2, "zzu": 3, "zuo": 4, "^pp": 19, "ppb": 3, "^pq": 1, "pq$": 2, "^pr": 9599, "prb": 1, "prc": 2, "mnt": 1, "vui": 5, "prf": 1, "prg": 1, "prl": 1, "prn": 1, "ocb": 1, "mmn": 1, "tku": 2, "svr": 3, "vry": 1, "ptb": 3, "prp": 1, "prs": 2, "prt": 1, "prv": 1, "prz": 2, "^ps": 1376, "psd": 3, "psg": 1, "ykt": 3, "psn": 6, "swm": 2, "^pt": 247, "goq": 1, "ptg": 1, "ptp": 1, "ptv": 1, "ptw": 6, "^pu": 1621, "vki": 1, "ulj": 1, "lpb": 1, "ulq": 2, "soj": 15, "pkn": 9, "pwr": 17, "unx": 4, "puq": 2, "pux": 2, "^pv": 7, "pva": 2, "pvc": 1, "pvn": 1, "pvo": 2, "pvp": 1, "pvt": 1, "^pw": 8, "pwb": 1, "pwc": 1, "pwd": 1, "pwg": 1, "pwt": 1, "^px": 1, "^qa": 25, "qad": 6, "qaf": 1, "qan": 3, "qar": 2, "qas": 3, "^qb": 2, "qb$": 1, "qbp": 1, "^qc": 1, "^qd": 3, "qd$": 3, "qda": 1, "qdc": 1, "^qe": 8, "qe$": 2, "qed": 2, "qef": 1, "qei": 1, "qer": 2, "qes": 1, "qet": 1, "^qf": 1, "qf$": 1, "^qh": 1, "^qy": 1, "qy$": 1, "^qi": 14, "qib": 2, "qic": 1, "qid": 1, "qiy": 1, "qin": 5, "qiv": 2, "^qk": 2, "qkt": 2, "^ql": 3, "qld": 2, "qli": 2, "^qm": 6, "qm$": 1, "qmc": 1, "qmf": 1, "qmg": 1, "qmp": 1, "qms": 1, "^qn": 3, "qnp": 1, "qns": 1, "^qo": 4, "qoh": 1, "qom": 1, "qop": 2, "^qp": 1, "qp$": 1, "^qq": 2, "qq$": 3, "qqv": 1, "qv$": 2, "^qr": 5, "qrp": 1, "^qs": 6, "qsl": 1, "qso": 1, "qss": 1, "qst": 1, "^qt": 8, "qt$": 1, "qta": 1, "qtc": 1, "qtd": 1, "qty": 1, "qto": 1, "qtr": 1, "qts": 1, "^qu": 1851, "lmp": 1, "qub": 2, "qul": 2, "qum": 3, "qun": 1, "qut": 2, "^qv": 1, "^qw": 2, "qwl": 2, "^ra": 2561, "afv": 1, "fvr": 1, "ajb": 1, "jba": 1, "jko": 2, "ajp": 3, "jpo": 1, "kij": 6, "mje": 3, "^rb": 10, "rbc": 1, "^rc": 29, "rcd": 1, "rcv": 2, "^rd": 16, "rdx": 1, "^re": 10900, "bln": 1, "duz": 2, "wud": 5, "ehb": 1, "hsg": 1, "ykj": 1, "ezv": 4, "eqd": 1, "eqs": 1, "qsp": 1, "ttk": 2, "exb": 1, "^rf": 14, "rfd": 4, "rfp": 5, "rfq": 1, "fq$": 1, "rft": 1, "rfz": 1, "^rg": 10, "rgb": 2, "rgp": 1, "^rh": 751, "rhb": 1, "rhc": 1, "rhd": 1, "rhg": 1, "hml": 1, "rhv": 1, "^ri": 1281, "^ry": 59, "zub": 6, "ibz": 1, "bzu": 1, "ycc": 1, "ydb": 1, "ifk": 2, "jks": 3, "nnc": 1, "wih": 3, "skp": 1, "ivk": 2, "ixd": 1, "^rj": 3, "rjc": 1, "jch": 1, "^rk": 1, "^rl": 12, "rlg": 2, "^rm": 11, "^rn": 12, "nwm": 1, "wmp": 1, "rnz": 2, "nzn": 1, "^ro": 2073, "ybn": 1, "ygb": 1, "orq": 14, "nzw": 1, "vtz": 1, "^rp": 11, "rpc": 1, "rpq": 1, "rpv": 1, "^rq": 3, "rqs": 2, "qsm": 1, "^rr": 11, "rrc": 1, "^rs": 26, "sgb": 1, "rsj": 1, "svp": 3, "^rt": 21, "tfm": 1, "tmp": 2, "^ru": 1065, "^rv": 3, "vsv": 1, "^rw": 9, "rwc": 1, "rwm": 1, "^rx": 1, "^sa": 3835, "abz": 3, "cbu": 2, "uud": 1, "vuc": 3, "lzf": 1, "lzg": 1, "baq": 7, "dyx": 1, "aqq": 2, "qqa": 1, "ukv": 1, "wdu": 5, "wyo": 8, "xev": 2, "^sb": 17, "sbm": 1, "sbs": 1, "sbw": 2, "^sc": 3416, "fpi": 3, "rfw": 2, "scb": 3, "sml": 2, "zax": 3, "lzy": 1, "rgk": 1, "rzk": 1, "rzw": 1, "wej": 2, "wyz": 1, "ubg": 77, "sct": 5, "scx": 1, "^sd": 28, "sdb": 1, "sdd": 1, "sdf": 1, "sdl": 2, "sdm": 1, "sdp": 1, "sdv": 1, "^se": 4923, "uho": 2, "ejm": 1, "jm$": 1, "fhy": 6, "lzn": 1, "jex": 1, "eqe": 2, "qen": 1, "eqf": 1, "qfc": 1, "eqq": 1, "eqr": 1, "qrc": 1, "rbd": 1, "rfh": 4, "uih": 3, "tpf": 1, "xfa": 1, "xlo": 1, "^sf": 21, "sfd": 3, "sfz": 1, "^sg": 14, "^sh": 2898, "fii": 4, "ykh": 2, "wmu": 1, "hcd": 1, "khd": 2, "ldd": 1, "ihc": 1, "ivz": 1, "wju": 1, "hpt": 1, "wmm": 1, "hrp": 1, "kng": 1, "^si": 2375, "^sy": 1329, "dky": 1, "yft": 2, "igc": 1, "gnb": 2, "gnw": 2, "yvk": 1, "nfj": 1, "ynk": 7, "ynr": 2, "yrp": 6, "siw": 8, "xgu": 2, "xhy": 1, "ixp": 4, "yzr": 1, "^sj": 13, "sjc": 1, "sjd": 1, "sjl": 1, "jla": 1, "^sk": 786, "zix": 1, "kyc": 4, "kyf": 4, "kyj": 6, "kyw": 14, "iwy": 2, "skk": 1, "kkv": 1, "plj": 1, "^sl": 1206, "vdo": 1, "slb": 1, "slc": 1, "sld": 3, "ezs": 1, "ojd": 2, "wgo": 1, "slp": 1, "slr": 1, "^sm": 590, "smb": 1, "smc": 2, "smm": 2, "mrg": 1, "^sn": 819, "eyy": 1, "vvl": 2, "snc": 2, "ncf": 1, "snf": 1, "sng": 1, "snp": 2, "snr": 1, "^so": 2401, "ofk": 2, "mnc": 1, "lvs": 1, "vsb": 2, "ouu": 2, "uvl": 1, "vkh": 5, "ovp": 1, "vpr": 1, "xhl": 1, "^sp": 3606, "pck": 1, "spd": 3, "pdm": 1, "pyp": 1, "zfl": 1, "spq": 1, "pqr": 1, "cdl": 1, "^sq": 537, "sqc": 1, "sqd": 1, "sqe": 1, "sql": 2, "sqq": 1, "sqr": 1, "qrt": 1, "qud": 2, "qus": 3, "^sr": 36, "srn": 1, "^ss": 39, "sff": 1, "^st": 5374, "tsr": 3, "cyv": 1, "mmr": 1, "tbd": 2, "tdd": 2, "dmp": 1, "tdm": 3, "yfz": 1, "fzi": 1, "tlg": 1, "lkj": 1, "dld": 1, "tng": 2, "uyv": 1, "^su": 7176, "bcy": 7, "bdw": 1, "bgi": 2, "bgy": 4, "bgu": 3, "bgw": 1, "bhy": 10, "bmy": 1, "bpl": 14, "bpu": 9, "ubq": 10, "bqu": 10, "brh": 4, "ubz": 6, "bze": 1, "bzy": 1, "bzo": 5, "sux": 1, "phb": 1, "phh": 2, "rbn": 1, "pvr": 1, "suq": 4, "suz": 19, "^sv": 48, "ajs": 1, "svc": 2, "svg": 1, "vgs": 1, "svv": 1, "vvs": 2, "^sw": 903, "swb": 3, "wbs": 2, "swf": 1, "swg": 1, "swy": 2, "gkn": 1, "swl": 1, "woy": 3, "sws": 1, "swt": 1, "wtz": 1, "^sx": 2, "sxs": 1, "^sz": 19, "ksz": 1, "szr": 1, "zrd": 1, "^ta": 2994, "fym": 4, "fyw": 1, "ftv": 1, "hlt": 1, "djy": 1, "wus": 8, "mqr": 1, "oiy": 1, "fvi": 1, "vgh": 1, "vgi": 2, "axg": 2, "xiw": 2, "^tb": 10, "^tc": 46, "tcb": 2, "tcg": 1, "tcm": 1, "tcp": 2, "tcs": 2, "tct": 1, "^td": 15, "tdc": 2, "tdl": 1, "^te": 3273, "iht": 2, "spg": 2, "trd": 1, "xtm": 1, "zcu": 1, "zki": 2, "^tf": 8, "tfc": 1, "tfp": 1, "tfs": 1, "tft": 2, "tfx": 1, "^tg": 6, "tgc": 1, "tgt": 2, "tgv": 1, "tgw": 1, "^th": 3001, "ksg": 3, "efm": 2, "gyc": 1, "ytw": 1, "thj": 2, "ujy": 1, "mbt": 4, "thx": 1, "hx$": 1, "^ti": 1494, "^ty": 453, "muq": 2, "ypw": 1, "tyz": 1, "^tj": 11, "^tk": 3, "tkt": 1, "^tl": 25, "tlb": 1, "tlc": 1, "tlm": 1, "tln": 1, "tlp": 1, "tlr": 1, "tlt": 1, "tlv": 1, "^tm": 20, "tmd": 1, "tmr": 3, "tmv": 1, "^tn": 10, "tnb": 1, "tnd": 1, "tnn": 1, "tnp": 2, "npk": 1, "^to": 2391, "ddv": 1, "omj": 2, "azf": 1, "xih": 2, "^tp": 15, "tpk": 2, "tpm": 2, "tpt": 1, "^tq": 1, "tqc": 1, "^tr": 5034, "evm": 1, "zm$": 1, "nsj": 2, "nzs": 1, "wln": 1, "efg": 1, "trf": 1, "ygv": 1, "tyq": 1, "trm": 1, "oez": 1, "dhj": 2, "hyw": 2, "trr": 1, "trw": 1, "^ts": 119, "tsg": 1, "gyu": 1, "^tt": 12, "tfn": 1, "ttp": 1, "^tu": 1448, "tuj": 1, "uoq": 1, "^tv": 4, "tvt": 1, "vtw": 1, "twm": 2, "^tw": 402, "twg": 1, "wib": 9, "wyb": 1, "wyv": 5, "twp": 1, "tws": 1, "twt": 1, "twx": 1, "wx$": 1, "^tx": 3, "txt": 1, "^tz": 42, "tuh": 1, "^ua": 16, "^ub": 46, "^uc": 28, "uub": 1, "^ud": 43, "dmh": 1, "^ue": 7, "^uf": 10, "^ug": 40, "ugc": 1, "^uh": 12, "uhd": 1, "uhf": 1, "uhs": 2, "^ui": 30, "^uy": 1, "^uj": 4, "ujj": 1, "ujp": 1, "^uk": 20, "^ul": 457, "aex": 3, "^um": 193, "aqs": 1, "mpq": 1, "^un": 20242, "wef": 10, "nhc": 1, "nlt": 1, "nrr": 2, "nrw": 1, "upd": 35, "nux": 4, "wem": 8, "^uo": 1, "^up": 817, "pbb": 2, "pby": 2, "pge": 1, "upj": 2, "upk": 5, "upq": 1, "pwh": 4, "^ur": 759, "^us": 225, "scg": 1, "sgs": 1, "^ut": 181, "tqg": 1, "qgs": 1, "^uu": 9, "uuc": 3, "ucp": 2, "cpn": 1, "^uv": 48, "uvv": 1, "^uw": 5, "uwc": 2, "wcs": 2, "uws": 2, "uwt": 1, "^ux": 13, "^uz": 17, "uzb": 5, "^va": 1530, "azs": 2, "vaq": 2, "^vb": 1, "^vc": 7, "vcm": 1, "vco": 1, "vcr": 1, "vcs": 1, "vcu": 1, "^vd": 8, "vdc": 1, "vdf": 1, "vdm": 1, "vdu": 1, "^ve": 1810, "hmg": 2, "ljk": 1, "lzq": 1, "^vf": 7, "vf$": 1, "vfe": 1, "vfy": 1, "vfo": 1, "vfr": 1, "vfs": 1, "vfw": 1, "^vg": 4, "vgf": 1, "^vh": 6, "vha": 1, "vhd": 2, "vhf": 1, "vhs": 2, "^vi": 1847, "^vy": 14, "vyc": 1, "vyk": 1, "yky": 1, "lhj": 1, "viq": 2, "vyr": 2, "vyv": 1, "yvy": 1, "izs": 2, "zsl": 2, "^vj": 1, "vj$": 1, "^vl": 28, "vlb": 2, "vlf": 1, "vls": 1, "vlu": 1, "^vm": 15, "vmc": 3, "vmd": 1, "vme": 1, "vmm": 1, "vmo": 1, "vmt": 1, "^vn": 6, "vnf": 1, "vny": 1, "vnl": 2, "^vo": 722, "voa": 2, "voj": 1, "ojv": 1, "jvo": 1, "kss": 1, "vgr": 1, "^vp": 4, "vpf": 1, "vpn": 1, "^vr": 30, "vrb": 1, "vrc": 1, "vrm": 1, "vrs": 1, "^vs": 14, "vse": 1, "vsp": 1, "vsr": 1, "^vt": 16, "vtc": 1, "vte": 4, "vto": 3, "vtp": 1, "vtr": 1, "vtv": 1, "tvm": 1, "^vu": 146, "vug": 9, "vup": 1, "^vv": 3, "vv$": 1, "vll": 1, "^vw": 2, "^vx": 1, "vxi": 1, "^wa": 2190, "ahp": 2, "yzg": 1, "zgo": 1, "lcz": 1, "hgl": 2, "^wb": 5, "wbc": 1, "^wc": 6, "wcc": 1, "wcp": 1, "wct": 1, "^wd": 4, "wdc": 1, "wdm": 1, "^we": 1124, "bbv": 1, "llq": 1, "wew": 4, "wex": 2, "^wf": 4, "wfp": 2, "wft": 1, "^wg": 2, "wgs": 1, "^wh": 1199, "whb": 1, "whf": 1, "whs": 3, "^wi": 1638, "^wy": 105, "wii": 2, "lcw": 1, "lhl": 1, "wym": 4, "wio": 1, "wyp": 1, "wyg": 1, "^wj": 1, "wjc": 1, "^wk": 3, "^wl": 10, "^wm": 5, "wmc": 2, "wmk": 1, "^wn": 3, "^wo": 1134, "woa": 14, "woi": 3, "woj": 2, "ojc": 1, "jci": 1, "fsb": 4, "kgi": 1, "ldq": 1, "wox": 1, "^wp": 6, "wpb": 1, "wpc": 1, "wpm": 1, "^wr": 388, "wrn": 2, "wrt": 1, "wrv": 1, "^ws": 9, "wsj": 1, "^wt": 4, "wtf": 1, "^wu": 86, "wuc": 2, "wuf": 1, "wug": 2, "wuh": 3, "wuz": 6, "^wv": 3, "wva": 1, "wvs": 1, "^ww": 6, "wwf": 1, "wwm": 1, "^xa": 128, "^xb": 2, "xb$": 1, "xbt": 1, "^xc": 4, "xcf": 1, "xct": 1, "^xd": 4, "xdm": 1, "dmc": 1, "^xe": 190, "rxe": 1, "^xf": 2, "^xh": 1, "^xi": 68, "^xy": 122, "^xl": 1, "^xm": 6, "xmm": 1, "xms": 1, "xmt": 1, "^xn": 3, "xn$": 1, "xns": 1, "xnt": 1, "^xo": 9, "^xp": 3, "xpg": 1, "^xq": 1, "xq$": 1, "^xr": 4, "xrm": 1, "^xs": 2, "^xt": 5, "xtc": 1, "^xu": 5, "xui": 1, "xut": 1, "^xv": 4, "^xw": 2, "xw$": 1, "xws": 1, "^xx": 7, "xxi": 4, "xxv": 1, "xv$": 1, "xxx": 1, "^za": 322, "zay": 3, "zaq": 3, "qaz": 1, "qqu": 1, "^zb": 3, "zb$": 1, "zbb": 1, "^zd": 1, "^ze": 308, "zui": 5, "^zg": 2, "zg$": 1, "zgs": 1, "^zh": 7, "zhd": 1, "zhm": 1, "^zi": 305, "^zy": 178, "zyd": 2, "igz": 11, "zih": 1, "ldj": 1, "zyr": 3, "zyt": 3, "zyp": 1, "zyz": 4, "yzz": 3, "zyv": 2, "^zk": 2, "^zl": 7, "^zm": 2, "zmr": 1, "zmu": 1, "^zn": 2, "^zo": 515, "^zp": 2, "zpg": 1, "zpr": 1, "^zr": 3, "^zs": 6, "^zt": 2, "^zu": 68, "bko": 1, "zud": 1, "zuf": 2, "ugz": 1, "gzw": 1, "zup": 2, "zuu": 1, "^zw": 21, "^zz": 3, "zzz": 1}} \ No newline at end of file diff --git a/deepsecrets/rules/variable_scoring_rules.json b/deepsecrets/rules/variable_scoring_rules.json index 03ce4a8..1858a0c 100644 --- a/deepsecrets/rules/variable_scoring_rules.json +++ b/deepsecrets/rules/variable_scoring_rules.json @@ -1,4 +1,13 @@ [ + { + "id": "SEM_VAR_VALUE_LENGTH", + "name": "Ignore variable values less than 4 chars long", + "pattern": ".*", + "target": "VALUE_LENGTH", + "threshold": 4, + "method": "<=", + "score": -100 + }, { "id": "SEM_VAR_FALSE_STARTING_SEQ", "name": "Ignore variable values with specific starting", @@ -8,12 +17,21 @@ }, { "id": "SEM_VAR_DUMMY_VALUES", - "name": "Ignore variable values with specific flags in values", + "name": "Ignore variable values with specific flags in values. Approach must be revised.", "description": "For me in future: The last part of the regex matches only special symbols", - "pattern": "^(.*${.*|.*obje.*|.*menu.*|.*settin.*|.*locat.*|.*option.*|.*histor.*|record.*|.*versi.*|.*origi.*|.*year.*|.*day.*|.*week.*|.*detai.*|.*gatew.*|.*packa.*|.*prefer.*|.*chain.*|.*provid.*|.*phon.*|.*email.*|.*convers.*|.*perso.*|.*recomm.*|.*referen.*|.*master.*|.*impor.*|.*encrypt.*|.*sched.*|.*contac.*|.*verla.*|.*mediu.*|.*extern.*|.*intern.*|.*progr.*|.*contr.*|.*positi.*|.*facebook.*|.*scale.*|.*button.*|.*untime.*|.*modif.*|.*service.*|.*volum.*|.*length.*|.*guest.*|.*point.*|.*speci.*|.*networ.*|.*mount.*|.*instruc.*|.*kuberne.*|.*layer.*|.*notifi.*|.*submi.*|.*success.*|.*operati.*|.*percen.*|.*persona.*|.*direct.*|.*prefix.*|.*print.*|.*previ.*|.*price.*|.*protoco.*|.*reaso.*|.*count.*|.*reques.*|.*return.*|.*event.*|.*desig.*|.*place.*|.*select.*|.*search.*|.*share.*|.*shape.*|.*show.*|.*stora.*|.*width.*|.*plan.*|.*suggest.*|.*style.*|.*syste.*|.*targe.*|.*export.*|.*amou.*|.*actio.*|.*encod.*|.*trans.*|.*page.*|.*local.*|.*symb.*|.*userid.*|.*useragent.*|.*mock.*|.*color.*|.*password.*|.*ctrl.*|.*help.*|.*numb.*|.*long.*|.*short.*|.*tip.*|.*down.*|.*head.*|.*foote.*|.*info.*|.*last.*|.*next.*|.*title.*|.*label.*|.*error.*|.*view.*|.*center.*|.*date.*|.*paren.*|.*able.*|.*examp.*|.*servi.*|.*class.*|.*prope.*|.*reset.*|.*eract.*|.*rule.*|.*track.*|.*cloud.*|.*call.*|.*index.*|.*limit.*|.*acces.*|.*param.*|.*note.*|.*name.*|.*descr.*|.*value.*|.*polic.*|.*part.*|.*correc.*|.*conte.*|.*media.*|.*exten.*|.*defau.*|.*fill.*|.*unive.*|.*star.*|.*addit.*|.*stac.*|.*excep.*|.*messa.*|.*zone.*|.*total.*|.*type.*|.*valid.*|.*instan.*|.*seria.*|.*docum.*|.*base.*|.*addre.*|.*size.*|.*first.*|.*sourc.*|.*cover.*|.*gener.*|.*config.*|.*with.*|.*window.*|.*support.*|.*comment.*|.*batch.*|.*vault.*|.*secret.*|.*copy.*|.*filte.*|.*clear.*|.*status.*|.*private.*|.*password.*|.*invalid.*|.*link.*|.*test.*|.*expir.*|.*empty.*|.*token.*|null|bearer|foo|foobar|xyz|undefined|none|.*todo.*|.*change.*|.*restore.*|[^A-Za-z0-9]*|%.*%|\\[.*\\]|{.*})$", + "pattern": "^(.*${.*|.*custom.*|.*deadbeef.*|.*langua.*|.*right.*|.*debug.*|.*left.*|.*egmen.*|.*auto.*|.*exampl.*|add.*|use.*|.*launc.*|.*mode.*|.*conne.*|.*sume.*|.*prod.*|.*ward.*|.*serv.*|.*elem.*|.*contain.*|sav.*|.*attac.*|.*fail.*|.*displ.*|.*fake.*|meta.*|.*text.*|.*scop.*|.*tach.*|.*vigat.*|.*ploy.*|.*reat.*|.*your.*|access.*|.*project.*|.*process.*|get.*|set.*|is.*|has.*|.*obje.*|.*beaut.*|.*file.*|.*runn.*|.*confir.*|.*click.*|.*wast.*|.*liste.*|.*hand.*|.*json.*|.*cance.*|.*reduc.*|.*delet.*|.*move.*|.*compa.*|.*forma.*|.*item.*|.*focus.*|.*west.*|.*east.*|.*build.*|.*toggl.*|.*dispos.*|.*componen.*|.*prop.*|.*destr.*|.*string.*|.*receiv.*|.*rende.*|.*featu.*|.*close.*|.*open.*|.*subscr.*|.*coord.*|.*space.*|.*dimen.*|.*menu.*|.*purcha.*|.*order.*|.*report.*|.*identifi.*|.*time.*|.*simpl.*|.*offse.*|.*state.*|.*quantit.*|.*categ.*|.*identit.*|.*devic.*|.*memor.*|.*settin.*|.*locat.*|.*option.*|.*stor.*|record.*|.*versi.*|.*origi.*|.*year.*|.*day.*|.*week.*|.*detai.*|.*gatew.*|.*packa.*|.*prefer.*|.*chain.*|.*provid.*|.*phon.*|.*email.*|.*convers.*|.*perso.*|.*recomm.*|.*referen.*|.*master.*|.*impor.*|.*encrypt.*|.*sched.*|.*contac.*|.*verla.*|.*mediu.*|.*extern.*|.*intern.*|.*progr.*|.*contr.*|.*positi.*|.*facebook.*|.*scale.*|.*button.*|.*untime.*|.*modif.*|.*service.*|.*volum.*|.*length.*|.*guest.*|.*point.*|.*speci.*|.*networ.*|.*mount.*|.*instruc.*|.*kuberne.*|.*layer.*|.*notifi.*|.*submi.*|.*success.*|.*operat.*|.*percen.*|.*persona.*|.*direct.*|.*prefix.*|.*print.*|.*previ.*|.*price.*|.*protoco.*|.*reaso.*|.*count.*|.*reques.*|.*return.*|.*event.*|.*desig.*|.*place.*|.*select.*|.*search.*|.*share.*|.*shape.*|.*show.*|.*stora.*|.*width.*|.*plan.*|.*suggest.*|.*style.*|.*syste.*|.*targe.*|.*export.*|.*amou.*|.*actio.*|.*encod.*|.*decod.*|.*trans.*|.*page.*|.*local.*|.*symb.*|.*userid.*|.*useragent.*|.*mock.*|.*color.*|.*password.*|.*ctrl.*|.*help.*|.*numb.*|.*long.*|.*short.*|.*tip.*|.*down.*|.*head.*|.*foote.*|.*info.*|.*last.*|.*next.*|.*title.*|.*label.*|.*error.*|.*view.*|.*center.*|.*date.*|.*paren.*|.*able.*|.*examp.*|.*servi.*|.*class.*|.*prope.*|.*reset.*|.*eract.*|.*rule.*|.*track.*|.*cloud.*|.*call.*|.*index.*|.*limit.*|.*acce.*|.*param.*|.*note.*|.*name.*|.*descr.*|.*value.*|.*polic.*|.*part.*|.*correc.*|.*conte.*|.*media.*|.*exten.*|.*defau.*|.*fill.*|.*unive.*|.*star.*|.*addit.*|.*stac.*|.*excep.*|.*messa.*|.*zone.*|.*total.*|.*type.*|.*valid.*|.*instan.*|.*seria.*|.*docum.*|.*base.*|.*addre.*|.*size.*|.*first.*|.*sourc.*|.*cover.*|.*gener.*|.*config.*|.*with.*|.*window.*|.*support.*|.*comment.*|.*batch.*|.*vault.*|.*secret.*|.*copy.*|.*filte.*|.*clear.*|.*status.*|.*private.*|.*password.*|.*invalid.*|.*link.*|.*test.*|.*expir.*|.*empty.*|.*token.*|null|bearer|foo|foobar|xyz|undefined|none|.*todo.*|.*change.*|.*restore.*|[^A-Za-z0-9]*|%.*%|\\[.*\\]|{.*})$", "target": "VALUE_NORMALIZED", "score": -50 }, + { + "id": "SEM_VAR_NATURAL_LANGUAGE_VALUES", + "name": "Ignore variable values with 'natural language' score of 0.6+", + "pattern": ".*", + "target": "VALUE_NORMALIZED_NATURALNESS_SCORE", + "threshold": 0.6, + "method": ">=", + "score": -50 + }, { "id": "SEM_VAR_FILE_PATHS", "enabled": true, @@ -26,14 +44,14 @@ "id": "SEM_VAR_NAME_SLICE_REDFLAGS", "description": "Reduce score for common non-secret words inside the variable name PARTS", "target": "NAME_SPACED", - "pattern": "\\b(.*agram.*|.*expira.*|.*contin.*|hashed|storage|operation|contains|last|used|button|meta|method|convert|title|region|label|saving|state|list|extra|group|list|time|id|base|row|status|item|limit|result|window|open|public|str|path|location|field|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template|address)\\b", + "pattern": "\\b(.*agram.*|.*expira.*|.*contin.*|pubkey|public|hashed|storage|operation|contains|last|buy|sell|used|button|meta|method|convert|title|region|label|saving|state|list|extra|group|list|time|id|base|row|status|item|limit|result|window|open|public|str|path|location|field|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template|address)\\b", "score": -20 }, { "id": "SEM_VAR_FULLNAME_REDFLAGS", "description": "Reduce score for common non-secret words inside the variable name", "target": "NAME_NORMALIZED", - "pattern": "(.*method.*|.*button.*|.*extra.*|.*ternal.*|.*parti.*|.*entry.*|.*time.*|.*status.*|.*expira.*|.*expect.*|.*lastuse.*|.*urlkey.*|.*signs.*|.*signal.*|item|meta|signature|limit|result|public|path|location|input|field|data|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template|address)", + "pattern": "(.*method.*|.*equali.*|.*pubkey.*|.*button.*|.*extra.*|.*ternal.*|.*parti.*|.*entry.*|.*time.*|.*status.*|.*expira.*|.*expect.*|.*lastuse.*|.*urlkey.*|.*signs.*|.*signal.*|item|meta|signature|limit|result|public|path|location|input|field|data|cache|prefix|threshold|name|algo|algorithm|mock|fake|dummy|output|uri|type|example|template|address)", "score": -15 }, { @@ -71,11 +89,18 @@ "pattern": "(\\btoken\\b)", "score": 8 }, + { + "id": "SEM_VAR_DB_AS_PART_OF_NAME", + "description": "Key as last slice", + "target": "NAME_NORMALIZED", + "pattern": "^(db.*|database.*)$", + "score": 6 + }, { "id": "SEM_VAR_KEY_AS_FINAL_PART_OF_NAME", "description": "Key as last slice", "target": "NAME_SPACED", - "pattern": "^[a-zA-Z0-9_ ]{3,} (?:key|token)$", + "pattern": "^[a-zA-Z0-9_ ]{2,} (?:key|token|pass)$", "score": 10 }, { @@ -85,12 +110,19 @@ "pattern": "(^token$)", "score": 4 }, + { + "id": "SEM_VAR_TOKEN_AS_END_OF_NAME", + "description": "", + "target": "NAME_NORMALIZED", + "pattern": "token$", + "score": 5 + }, { "id": "SEM_VAR_KEY_AS_PART_OF_NAME", "description": "Key in a variable name slice", "target": "NAME_SPACED", "pattern": "\\bkey\\b", - "score": 3 + "score": 5 }, { diff --git a/poetry.lock b/poetry.lock index d3f5c88..38fa8cd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,16 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand. [[package]] name = "aenum" -version = "3.1.16" +version = "3.1.17" description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "aenum-3.1.16-py2-none-any.whl", hash = "sha256:7810cbb6b4054b7654e5a7bafbe16e9ee1d25ef8e397be699f63f2f3a5800433"}, - {file = "aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf"}, + {file = "aenum-3.1.17-py2-none-any.whl", hash = "sha256:0dad0421b2fbe30e3fb623b2a0a23eff823407df53829d6a72595e7f76f3d872"}, + {file = "aenum-3.1.17-py3-none-any.whl", hash = "sha256:8b883a37a04e74cc838ac442bdd28c266eae5bbf13e1342c7ef123ed25230139"}, + {file = "aenum-3.1.17.tar.gz", hash = "sha256:a969a4516b194895de72c875ece355f17c0d272146f7fda346ef74f93cf4d5ba"}, ] [[package]] @@ -26,55 +27,60 @@ files = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, - {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, ] [[package]] name = "black" -version = "25.9.0" +version = "26.5.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, - {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, - {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, - {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, - {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, - {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, - {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, - {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, - {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, - {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, - {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, - {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, - {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, - {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, - {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, - {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, - {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, - {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, - {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, - {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, - {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, - {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, + {file = "black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893"}, + {file = "black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90"}, + {file = "black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4"}, + {file = "black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef"}, + {file = "black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22"}, + {file = "black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c"}, + {file = "black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7"}, + {file = "black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59"}, + {file = "black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3"}, + {file = "black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe"}, + {file = "black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8"}, + {file = "black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217"}, + {file = "black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d"}, + {file = "black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264"}, + {file = "black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418"}, + {file = "black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3"}, + {file = "black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0"}, + {file = "black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294"}, + {file = "black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a"}, + {file = "black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52"}, + {file = "black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168"}, + {file = "black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3"}, + {file = "black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18"}, + {file = "black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50"}, + {file = "black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae"}, + {file = "black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2"}, + {file = "black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" -pathspec = ">=0.9.0" +pathspec = ">=1.0.0" platformdirs = ">=2" -pytokens = ">=0.1.10" +pytokens = ">=0.4.0,<0.5.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} @@ -82,18 +88,18 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] [[package]] name = "click" -version = "8.1.8" +version = "8.4.0" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81"}, + {file = "click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973"}, ] [package.dependencies] @@ -114,116 +120,118 @@ markers = {main = "sys_platform == \"win32\"", dev = "platform_system == \"Windo [[package]] name = "coverage" -version = "7.10.7" +version = "7.14.0" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["test"] files = [ - {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, - {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, - {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, - {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, - {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, - {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, - {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, - {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, - {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, - {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, - {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, - {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, - {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, - {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, - {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, - {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, - {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, - {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, - {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, - {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, - {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, - {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, - {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, - {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, - {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, - {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, - {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, - {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, - {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, - {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, - {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, - {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, - {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, - {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, - {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, - {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, - {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, - {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, - {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, - {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, - {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, - {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, - {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, - {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, - {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, - {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, - {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, - {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, - {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, - {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, - {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, - {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, - {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, - {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, - {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, - {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, - {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, - {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, - {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, - {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, - {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, - {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, - {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, - {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, - {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, - {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, - {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, - {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, - {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, - {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, - {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, - {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, - {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, - {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, - {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, - {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, - {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, - {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, - {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, - {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, - {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, - {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, - {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, - {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, - {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, - {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, - {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, - {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, - {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, - {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, - {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, - {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, - {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, - {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, - {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, - {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, - {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, - {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, - {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, - {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, - {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, - {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, - {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, - {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, + {file = "coverage-7.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84c32d90bf4537f0e7b4dec9aaa9a938fb8205136b9d2ecf4d7629d5262dc075"}, + {file = "coverage-7.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7c843572c605ab51cfdb5c6b5f2586e2a8467c0d28eca4bdef4ec70c5fecbd82"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0c451757d3fa2603354fdc789b5e58a0e327a117c370a40e3476ba4eabab228c"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3fd43f0616e765ab78d069cf8358def7363957a45cee446d65c502dcfeea7893"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:731e535b1498b27d13594a0527a79b0510867b0ad891532be41cb883f2128e20"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7492f2d493b976941c7ca050f273cbda2f43c381124f7586a3e3c16d1804fec"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc38367eaa2abb1b766ac333142bce7655335a73537f5c8b75aaa89c2b987757"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0a951308cde22cf77f953955a754d04dccb57fe3bb8e345d685778ed9fc1632a"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fab3877e4ebb06bd9d4d4d00ee53309ee5478e66873c66a382272e3ee33eb7ea"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b812eb847b19876ebf33fb6c4f11819af05ab6050b0bfa1bc53412ae81779adb"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d9c8ef6ed820c433de075657d72dda1f89a2984955e58b8a75feb3f184250218"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d128b1bba9361fbaaf6a19e179e6cfd6a9103ce0c0555876f72780acc93efd85"}, + {file = "coverage-7.14.0-cp310-cp310-win32.whl", hash = "sha256:65f267ca1370726ec2c1aa38bbe4df9a71a740f22878d2d4bf59d71a4cd8d323"}, + {file = "coverage-7.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:b34ece8065914f938ed7f2c5872bb865336977a52919149846eac3744327267a"}, + {file = "coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480"}, + {file = "coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c"}, + {file = "coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3"}, + {file = "coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1"}, + {file = "coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627"}, + {file = "coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5"}, + {file = "coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595"}, + {file = "coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27"}, + {file = "coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2"}, + {file = "coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d"}, + {file = "coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef"}, + {file = "coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe"}, + {file = "coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae"}, + {file = "coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e"}, + {file = "coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96"}, + {file = "coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90"}, + {file = "coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477"}, + {file = "coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab"}, + {file = "coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917"}, + {file = "coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8"}, + {file = "coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d"}, + {file = "coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca"}, + {file = "coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828"}, + {file = "coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d"}, + {file = "coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9"}, + {file = "coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1"}, + {file = "coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db"}, + {file = "coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2"}, + {file = "coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644"}, + {file = "coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b"}, + {file = "coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1"}, + {file = "coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74"}, ] [package.dependencies] @@ -249,15 +257,15 @@ pyheck = "0.1.5" [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "test"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] [package.dependencies] @@ -268,14 +276,14 @@ test = ["pytest (>=6)"] [[package]] name = "humanize" -version = "4.13.0" +version = "4.15.0" description = "Python humanize utilities" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "humanize-4.13.0-py3-none-any.whl", hash = "sha256:b810820b31891813b1673e8fec7f1ed3312061eab2f26e3fa192c393d11ed25f"}, - {file = "humanize-4.13.0.tar.gz", hash = "sha256:78f79e68f76f0b04d711c4e55d32bebef5be387148862cb1ef83d2b58e7935a0"}, + {file = "humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769"}, + {file = "humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10"}, ] [package.extras] @@ -283,14 +291,14 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "test"] files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] @@ -346,14 +354,14 @@ Pygments = ">=2.12.0" [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, + {file = "markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a"}, + {file = "markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49"}, ] [package.dependencies] @@ -361,13 +369,12 @@ mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] +plugins = ["mdit-py-plugins (>=0.5.0)"] profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "pytest-timeout", "requests"] [[package]] name = "mdurl" @@ -383,142 +390,128 @@ files = [ [[package]] name = "mmh3" -version = "5.2.0" +version = "5.2.1" description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "mmh3-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81c504ad11c588c8629536b032940f2a359dda3b6cbfd4ad8f74cb24dcd1b0bc"}, - {file = "mmh3-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b898cecff57442724a0f52bf42c2de42de63083a91008fb452887e372f9c328"}, - {file = "mmh3-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be1374df449465c9f2500e62eee73a39db62152a8bdfbe12ec5b5c1cd451344d"}, - {file = "mmh3-5.2.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0d753ad566c721faa33db7e2e0eddd74b224cdd3eaf8481d76c926603c7a00e"}, - {file = "mmh3-5.2.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dfbead5575f6470c17e955b94f92d62a03dfc3d07f2e6f817d9b93dc211a1515"}, - {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7434a27754049144539d2099a6d2da5d88b8bdeedf935180bf42ad59b3607aa3"}, - {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cadc16e8ea64b5d9a47363013e2bea469e121e6e7cb416a7593aeb24f2ad122e"}, - {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d765058da196f68dc721116cab335e696e87e76720e6ef8ee5a24801af65e63d"}, - {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8b0c53fe0994beade1ad7c0f13bd6fec980a0664bfbe5a6a7d64500b9ab76772"}, - {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:49037d417419863b222ae47ee562b2de9c3416add0a45c8d7f4e864be8dc4f89"}, - {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6ecb4e750d712abde046858ee6992b65c93f1f71b397fce7975c3860c07365d2"}, - {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:382a6bb3f8c6532ea084e7acc5be6ae0c6effa529240836d59352398f002e3fc"}, - {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7733ec52296fc1ba22e9b90a245c821adbb943e98c91d8a330a2254612726106"}, - {file = "mmh3-5.2.0-cp310-cp310-win32.whl", hash = "sha256:127c95336f2a98c51e7682341ab7cb0be3adb9df0819ab8505a726ed1801876d"}, - {file = "mmh3-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:419005f84ba1cab47a77465a2a843562dadadd6671b8758bf179d82a15ca63eb"}, - {file = "mmh3-5.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:d22c9dcafed659fadc605538946c041722b6d1104fe619dbf5cc73b3c8a0ded8"}, - {file = "mmh3-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7901c893e704ee3c65f92d39b951f8f34ccf8e8566768c58103fb10e55afb8c1"}, - {file = "mmh3-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5f5536b1cbfa72318ab3bfc8a8188b949260baed186b75f0abc75b95d8c051"}, - {file = "mmh3-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cedac4f4054b8f7859e5aed41aaa31ad03fce6851901a7fdc2af0275ac533c10"}, - {file = "mmh3-5.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb756caf8975882630ce4e9fbbeb9d3401242a72528230422c9ab3a0d278e60c"}, - {file = "mmh3-5.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:097e13c8b8a66c5753c6968b7640faefe85d8e38992703c1f666eda6ef4c3762"}, - {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7c0c7845566b9686480e6a7e9044db4afb60038d5fabd19227443f0104eeee4"}, - {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61ac226af521a572700f863d6ecddc6ece97220ce7174e311948ff8c8919a363"}, - {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:582f9dbeefe15c32a5fa528b79b088b599a1dfe290a4436351c6090f90ddebb8"}, - {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ebfc46b39168ab1cd44670a32ea5489bcbc74a25795c61b6d888c5c2cf654ed"}, - {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1556e31e4bd0ac0c17eaf220be17a09c171d7396919c3794274cb3415a9d3646"}, - {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81df0dae22cd0da87f1c978602750f33d17fb3d21fb0f326c89dc89834fea79b"}, - {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:eba01ec3bd4a49b9ac5ca2bc6a73ff5f3af53374b8556fcc2966dd2af9eb7779"}, - {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9a011469b47b752e7d20de296bb34591cdfcbe76c99c2e863ceaa2aa61113d2"}, - {file = "mmh3-5.2.0-cp311-cp311-win32.whl", hash = "sha256:bc44fc2b886243d7c0d8daeb37864e16f232e5b56aaec27cc781d848264cfd28"}, - {file = "mmh3-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ebf241072cf2777a492d0e09252f8cc2b3edd07dfdb9404b9757bffeb4f2cee"}, - {file = "mmh3-5.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5f317a727bba0e633a12e71228bc6a4acb4f471a98b1c003163b917311ea9a9"}, - {file = "mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be"}, - {file = "mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd"}, - {file = "mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96"}, - {file = "mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094"}, - {file = "mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037"}, - {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773"}, - {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5"}, - {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50"}, - {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765"}, - {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43"}, - {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4"}, - {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3"}, - {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c"}, - {file = "mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49"}, - {file = "mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3"}, - {file = "mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0"}, - {file = "mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065"}, - {file = "mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de"}, - {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044"}, - {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73"}, - {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504"}, - {file = "mmh3-5.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ddc63328889bcaee77b743309e5c7d2d52cee0d7d577837c91b6e7cc9e755e0b"}, - {file = "mmh3-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb0fdc451fb6d86d81ab8f23d881b8d6e37fc373a2deae1c02d27002d2ad7a05"}, - {file = "mmh3-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b29044e1ffdb84fe164d0a7ea05c7316afea93c00f8ed9449cf357c36fc4f814"}, - {file = "mmh3-5.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58981d6ea9646dbbf9e59a30890cbf9f610df0e4a57dbfe09215116fd90b0093"}, - {file = "mmh3-5.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e5634565367b6d98dc4aa2983703526ef556b3688ba3065edb4b9b90ede1c54"}, - {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0271ac12415afd3171ab9a3c7cbfc71dee2c68760a7dc9d05bf8ed6ddfa3a7a"}, - {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:45b590e31bc552c6f8e2150ff1ad0c28dd151e9f87589e7eaf508fbdd8e8e908"}, - {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bdde97310d59604f2a9119322f61b31546748499a21b44f6715e8ced9308a6c5"}, - {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc9c5f280438cf1c1a8f9abb87dc8ce9630a964120cfb5dd50d1e7ce79690c7a"}, - {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c903e71fd8debb35ad2a4184c1316b3cb22f64ce517b4e6747f25b0a34e41266"}, - {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:eed4bba7ff8a0d37106ba931ab03bdd3915fbb025bcf4e1f0aa02bc8114960c5"}, - {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fdb36b940e9261aff0b5177c5b74a36936b902f473180f6c15bde26143681a9"}, - {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7303aab41e97adcf010a09efd8f1403e719e59b7705d5e3cfed3dd7571589290"}, - {file = "mmh3-5.2.0-cp313-cp313-win32.whl", hash = "sha256:03e08c6ebaf666ec1e3d6ea657a2d363bb01effd1a9acfe41f9197decaef0051"}, - {file = "mmh3-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7fddccd4113e7b736706e17a239a696332360cbaddf25ae75b57ba1acce65081"}, - {file = "mmh3-5.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa0c966ee727aad5406d516375593c5f058c766b21236ab8985693934bb5085b"}, - {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e5015f0bb6eb50008bed2d4b1ce0f2a294698a926111e4bb202c0987b4f89078"}, - {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0f3ed828d709f5b82d8bfe14f8856120718ec4bd44a5b26102c3030a1e12501"}, - {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:f35727c5118aba95f0397e18a1a5b8405425581bfe53e821f0fb444cbdc2bc9b"}, - {file = "mmh3-5.2.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bc244802ccab5220008cb712ca1508cb6a12f0eb64ad62997156410579a1770"}, - {file = "mmh3-5.2.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ff3d50dc3fe8a98059f99b445dfb62792b5d006c5e0b8f03c6de2813b8376110"}, - {file = "mmh3-5.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:37a358cc881fe796e099c1db6ce07ff757f088827b4e8467ac52b7a7ffdca647"}, - {file = "mmh3-5.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b9a87025121d1c448f24f27ff53a5fe7b6ef980574b4a4f11acaabe702420d63"}, - {file = "mmh3-5.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ba55d6ca32eeef8b2625e1e4bfc3b3db52bc63014bd7e5df8cc11bf2b036b12"}, - {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9ff37ba9f15637e424c2ab57a1a590c52897c845b768e4e0a4958084ec87f22"}, - {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a094319ec0db52a04af9fdc391b4d39a1bc72bc8424b47c4411afb05413a44b5"}, - {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5584061fd3da584659b13587f26c6cad25a096246a481636d64375d0c1f6c07"}, - {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecbfc0437ddfdced5e7822d1ce4855c9c64f46819d0fdc4482c53f56c707b935"}, - {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7b986d506a8e8ea345791897ba5d8ba0d9d8820cd4fc3e52dbe6de19388de2e7"}, - {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:38d899a156549da8ef6a9f1d6f7ef231228d29f8f69bce2ee12f5fba6d6fd7c5"}, - {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d86651fa45799530885ba4dab3d21144486ed15285e8784181a0ab37a4552384"}, - {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c463d7c1c4cfc9d751efeaadd936bbba07b5b0ed81a012b3a9f5a12f0872bd6e"}, - {file = "mmh3-5.2.0-cp314-cp314-win32.whl", hash = "sha256:bb4fe46bdc6104fbc28db7a6bacb115ee6368ff993366bbd8a2a7f0076e6f0c0"}, - {file = "mmh3-5.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c7f0b342fd06044bedd0b6e72177ddc0076f54fd89ee239447f8b271d919d9b"}, - {file = "mmh3-5.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:3193752fc05ea72366c2b63ff24b9a190f422e32d75fdeae71087c08fff26115"}, - {file = "mmh3-5.2.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:69fc339d7202bea69ef9bd7c39bfdf9fdabc8e6822a01eba62fb43233c1b3932"}, - {file = "mmh3-5.2.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:12da42c0a55c9d86ab566395324213c319c73ecb0c239fad4726324212b9441c"}, - {file = "mmh3-5.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7f9034c7cf05ddfaac8d7a2e63a3c97a840d4615d0a0e65ba8bdf6f8576e3be"}, - {file = "mmh3-5.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11730eeb16dfcf9674fdea9bb6b8e6dd9b40813b7eb839bc35113649eef38aeb"}, - {file = "mmh3-5.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:932a6eec1d2e2c3c9e630d10f7128d80e70e2d47fe6b8c7ea5e1afbd98733e65"}, - {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca975c51c5028947bbcfc24966517aac06a01d6c921e30f7c5383c195f87991"}, - {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b0b58215befe0f0e120b828f7645e97719bbba9f23b69e268ed0ac7adde8645"}, - {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29c2b9ce61886809d0492a274a5a53047742dea0f703f9c4d5d223c3ea6377d3"}, - {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a367d4741ac0103f8198c82f429bccb9359f543ca542b06a51f4f0332e8de279"}, - {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5a5dba98e514fb26241868f6eb90a7f7ca0e039aed779342965ce24ea32ba513"}, - {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:941603bfd75a46023807511c1ac2f1b0f39cccc393c15039969806063b27e6db"}, - {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:132dd943451a7c7546978863d2f5a64977928410782e1a87d583cb60eb89e667"}, - {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f698733a8a494466432d611a8f0d1e026f5286dee051beea4b3c3146817e35d5"}, - {file = "mmh3-5.2.0-cp314-cp314t-win32.whl", hash = "sha256:6d541038b3fc360ec538fc116de87462627944765a6750308118f8b509a8eec7"}, - {file = "mmh3-5.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e912b19cf2378f2967d0c08e86ff4c6c360129887f678e27e4dde970d21b3f4d"}, - {file = "mmh3-5.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e7884931fe5e788163e7b3c511614130c2c59feffdc21112290a194487efb2e9"}, - {file = "mmh3-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3c6041fd9d5fb5fcac57d5c80f521a36b74aea06b8566431c63e4ffc49aced51"}, - {file = "mmh3-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:58477cf9ef16664d1ce2b038f87d2dc96d70fe50733a34a7f07da6c9a5e3538c"}, - {file = "mmh3-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be7d3dca9358e01dab1bad881fb2b4e8730cec58d36dd44482bc068bfcd3bc65"}, - {file = "mmh3-5.2.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:931d47e08c9c8a67bf75d82f0ada8399eac18b03388818b62bfa42882d571d72"}, - {file = "mmh3-5.2.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd966df3489ec13848d6c6303429bbace94a153f43d1ae2a55115fd36fd5ca5d"}, - {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c677d78887244bf3095020b73c42b505b700f801c690f8eaa90ad12d3179612f"}, - {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63830f846797187c5d3e2dae50f0848fdc86032f5bfdc58ae352f02f857e9025"}, - {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c3f563e8901960e2eaa64c8e8821895818acabeb41c96f2efbb936f65dbe486c"}, - {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96f1e1ac44cbb42bcc406e509f70c9af42c594e72ccc7b1257f97554204445f0"}, - {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7bbb0df897944b5ec830f3ad883e32c5a7375370a521565f5fe24443bfb2c4f7"}, - {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1fae471339ae1b9c641f19cf46dfe6ffd7f64b1fba7c4333b99fa3dd7f21ae0a"}, - {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:aa6e5d31fdc5ed9e3e95f9873508615a778fe9b523d52c17fc770a3eb39ab6e4"}, - {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:746a5ee71c6d1103d9b560fa147881b5e68fd35da56e54e03d5acefad0e7c055"}, - {file = "mmh3-5.2.0-cp39-cp39-win32.whl", hash = "sha256:10983c10f5c77683bd845751905ba535ec47409874acc759d5ce3ff7ef34398a"}, - {file = "mmh3-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fdfd3fb739f4e22746e13ad7ba0c6eedf5f454b18d11249724a388868e308ee4"}, - {file = "mmh3-5.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:33576136c06b46a7046b6d83a3d75fbca7d25f84cec743f1ae156362608dc6d2"}, - {file = "mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8"}, + {file = "mmh3-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5d87a3584093e1a89987e3d36d82c98d9621b2cb944e22a420aa1401e096758f"}, + {file = "mmh3-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30e4d2084df019880d55f6f7bea35328d9b464ebee090baa372c096dc77556fb"}, + {file = "mmh3-5.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bbc17250b10d3466875a40a52520a6bac3c02334ca709207648abd3c223ed5c"}, + {file = "mmh3-5.2.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76219cd1eefb9bf4af7856e3ae563d15158efa145c0aab01e9933051a1954045"}, + {file = "mmh3-5.2.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb9d44c25244e11c8be3f12c938ca8ba8404620ef8092245d2093c6ab3df260f"}, + {file = "mmh3-5.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5d542bf2abd0fd0361e8017d03f7cb5786214ceb4a40eef1539d6585d93386"}, + {file = "mmh3-5.2.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:08043f7cb1fb9467c3fbbbaea7896986e7fbc81f4d3fd9289a73d9110ab6207a"}, + {file = "mmh3-5.2.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:add7ac388d1e0bf57259afbcf9ed05621a3bf11ce5ee337e7536f1e1aaf056b0"}, + {file = "mmh3-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41105377f6282e8297f182e393a79cfffd521dde37ace52b106373bdcd9ca5cb"}, + {file = "mmh3-5.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3cb61db880ec11e984348227b333259994c2c85caa775eb7875decb3768db890"}, + {file = "mmh3-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b5378de2b139c3a830f0209c1e91f7705919a4b3e563a10955104f5097a70a"}, + {file = "mmh3-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e904f2417f0d6f6d514f3f8b836416c360f306ddaee1f84de8eef1e722d212e5"}, + {file = "mmh3-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f1fbb0a99125b1287c6d9747f937dc66621426836d1a2d50d05aecfc81911b57"}, + {file = "mmh3-5.2.1-cp310-cp310-win32.whl", hash = "sha256:b4cce60d0223074803c9dbe0721ad3fa51dafe7d462fee4b656a1aa01ee07518"}, + {file = "mmh3-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f01f044112d43a20be2f13a11683666d87151542ad627fe41a18b9791d2802f"}, + {file = "mmh3-5.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:7501e9be34cb21e72fcfe672aafd0eee65c16ba2afa9dcb5500a587d3a0580f0"}, + {file = "mmh3-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae0f0bd7d30c0ad61b9a504e8e272cb8391eed3f1587edf933f4f6b33437450"}, + {file = "mmh3-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9aeaf53eaa075dd63e81512522fd180097312fb2c9f476333309184285c49ce0"}, + {file = "mmh3-5.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0634581290e6714c068f4aa24020acf7880927d1f0084fa753d9799ae9610082"}, + {file = "mmh3-5.2.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080c0637aea036f35507e803a4778f119a9b436617694ae1c5c366805f1e997"}, + {file = "mmh3-5.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db0562c5f71d18596dcd45e854cf2eeba27d7543e1a3acdafb7eef728f7fe85d"}, + {file = "mmh3-5.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d9f9a3ce559a5267014b04b82956993270f63ec91765e13e9fd73daf2d2738e"}, + {file = "mmh3-5.2.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:960b1b3efa39872ac8b6cc3a556edd6fb90ed74f08c9c45e028f1005b26aa55d"}, + {file = "mmh3-5.2.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d30b650595fdbe32366b94cb14f30bb2b625e512bd4e1df00611f99dc5c27fd4"}, + {file = "mmh3-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82f3802bfc4751f420d591c5c864de538b71cea117fce67e4595c2afede08a15"}, + {file = "mmh3-5.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:915e7a2418f10bd1151b1953df06d896db9783c9cfdb9a8ee1f9b3a4331ab503"}, + {file = "mmh3-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fc78739b5ec6e4fb02301984a3d442a91406e7700efbe305071e7fd1c78278f2"}, + {file = "mmh3-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:41aac7002a749f08727cb91babff1daf8deac317c0b1f317adc69be0e6c375d1"}, + {file = "mmh3-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d8089d853c7963a8ce87fff93e2a67075c0bc08684a08ea6ad13577c38ffc38"}, + {file = "mmh3-5.2.1-cp311-cp311-win32.whl", hash = "sha256:baeb47635cb33375dee4924cd93d7f5dcaa786c740b08423b0209b824a1ee728"}, + {file = "mmh3-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e4ecee40ba19e6975e1120829796770325841c2f153c0e9aecca927194c6a2a"}, + {file = "mmh3-5.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:c302245fd6c33d96bd169c7ccf2513c20f4c1e417c07ce9dce107c8bc3f8411f"}, + {file = "mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1"}, + {file = "mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00"}, + {file = "mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7"}, + {file = "mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b"}, + {file = "mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006"}, + {file = "mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825"}, + {file = "mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a"}, + {file = "mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b"}, + {file = "mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166"}, + {file = "mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16"}, + {file = "mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211"}, + {file = "mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000"}, + {file = "mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5"}, + {file = "mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025"}, + {file = "mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00"}, + {file = "mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc"}, + {file = "mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e"}, + {file = "mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d"}, + {file = "mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6"}, + {file = "mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f"}, + {file = "mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8"}, + {file = "mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6"}, + {file = "mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9"}, + {file = "mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03"}, + {file = "mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b"}, + {file = "mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5"}, + {file = "mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593"}, + {file = "mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4"}, + {file = "mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1"}, + {file = "mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104"}, + {file = "mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d"}, + {file = "mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f"}, + {file = "mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2"}, + {file = "mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a"}, + {file = "mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b"}, + {file = "mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229"}, + {file = "mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d"}, + {file = "mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227"}, + {file = "mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0"}, + {file = "mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b"}, + {file = "mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966"}, + {file = "mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b"}, + {file = "mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8"}, + {file = "mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7"}, + {file = "mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e"}, + {file = "mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74"}, + {file = "mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc"}, + {file = "mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617"}, + {file = "mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2"}, + {file = "mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312"}, + {file = "mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb"}, + {file = "mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a"}, + {file = "mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105"}, + {file = "mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a"}, + {file = "mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd"}, + {file = "mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4"}, + {file = "mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb"}, + {file = "mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe"}, + {file = "mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba"}, + {file = "mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00"}, + {file = "mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8"}, + {file = "mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc"}, + {file = "mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f"}, + {file = "mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44"}, + {file = "mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7"}, + {file = "mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c"}, + {file = "mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac"}, + {file = "mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912"}, + {file = "mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf"}, + {file = "mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d"}, + {file = "mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18"}, + {file = "mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82"}, + {file = "mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb"}, + {file = "mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b"}, + {file = "mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad"}, ] [package.extras] -benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.9.0)", "xxhash (==3.5.0)"] -docs = ["myst-parser (==4.0.1)", "shibuya (==2025.7.24)", "sphinx (==8.2.3)", "sphinx-copybutton (==0.5.2)"] -lint = ["black (==25.1.0)", "clang-format (==20.1.8)", "isort (==6.0.1)", "pylint (==3.3.7)"] -plot = ["matplotlib (==3.10.3)", "pandas (==2.3.1)"] -test = ["pytest (==8.4.1)", "pytest-sugar (==1.0.0)"] -type = ["mypy (==1.17.0)"] +benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.10.0)", "xxhash (==3.6.0)"] +docs = ["myst-parser (==5.0.0)", "shibuya (==2026.1.9)", "sphinx (==8.2.3)", "sphinx-copybutton (==0.5.2)"] +lint = ["actionlint-py (==1.7.11.24)", "clang-format (==22.1.0)", "codespell (==2.4.1)", "pylint (==4.0.5)", "ruff (==0.15.4)"] +plot = ["matplotlib (==3.10.8)", "pandas (==3.0.1)"] +test = ["pytest (==9.0.2)", "pytest-sugar (==1.1.1)"] +type = ["mypy (==1.19.1)"] [[package]] name = "mypy-extensions" @@ -571,38 +564,43 @@ dev = ["black", "mypy", "pytest"] [[package]] name = "packaging" -version = "25.0" +version = "26.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev", "test"] files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, + {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, + {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, ] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.1.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, + {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, + {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, ] +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] + [[package]] name = "pbr" -version = "7.0.1" +version = "7.0.3" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" groups = ["main"] files = [ - {file = "pbr-7.0.1-py2.py3-none-any.whl", hash = "sha256:32df5156fbeccb6f8a858d1ebc4e465dcf47d6cc7a4895d5df9aa951c712fc35"}, - {file = "pbr-7.0.1.tar.gz", hash = "sha256:3ecbcb11d2b8551588ec816b3756b1eb4394186c3b689b17e04850dfc20f7e57"}, + {file = "pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b"}, + {file = "pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29"}, ] [package.dependencies] @@ -622,21 +620,16 @@ files = [ [[package]] name = "platformdirs" -version = "4.4.0" +version = "4.9.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, + {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, + {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - [[package]] name = "pluggy" version = "1.6.0" @@ -682,19 +675,19 @@ ply = "3.11" [[package]] name = "pydantic" -version = "2.12.3" +version = "2.13.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, - {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, + {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.41.4" +pydantic-core = "2.46.4" typing-extensions = ">=4.14.1" typing-inspection = ">=0.4.2" @@ -704,129 +697,132 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.46.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, - {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, ] [package.dependencies] @@ -834,14 +830,14 @@ typing-extensions = ">=4.14.1" [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "test"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] @@ -875,21 +871,21 @@ files = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.3" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "test"] files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, + {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, + {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1" -packaging = ">=20" +iniconfig = ">=1.0.1" +packaging = ">=22" pluggy = ">=1.5,<2" pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} @@ -899,14 +895,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, - {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, + {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, ] [package.dependencies] @@ -919,14 +915,54 @@ testing = ["process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytokens" -version = "0.2.0" -description = "A Fast, spec compliant Python 3.13+ tokenizer that runs on older Pythons." +version = "0.4.1" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8"}, - {file = "pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43"}, + {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, + {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, + {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, + {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, + {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, + {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, + {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, + {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, + {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, + {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, + {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, + {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, + {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, + {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, + {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, + {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, + {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, + {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, + {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, + {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, + {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, + {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, + {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, + {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, + {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, + {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, ] [package.extras] @@ -1017,139 +1053,138 @@ files = [ [[package]] name = "regex" -version = "2025.10.23" +version = "2026.5.9" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "regex-2025.10.23-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:17bbcde374bef1c5fad9b131f0e28a6a24856dd90368d8c0201e2b5a69533daa"}, - {file = "regex-2025.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4e10434279cc8567f99ca6e018e9025d14f2fded2a603380b6be2090f476426"}, - {file = "regex-2025.10.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c9bb421cbe7012c744a5a56cf4d6c80829c72edb1a2991677299c988d6339c8"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275cd1c2ed8c4a78ebfa489618d7aee762e8b4732da73573c3e38236ec5f65de"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b426ae7952f3dc1e73a86056d520bd4e5f021397484a6835902fc5648bcacce"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5cdaf5b6d37c7da1967dbe729d819461aab6a98a072feef65bbcff0a6e60649"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bfeff0b08f296ab28b4332a7e03ca31c437ee78b541ebc874bbf540e5932f8d"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f97236a67307b775f30a74ef722b64b38b7ab7ba3bb4a2508518a5de545459c"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be19e7de499940cd72475fb8e46ab2ecb1cf5906bebdd18a89f9329afb1df82f"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:883df76ee42d9ecb82b37ff8d01caea5895b3f49630a64d21111078bbf8ef64c"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2e9117d1d35fc2addae6281019ecc70dc21c30014b0004f657558b91c6a8f1a7"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ff1307f531a5d8cf5c20ea517254551ff0a8dc722193aab66c656c5a900ea68"}, - {file = "regex-2025.10.23-cp310-cp310-win32.whl", hash = "sha256:7888475787cbfee4a7cd32998eeffe9a28129fa44ae0f691b96cb3939183ef41"}, - {file = "regex-2025.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ec41a905908496ce4906dab20fb103c814558db1d69afc12c2f384549c17936a"}, - {file = "regex-2025.10.23-cp310-cp310-win_arm64.whl", hash = "sha256:b2b7f19a764d5e966d5a62bf2c28a8b4093cc864c6734510bdb4aeb840aec5e6"}, - {file = "regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd"}, - {file = "regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb"}, - {file = "regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd"}, - {file = "regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575"}, - {file = "regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8"}, - {file = "regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566"}, - {file = "regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc"}, - {file = "regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2"}, - {file = "regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147"}, - {file = "regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079"}, - {file = "regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842"}, - {file = "regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335"}, - {file = "regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193"}, - {file = "regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b"}, - {file = "regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87"}, - {file = "regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1"}, - {file = "regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0"}, - {file = "regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f"}, - {file = "regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044"}, - {file = "regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc"}, - {file = "regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494"}, - {file = "regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9"}, - {file = "regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6"}, - {file = "regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473"}, - {file = "regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6"}, - {file = "regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5"}, - {file = "regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec"}, - {file = "regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8"}, - {file = "regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796"}, - {file = "regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9"}, - {file = "regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422"}, - {file = "regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788"}, - {file = "regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3"}, - {file = "regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521"}, - {file = "regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741"}, - {file = "regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed"}, - {file = "regex-2025.10.23-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8d286760ee5b77fd21cf6b33cc45e0bffd1deeda59ca65b9be996f590a9828a"}, - {file = "regex-2025.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e72e3b84b170fec02193d32620a0a7060a22e52c46e45957dcd14742e0d28fb"}, - {file = "regex-2025.10.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec506e8114fa12d21616deb44800f536d6bf2e1a69253dbf611f69af92395c99"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7e481f9710e8e24228ce2c77b41db7662a3f68853395da86a292b49dadca2aa"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4663ff2fc367735ae7b90b4f0e05b25554446df4addafc76fdaacaaa0ba852b5"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0879dd3251a42d2e9b938e1e03b1e9f60de90b4d153015193f5077a376a18439"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:651c58aecbab7e97bdf8ec76298a28d2bf2b6238c099ec6bf32e6d41e2f9a9cb"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ceabc62a0e879169cd1bf066063bd6991c3e41e437628936a2ce66e0e2071c32"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bfdf4e9aa3e7b7d02fda97509b4ceeed34542361694ecc0a81db1688373ecfbd"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:92f565ff9beb9f51bc7cc8c578a7e92eb5c4576b69043a4c58cd05d73fda83c5"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:abbea548b1076eaf8635caf1071c9d86efdf0fa74abe71fca26c05a2d64cda80"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:33535dcf34f47821381e341f7b715cbd027deda4223af4d3932adcd371d3192a"}, - {file = "regex-2025.10.23-cp39-cp39-win32.whl", hash = "sha256:345c9df49a15bf6460534b104b336581bc5f35c286cac526416e7a63d389b09b"}, - {file = "regex-2025.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:f668fe1fd3358c5423355a289a4a003e58005ce829d217b828f80bd605a90145"}, - {file = "regex-2025.10.23-cp39-cp39-win_arm64.whl", hash = "sha256:07a3fd25d9074923e4d7258b551ae35ab6bdfe01904b8f0d5341c7d8b20eb18d"}, - {file = "regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26"}, + {file = "regex-2026.5.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a9e1328e17c84c1a5d22ec9f785ecef4a967fab9a42b6a8dc3bcbebd0a0c9e44"}, + {file = "regex-2026.5.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfe1ce50cbfb569d74e1e4337da6468961f31dbea55fd85aa5de59c0947a805a"}, + {file = "regex-2026.5.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15ee42209947f4ca045412eae98416317238163618ace2a8e54f99586a466733"}, + {file = "regex-2026.5.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb445ff3f725f59df8f6014edb547ee928ec7023a774f6a39a3f953038cbb2"}, + {file = "regex-2026.5.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:446ddd671e43ab535810c4b21cff7104945c701d4a14d1e6d1cd6f4e445a8bea"}, + {file = "regex-2026.5.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b92817338591505f282cf3864c145244b1edcf5381d237038df955001091538"}, + {file = "regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b8a143aca6c39b446ea8092cde25cc8fe9304d4f5fecfbc1a9dbb0282703c2"}, + {file = "regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0f03aa6898aaaac4592479821df16e68e8d0e29e903e65d8f2dfb2f19028a989"}, + {file = "regex-2026.5.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ed457d8e98ae812ed7732bef7bf78de78e834eae0372a74e23ca90ef21d910f9"}, + {file = "regex-2026.5.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71b61c5bfe1c806332defc42ad6c780b3c55f661986d7f40283a3a88274b4c00"}, + {file = "regex-2026.5.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3b1e39888c5e0c7d92cea4fc777396c4a90363b05de75d02eb459a4752200808"}, + {file = "regex-2026.5.9-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6ba42b2e7e7f46cf68cc6a5ca36fa07959f9bbd9c6bdcc47b6ee76549a590248"}, + {file = "regex-2026.5.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c010eb8caca74bdb40c07498d7ece26b4428fd3f04aa8a72c9ac6f79e8faaac6"}, + {file = "regex-2026.5.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a6a563446a41adc451393dc6b8e6ad87979efaee3c8738690a8d1b08ebead1b4"}, + {file = "regex-2026.5.9-cp310-cp310-win32.whl", hash = "sha256:954cc214c04663ee6d266fc61739cad83054683048de65c5bd1d640ad28098ac"}, + {file = "regex-2026.5.9-cp310-cp310-win_amd64.whl", hash = "sha256:b310768746dd314ea6e2ff4cc89ef215426813396ff4e94ee8e6f7096c8b6e03"}, + {file = "regex-2026.5.9-cp310-cp310-win_arm64.whl", hash = "sha256:19c16ceb4a267a8789e25733e583983eeab9f0f8664e66b0bd1c5d21f14c2d4b"}, + {file = "regex-2026.5.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48"}, + {file = "regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8"}, + {file = "regex-2026.5.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555"}, + {file = "regex-2026.5.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919"}, + {file = "regex-2026.5.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451"}, + {file = "regex-2026.5.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c"}, + {file = "regex-2026.5.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc"}, + {file = "regex-2026.5.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d"}, + {file = "regex-2026.5.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9"}, + {file = "regex-2026.5.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2"}, + {file = "regex-2026.5.9-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf"}, + {file = "regex-2026.5.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611"}, + {file = "regex-2026.5.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c"}, + {file = "regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994"}, + {file = "regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b"}, + {file = "regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046"}, + {file = "regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06"}, + {file = "regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6"}, + {file = "regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225"}, + {file = "regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0"}, + {file = "regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107"}, + {file = "regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309"}, + {file = "regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8"}, + {file = "regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66"}, + {file = "regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026"}, + {file = "regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962"}, + {file = "regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621"}, + {file = "regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d"}, + {file = "regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce"}, + {file = "regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e"}, + {file = "regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e"}, + {file = "regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070"}, + {file = "regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb"}, + {file = "regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f"}, + {file = "regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c"}, + {file = "regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed"}, + {file = "regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020"}, + {file = "regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2"}, + {file = "regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2"}, + {file = "regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04"}, + {file = "regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c"}, + {file = "regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f"}, + {file = "regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8"}, + {file = "regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6"}, + {file = "regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21"}, + {file = "regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127"}, + {file = "regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca"}, + {file = "regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6"}, + {file = "regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3"}, + {file = "regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6"}, + {file = "regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff"}, + {file = "regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88"}, + {file = "regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178"}, + {file = "regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100"}, + {file = "regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e"}, + {file = "regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2"}, + {file = "regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b"}, + {file = "regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e"}, + {file = "regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041"}, + {file = "regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0"}, + {file = "regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081"}, + {file = "regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5"}, + {file = "regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4"}, + {file = "regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de"}, + {file = "regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a"}, + {file = "regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4"}, + {file = "regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c"}, + {file = "regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9"}, + {file = "regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af"}, + {file = "regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0"}, + {file = "regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4"}, + {file = "regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf"}, + {file = "regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346"}, + {file = "regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676"}, + {file = "regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14"}, + {file = "regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd"}, + {file = "regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e"}, + {file = "regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad"}, + {file = "regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763"}, + {file = "regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372"}, + {file = "regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499"}, + {file = "regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1"}, + {file = "regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d"}, + {file = "regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c"}, + {file = "regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5"}, + {file = "regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20"}, + {file = "regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0"}, + {file = "regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d"}, + {file = "regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b"}, + {file = "regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a"}, + {file = "regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415"}, + {file = "regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2"}, + {file = "regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41"}, + {file = "regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58"}, + {file = "regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77"}, + {file = "regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa"}, + {file = "regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270"}, ] [[package]] name = "rich" -version = "14.2.0" +version = "15.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.9.0" groups = ["main"] files = [ - {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, - {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, + {file = "rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb"}, + {file = "rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36"}, ] [package.dependencies] @@ -1177,35 +1212,35 @@ pbr = "*" [[package]] name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +version = "82.0.1" +description = "Most extensible Python build backend with support for C/C++ extension modules" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, + {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, + {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "tabulate" -version = "0.9.0" +version = "0.10.0" description = "Pretty-print tabular data" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, + {file = "tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3"}, + {file = "tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d"}, ] [package.extras] @@ -1213,56 +1248,61 @@ widechars = ["wcwidth"] [[package]] name = "tomli" -version = "2.3.0" +version = "2.4.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main", "dev", "test"] files = [ - {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, - {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, - {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, - {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, - {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, - {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, - {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, - {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, - {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, - {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, - {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, - {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, - {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, - {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] -markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.11\"", test = "python_full_version <= \"3.11.0a6\""} +markers = {main = "python_version == \"3.10\"", dev = "python_version == \"3.10\"", test = "python_full_version <= \"3.11.0a6\""} [[package]] name = "typing-extensions" @@ -1275,7 +1315,7 @@ files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] -markers = {dev = "python_version < \"3.11\"", test = "python_version < \"3.11\""} +markers = {dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} [[package]] name = "typing-inspection" @@ -1294,5 +1334,5 @@ typing-extensions = ">=4.12.0" [metadata] lock-version = "2.1" -python-versions = ">=3.9,<4.0.0" -content-hash = "7b1d6d8c73a100a5277f8855a89fcc75f65555d5f9d56d5af6ccd20d5fcd167a" +python-versions = ">=3.10,<4.0.0" +content-hash = "5002680d75a020a106d69a8032dd310c2080f37dea756eecf064f28c37083c55" diff --git a/pyproject.toml b/pyproject.toml index b28e4b2..c8fa9f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "deepsecrets" -version = "1.4.0" +version = "2.0.0" description = "A better tool for secrets search" license = { text = "MIT" } readme = "README.md" @@ -15,7 +15,7 @@ maintainers = [ keywords = ["security", "secrets", "credentials", "scanning", "appsec", "code", "search"] packages = [{include = "deepsecrets"}] -requires-python = ">=3.9,<4.0.0" +requires-python = ">=3.10,<4.0.0" classifiers = [ "Programming Language :: Python :: 3", @@ -27,19 +27,19 @@ classifiers = [ ] dependencies = [ - "pydantic (==2.12.3)", + "pydantic (==2.13.4)", "pyyaml (==6.0.3)", - "pygments (==2.19.2)", + "pygments (==2.20.0)", "ordered-set (==4.1.0)", "dotwiz (==0.4.0)", - "mmh3 (==5.2.0)", - "regex (==2025.10.23)", + "mmh3 (==5.2.1)", + "regex (==2026.5.9)", "jsx-lexer == 2.0.1", - "aenum (==3.1.16)", + "aenum (==3.1.17)", "puppetparser == 0.2.14", "sarif-om == 1.0.4", "jschema-to-python == 1.2.3", - "rich (==14.2.0)", + "rich (==15.0.0)", "nostril @ git+https://github.com/casics/nostril.git", ] @@ -53,12 +53,12 @@ deepsecrets = "deepsecrets:__main__.runnable_entrypoint" [tool.poetry.group.test.dependencies] -pytest = "8.4.2" -coverage = "7.10.7" -pytest-cov = "7.0.0" +pytest = "9.0.3" +coverage = "7.14.0" +pytest-cov = "7.1.0" [tool.poetry.group.dev.dependencies] -black = "^25.1.0" +black = "^26.5.1" [build-system] requires = ["poetry-core"] diff --git a/tests/case_helpers.py b/tests/case_helpers.py index 86efe8c..15463c9 100644 --- a/tests/case_helpers.py +++ b/tests/case_helpers.py @@ -12,7 +12,7 @@ from deepsecrets.core.utils.fs import get_path_inside_package -def run(file, engine, tokenizer): +def run(file, engine, tokenizer: Tokenizer): fa = FileAnalyzer(file) fa.add_engine(engine, [tokenizer]) findings: List[Finding] = fa.process() diff --git a/tests/core/cohesive/test_specific_cases.py b/tests/core/cohesive/test_specific_cases.py index f60f2ef..a7a88d3 100644 --- a/tests/core/cohesive/test_specific_cases.py +++ b/tests/core/cohesive/test_specific_cases.py @@ -18,7 +18,7 @@ def test_inline_yaml_inside_yaml_inside_markdown(file: File): @pytest.mark.fixture_file_path('cases/inline_yaml_inside_yaml.yaml') def test_inline_yaml_inside_yaml(file: File, lexer_tokenizer: LexerTokenizer): vars, _, tokens = variable_detection_case(lexer_tokenizer, file) - assert len(vars) == 7 + assert len(vars) == 9 @pytest.mark.fixture_file_path('cases/code_in_markdown_with_lang_labels.md') @@ -54,3 +54,13 @@ def test_4_json(file: File): def test_5_jsinhtml(file: File): findings, tokens, variables = semantic_case(file) assert len(findings) == 1 + + +@pytest.mark.fixture_file_path('6.json') +def test_6_json(file: File, full_content_tokenizer: FullContentTokenizer, regex_engine: RegexEngine): + findings, tokens, variables = regex_case( + tokenizer=full_content_tokenizer, + engine=regex_engine, + file=file, + ) + assert len(findings) == 1 diff --git a/tests/core/engines/regex/test_regex.py b/tests/core/engines/regex/test_regex.py index 5586bda..ee3f817 100644 --- a/tests/core/engines/regex/test_regex.py +++ b/tests/core/engines/regex/test_regex.py @@ -14,7 +14,7 @@ def test_1(file: File, regex_engine: RegexEngine): file=file, ) - assert len(findings) == 11 + assert len(findings) == 13 assert findings[0].final_rule.id == 'S0' assert findings[1].final_rule.id == 'S0' assert findings[2].final_rule.id == 'S1' diff --git a/tests/core/engines/semantic/test_semantic.py b/tests/core/engines/semantic/test_semantic.py index e56309f..a4164ff 100644 --- a/tests/core/engines/semantic/test_semantic.py +++ b/tests/core/engines/semantic/test_semantic.py @@ -81,7 +81,7 @@ def test_html_3(file: File): @pytest.mark.fixture_file_path('cases/code_in_markdown.md') def test_ec_code_in_markdown(file: File): findings, tokens, vars = semantic_case(file) - assert len(findings) == 2 + assert len(findings) == 1 @pytest.mark.fixture_file_path('8.go') @@ -107,3 +107,9 @@ def test_conf_3(file: File): def test_with_cheap_var_search(file: File): findings, tokens, vars = semantic_case_with_cheap_var_search(file) assert len(findings) == 2 + + +@pytest.mark.fixture_file_path('5.py') +def test_5(file: File): + findings, tokens, vars = semantic_case(file) + assert len(findings) == 1 diff --git a/tests/core/helpers/test_variable_evaluator.py b/tests/core/helpers/test_variable_evaluator.py index acafab9..7042b8d 100644 --- a/tests/core/helpers/test_variable_evaluator.py +++ b/tests/core/helpers/test_variable_evaluator.py @@ -2,6 +2,16 @@ from deepsecrets.core.model.semantic import Context +def test_0(variable_scoring_rules): + ctx = Context(name='SERVICE_OAUTH', value='aaa', filepath='good') + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + + assert result.is_dangerous is False + assert 'SEM_VAR_VALUE_LENGTH' in result.matched_rules + assert result.export_confidence == 0 + + def test_1(variable_scoring_rules): ctx = Context(name='SERVICE_OAUTH', value='vhpn6mbsvhpn6mbsvhpn6mbsvhpn6mbs', filepath='good') ve = VariableEvaluator(variable_scoring_rules) @@ -195,3 +205,27 @@ def test_16(variable_scoring_rules): result: EvaluationResult = ve.evaluate(ctx) assert result.is_dangerous is True assert result.export_confidence >= 8 + + +def test_17(variable_scoring_rules): + ctx = Context( + name='db_pass', + value='nacc6opq', + filepath='1.py', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 6 + + +def test_18(variable_scoring_rules): + ctx = Context( + name='Mytoken', + value='13572850-V1bz11ZrIGoGpqCOJw8mhwBfoswbVjWCA', + filepath='ssifier.md', + ) + ve = VariableEvaluator(variable_scoring_rules) + result: EvaluationResult = ve.evaluate(ctx) + assert result.is_dangerous is True + assert result.export_confidence >= 6 diff --git a/tests/core/model/test_variable_context.py b/tests/core/model/test_variable_context.py index f0a5bbc..dd69466 100644 --- a/tests/core/model/test_variable_context.py +++ b/tests/core/model/test_variable_context.py @@ -55,3 +55,11 @@ def test_7_context(): assert ctx.name_normalized == 'datasitekey' assert ctx.name_parts == ['data', 'sitekey'] assert ctx.name_spaced == 'data sitekey' + + +def test_8_context(): + ctx = Context(name='siteKey', value='', filepath='') + assert ctx.name == 'siteKey' + assert ctx.name_normalized == 'sitekey' + assert ctx.name_parts == ['site', 'key'] + assert ctx.name_spaced == 'site key' diff --git a/tests/core/tokenizers/lexer/variable_detection/test_php.py b/tests/core/tokenizers/lexer/variable_detection/test_php.py index 76fc366..53cd1c6 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_php.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_php.py @@ -5,13 +5,13 @@ from tests.case_helpers import variable_detection_case -@pytest.fixture(scope='module') -def file_php_1(): - path = 'tests/fixtures/1.php' - return File(path=path, relative_path=path) - - @pytest.mark.fixture_file_path('1.php') def test_1(file: File, lexer_tokenizer: LexerTokenizer): variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 12 + + +@pytest.mark.fixture_file_path('2.php') +def test_2(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 1 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py b/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py index 0c5a40c..0b55649 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_r_rd.py @@ -8,6 +8,7 @@ @pytest.mark.fixture_file_path( 'problem_files/dmpe-rbitly_5e7b14925d70ccc7f59a05e4b5a398c4acce6e76_man-link_Metrics_EncodersByCount.Rd' ) +@pytest.mark.skip() def test_1(file: File, lexer_tokenizer: LexerTokenizer): variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 1 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_ruby.py b/tests/core/tokenizers/lexer/variable_detection/test_ruby.py new file mode 100644 index 0000000..bf99210 --- /dev/null +++ b/tests/core/tokenizers/lexer/variable_detection/test_ruby.py @@ -0,0 +1,10 @@ +import pytest + +from deepsecrets.core.tokenizers.lexer import LexerTokenizer +from tests.case_helpers import variable_detection_case + + +@pytest.mark.fixture_file_path('1.rb') +def test_1(file, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 3 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_sh.py b/tests/core/tokenizers/lexer/variable_detection/test_sh.py index e4253c5..721a950 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_sh.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_sh.py @@ -9,3 +9,9 @@ def test_1(file: File, lexer_tokenizer: LexerTokenizer): variables, _, _ = variable_detection_case(lexer_tokenizer, file) assert len(variables) == 7 + + +@pytest.mark.fixture_file_path('3.sh') +def test_2(file: File, lexer_tokenizer: LexerTokenizer): + variables, _, _ = variable_detection_case(lexer_tokenizer, file) + assert len(variables) == 31 diff --git a/tests/core/tokenizers/test_cheap_var_search.py b/tests/core/tokenizers/test_cheap_var_search.py index d2418d3..0a8e462 100644 --- a/tests/core/tokenizers/test_cheap_var_search.py +++ b/tests/core/tokenizers/test_cheap_var_search.py @@ -6,5 +6,13 @@ @pytest.mark.fixture_file_path('cheap_var_detector_cases.txt') def test_1(file: File, cheap_var_search_tokenizer: CheapVarSearchTokenizer): - tokens = cheap_var_search_tokenizer.tokenize(file=file) - assert len(tokens) == 4 + _ = cheap_var_search_tokenizer.tokenize(file=file) + variables = cheap_var_search_tokenizer.get_variables() + assert len(variables) == 15 + + +@pytest.mark.fixture_file_path('6.json') +def test_2(file: File, cheap_var_search_tokenizer: CheapVarSearchTokenizer): + _ = cheap_var_search_tokenizer.tokenize(file=file) + variables = cheap_var_search_tokenizer.get_variables() + assert len(variables) == 24 diff --git a/tests/fixtures/1.rb b/tests/fixtures/1.rb new file mode 100644 index 0000000..86a2b1b --- /dev/null +++ b/tests/fixtures/1.rb @@ -0,0 +1,18 @@ +require 'test' +client = Test::Client.new(api_key: 'ccc8af938590f84a77aec8ce04ec') + + "ccc8af938590f84a77aec8ce04ec", + + :to_zip => 90002, + + :line_items => [ + { + :id => 1, + :quantity => 1, + :api_token => "ccc8af938590f84a77aec8ce04e", + :product_tax_code => nil, + + } + ] +}> diff --git a/tests/fixtures/2.php b/tests/fixtures/2.php new file mode 100644 index 0000000..7cc6651 --- /dev/null +++ b/tests/fixtures/2.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/tests/fixtures/3.sh b/tests/fixtures/3.sh new file mode 100644 index 0000000..7471c95 --- /dev/null +++ b/tests/fixtures/3.sh @@ -0,0 +1,15 @@ +#!/bin/bash +reset; bash -x ./make-lxd-node.sh xen --map-host-folder /media/fdsfdsa/other/spack-mirror /media/ffdsadsa/other/spack-mirror +reset;./execute-script-remotely.sh prepare_spack.sh --ssh-address adam@10.0.19.68 -- --spack-mirror /media/fdsafdsa/other/spack-mirror --pre-install jq +reset; ./execute-script-remotely.sh IMGW-VPN.sh --ssh-address 10.51.192.109 -- https://fdsafdsa@vpn.fdsafdsa.fdsa --password AeXw13589123 + + +reset; bash -x ./deploy_IMGW_CI.sh xen --vpn-password jkfsadjhfdsaijkfhdsa;kfdsa --vpn-username aryczkowski --git-address git@git.imgw.ad:fdsafda/propoze.git --git-branch CEfused --ssh-key-path /home/adam/tmp/puppet-bootstrap/id_ed25519 --host-repo-path /home/adam/tmp/all1 --guest-repo-path /home/adam/tmp/propoze --preinstall-spack boost --repo-path /media/adam-minipc/other/debs --spack-mirror /media/adam-minipc/other/spack-mirror --source-dir tests/mpdata-gauge + + +reset; ./make-lxd-node.sh ci-runner --private-key-path /home/adam/tmp/puppet-bootstrap/id_ed25519 --map-host-folder /media/fdsafdas/other /media/fdsaad/other +reset; ./execute-script-remotely.sh prepare_spack.sh --lxc-name ci-runner --user adam -- --spack-mirror /media/fdsafads/other/spack-mirror --pre-install cmake +reset; ./execute-script-remotely.sh prepare_for_imgw.sh --lxc-name ci-runner --user adam --step-debug -- --gcc6 +reset; ./execute-script-remotely.sh prepare_GitLab_CI_runner.sh --lxc-name ci-runner --user adam --step-debug -- --user adam --gitlab-server https://git1.imgw.pl --gitlab-token hfjkdahsflidsahfdsaoijfosakfnas --runner-name potworny + + diff --git a/tests/fixtures/6.json b/tests/fixtures/6.json new file mode 100644 index 0000000..00816a6 --- /dev/null +++ b/tests/fixtures/6.json @@ -0,0 +1,18 @@ +[ { + "id_knwKB": 15, + "m_key": "01.10.Cr", + "m_value": "Communication, education, history, and philosophy: Announcements, news, and awards" + }, + { + "id_knwKB": 15, + "m_value": "Communication, education, history, and philosophy: Announcements, news, and awards", + "id_knwKB2": 15, + "m_key": "01.10.Cr" + }, + { + "password": "fdjsakfbdajsklfkdsajknfjkdsafnmdklasmfds" + }, + { + "ResponseBody": "{\r\n \"status\": {\r\n \"mapProgress\": 0.0,\r\n \"reduceProgress\": 0.0,\r\n \"cleanupProgress\": 0.0,\r\n \"setupProgress\": 1.0,\r\n \"runState\": 1,\r\n \"startTime\": 1460774301631,\r\n \"queue\": \"default\",\r\n \"priority\": \"NORMAL\",\r\n \"schedulingInfo\": \"NA\",\r\n \"failureInfo\": \"NA\",\r\n \"jobACLs\": {},\r\n \"jobName\": \"TempletonControllerJob\",\r\n \"jobFile\": \"wasb://pattipakawin33@pattipakastorageaccount.blob.core.windows.net/user/admin/.staging/job_1460573144061_0069/job.xml\",\r\n \"finishTime\": 0,\r\n \"historyFile\": \"\",\r\n \"trackingUrl\": \"http://headnodehost:9014/proxy/application_1460573144061_0069/\",\r\n \"numUsedSlots\": 0,\r\n \"numReservedSlots\": 0,\r\n \"usedMem\": 0,\r\n \"reservedMem\": 0,\r\n \"neededMem\": 0,\r\n \"jobPriority\": \"NORMAL\",\r\n \"jobID\": {\r\n \"id\": 69,\r\n \"jtIdentifier\": \"1460573144061\"\r\n },\r\n \"username\": \"hdp\",\r\n \"jobId\": \"job_1460573144061_0069\",\r\n \"state\": \"RUNNING\",\r\n \"jobComplete\": false,\r\n \"retired\": false,\r\n \"uber\": false\r\n },\r\n \"profile\": {\r\n \"user\": \"hdp\",\r\n \"jobFile\": \"wasb://pattipakawin33@pattipakastorageaccount.blob.core.windows.net/user/admin/.staging/job_1460573144061_0069/job.xml\",\r\n \"url\": \"http://headnodehost:9014/proxy/application_1460573144061_0069/\",\r\n \"queueName\": \"default\",\r\n \"jobID\": {\r\n \"id\": 69,\r\n \"jtIdentifier\": \"1460573144061\"\r\n },\r\n \"jobName\": \"TempletonControllerJob\",\r\n \"jobId\": \"job_1460573144061_0069\"\r\n },\r\n \"id\": \"job_1460573144061_0069\",\r\n \"parentId\": null,\r\n \"percentComplete\": \"map 0% reduce 0%\",\r\n \"exitValue\": null,\r\n \"user\": \"hdp\",\r\n \"callback\": null,\r\n \"completed\": null,\r\n \"userargs\": {\r\n \"statusdir\": \"SqoopStatus\",\r\n \"optionsfile\": null,\r\n \"files\": null,\r\n \"enablelog\": \"false\",\r\n \"user.name\": \"admin\",\r\n \"callback\": null,\r\n \"command\": \"import --connect jdbc:sqlserver://hdinsightjobtest.database.windows.net:1433;database=HdInsightJobTest;user=jobtest@hdinsightjobtest;password=Password@1; --table dept --warehouse-dir /user/admin/sqoop/1573600b-9f05-414c-beac-03e29667ae63 --hive-import -m 1 --hive-table dept5b2d22b4cb8e4043a7632776970a43fb\",\r\n \"enablejobreconnect\": null,\r\n \"libdir\": null\r\n },\r\n \"msg\": null\r\n}", + } +] \ No newline at end of file diff --git a/tests/fixtures/cheap_var_detector_cases.txt b/tests/fixtures/cheap_var_detector_cases.txt index 1e01769..6cbd93f 100644 --- a/tests/fixtures/cheap_var_detector_cases.txt +++ b/tests/fixtures/cheap_var_detector_cases.txt @@ -1,8 +1,20 @@ -client_secret: '5846d428b5340812b76c9637eceaee979340b922' -client_secret = '5846d428b5340812b76c9637eceaee979340b922' +client_secret: '1846d428b5340812b76c9637eceaee979340b922' +client_secret = '2846d428b5340812b76c9637eceaee979340b922' -"client_secret": "5846d428b5340812b76c9637eceaee979340b922" -'client_secret'= '5846d428b5340812b76c9637eceaee979340b922' +"client_secret": "3846d428b5340812b76c9637eceaee979340b922" +'client_secret'= '4846d428b5340812b76c9637eceaee979340b922' https://www.youtube.com/watch?v=HHi19zvnyGM&key=5846d428b5340812b76c9637eceaee979340b922 -https://www.youtube.com/watch?key=5846d428b5340812b76c9637eceaee979340b922 \ No newline at end of file +https://www.youtube.com/watch?key=6846d428b5340812b76c9637eceaee979340b922 + +# bitly_token <- bitly_auth(key = "7kjfkjdsabfnladksjfdsalkfmdklsa", secret = "8njdabfdsajkfndsafmhbfjdasbfndsa") +# saveRDS(bitly_token, file = "tests/bitly_local_token.rds") + +
+ + +https://www.youtube.com/watch?key=9846d428b5340812b76c9637eceaee979340b922&anotherSecretKey=10fdaskfdsajhfd5846d428b5340812b76c9637eceaee979340b922&andOnceAgainPassword=fmdksafbdsjafbdksjafds + + +#Sane Reports +[![blabla](https://blabla.com/gh/fdsaf/fddfas.svg?style=svg&circle-token=11fdaskfdsajhfd5846d428b5340812b76c9637eceaee979340b922)](https://blabla.com/gh/fdsa/sane-reports) diff --git a/tests/fixtures/regex_checks.txt b/tests/fixtures/regex_checks.txt index 36f7b1f..29ba7d5 100644 --- a/tests/fixtures/regex_checks.txt +++ b/tests/fixtures/regex_checks.txt @@ -79,4 +79,18 @@ http://user:*****@localhost:3001/@whatever/path/more eing prematurely saved to database\n ([230e4a11](https://gitlab-ci-token:ridCNWnbTpavfVuJvWmS@git.sickrage.ca/SiCKRAGE/sickrage/commit/230e4a11b46145fc881186a3e -5Nprr13Wue677z5uvvlmLrvsMizL4rbbbuOVr3wlhw4dItyh2t8f \ No newline at end of file +5Nprr13Wue677z5uvvlmLrvsMizL4rbbbuOVr3wlhw4dItyh2t8f + + +# not match +pk_live_xxxxxxxxxxxxxxxxxxxxxxxx +sk_live_xxxxxxxxxxxxxxxxxxxxxxxx +sk_test_AAAAAAAAAAAAAAAAAAAAAAAA +pk_live_YYYYYYYYYYYYYYYYYYYYYYYYYY + +# match +pk_live_YYYYYYYYYYYYYfdsafdsafsd +pk_live_sbI2q15f2zAg8Rfqiw8xmh9q +sk_live_XCArxr2KBvWegUqwqgojiqK200qT465n +pk_test_XCArxr2KBvWegUqwqgojiqK200qT465n +sk_test_XCArxr2KBvWegUqwqgojiqK200qT465n \ No newline at end of file diff --git a/tests/generic_fixture_scans/test_run_full_scan.py b/tests/generic_fixture_scans/test_run_full_scan.py index 5fe8e6d..1bb8240 100644 --- a/tests/generic_fixture_scans/test_run_full_scan.py +++ b/tests/generic_fixture_scans/test_run_full_scan.py @@ -1,33 +1,30 @@ -from unittest.mock import Mock +from typing import List + import pytest -from deepsecrets.config import Config, Output -from deepsecrets.core.engines.regex import RegexEngine -from deepsecrets.core.engines.semantic import SemanticEngine -from deepsecrets.core.rulesets.false_findings import FalseFindingsBuilder -from deepsecrets.core.rulesets.regex import RegexRulesetBuilder -from deepsecrets.scan_modes.cli import CliScanMode +from deepsecrets.cli import DeepSecretsCliTool -@pytest.fixture() -def config() -> Config: - config = None - config = Config() - config.set_workdir('tests/fixtures') - config.engines.append(RegexEngine) - config.engines.append(SemanticEngine) - config.add_ruleset(RegexRulesetBuilder, ['tests/fixtures/regexes.json']) - config.add_ruleset(FalseFindingsBuilder, ['tests/fixtures/false_findings.json']) - config.output = Output(type='json', path='tests/1.json') - return config +@pytest.fixture(scope='module') +def args(): + return [ + '', + '--target-dir', + '/app/tests/fixtures/', + '--outfile', + './fdsafad.json', + '--outformat', + 'dojo-sarif', + '--benchmarking-mode', + '--process-count', + '1', + ] -def test_everything(config: Config) -> None: - mode = CliScanMode(config=config) - mode.progress_bar = Mock() - mode.progress_bar.add_task.return_value = 0 - mode.progress_bar.task_ids = [] - findings, errors, timings = mode.run() +def test_everything(args: List[str]) -> None: + tool = DeepSecretsCliTool(args) + tool.parse_arguments() + findings, errors, timings, _ = tool.start() detections = [finding.detection for finding in findings] assert 'bAicxJVa5uVY7MjDlapthw' in detections diff --git a/tests/output/test_sarif.py b/tests/output/test_sarif.py index f570f66..d2e580b 100644 --- a/tests/output/test_sarif.py +++ b/tests/output/test_sarif.py @@ -33,7 +33,7 @@ def test_dojo_sarif(config: Config) -> None: findings = [] for file in mode.filepaths[:10]: - pfar = mode._per_file_analyzer(mode.analyzer_bundle(), file) + pfar = mode._per_file_analyzer(mode.analyzer_bundle(), file, task_reporter=MagicMock()) findings.extend(pfar.findings) sarif_response = to_json( diff --git a/tests/scan_modes/test_cli_scan_mode.py b/tests/scan_modes/test_cli_scan_mode.py index c9c7103..aa37906 100644 --- a/tests/scan_modes/test_cli_scan_mode.py +++ b/tests/scan_modes/test_cli_scan_mode.py @@ -16,6 +16,7 @@ def config() -> Config: config = Config() config.set_workdir('tests/fixtures') config.engines.append(RegexEngine) + config.add_ruleset(RegexRulesetBuilder, ['tests/fixtures/regexes.json']) config.add_ruleset(FalseFindingsBuilder, ['tests/fixtures/false_findings.json']) config.output = Output(type='json', path='tests/1.json') @@ -38,11 +39,11 @@ def test_cli_scan_mode(config: Config) -> None: for file in mode.filepaths: findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file, 0, {}).findings) - assert len(findings) == 3 + assert len(findings) == 6 # checking through the 'run' method # false findings checked at the end findings = [] findings = mode.run() - assert len(findings) == 2 + assert len(findings) == 3 From 5f333c0f438ba6fbc4b14c2ef561a9cdf7f497d6 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 12:45:02 +0300 Subject: [PATCH 06/20] 2.0.0 RC3 --- .gitignore | 6 +- .vscode/launch.json | 175 +++++++++++++++++- deepsecrets/core/engines/semantic.py | 8 +- deepsecrets/core/model/finding.py | 4 + deepsecrets/core/model/response/dojo_sarif.py | 148 +++++++++------ deepsecrets/core/model/rules/rule.py | 13 +- deepsecrets/core/model/semantic.py | 1 + deepsecrets/core/rulesets/hashed_secrets.py | 2 +- .../helpers/single_token_improver.py | 34 +++- deepsecrets/rules/regexes.json | 56 +++--- tests/cli/test_cli.py | 2 +- tests/core/cohesive/test_specific_cases.py | 2 +- tests/core/engines/semantic/test_semantic.py | 17 +- .../lexer/variable_detection/test_php.py | 2 +- tests/fixtures/1.php | 2 +- tests/output/test_sarif.py | 2 +- tests/scan_modes/test_cli_scan_mode.py | 2 +- 17 files changed, 364 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index 5f1650a..088ce28 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,8 @@ doc.html playground/ .mypy* *.db -pytest* \ No newline at end of file +pytest* + +/benchmarker/ +/tests/fixtures/problem_files/ +/*.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 63a8033..38d3b2f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,23 +10,109 @@ "console": "integratedTerminal", "justMyCode": true, "env": { - "PYTHONPATH": "/app/deepsecrets/" + "PYTHONPATH": "/app/" } }, { - "name": "Python: Module", + "name": "Python: Benchmark", "type": "python", "request": "launch", "module": "deepsecrets", "args": [ + "--target-dir", + "/secretbench/bench/buckets/bucket_0", + "--outfile", + "./bucket_0.json", + "--max-file-size", "0", + + "--disable-masking", + "--excluded-paths", "disable", + "--outformat", "sarif" + ], + "justMyCode": false + }, + { + "name": "Python: Problems Benchmark", + "type": "python", + "request": "launch", + "module": "deepsecrets", + "args": [ + "--outfile", + "test.json", + "--target-dir", + "/app/tests/fixtures/edge_cases", + "--outfile", + "./fdsafad.json", + "--outformat", "sarif", + "--process-count", "1", + "--max-file-size", "0", + "--disable-masking", + "--excluded-paths", "disable" + ], + "justMyCode": false + }, + + { + "name": "Python: Oneshot testing", + "type": "python", + "request": "launch", + "module": "deepsecrets", + "args": [ + "--outfile", + "test.json", + "--oneshot", + "/secretbench/bench/Files/${fileBasename}", + "--target-dir", "", + "--outfile", + "./fdsafad.json", + "--outformat", "sarif", + "--process-count", "1", + "--max-file-size", "0", + "--disable-masking", + "--excluded-paths", "disable" + ], + "justMyCode": false + }, + { + "name": "Python: Tests on Fixtures", + "type": "debugpy", + "request": "launch", + "module": "deepsecrets", + "args": [ + "--outfile", + "test.json", + "--target-dir", + "./tests/fixtures", + "--outfile", + "./fdsafad.json", + "--outformat", + "sarif", + "--process-count", "1", + "--max-file-size", "0", + "--disable-masking" + ], + "justMyCode": false + }, + + { + "name": "[PROF] Python: Tests on Fixtures", + "type": "debugpy", + "request": "launch", + "module": "viztracer", + "args": [ + "--log-multiprocessing", + "-m", + "deepsecrets", "--outfile", "test.json", "--target-dir", "./tests/fixtures", "--outfile", "./fdsafad.json", + "--outformat", + "json", "--process-count", "1", - "--max-file-size", "5000", + "--max-file-size", "0", "--disable-masking" ], "justMyCode": false @@ -37,6 +123,87 @@ "request": "launch", "program": "${file}", "justMyCode": true - } + }, + { + "name": "Verify Results", + "request": "launch", + "type": "python", + "program": "${workspaceFolder}/benchmarker/run.py", + "console": "integratedTerminal", + "args": [ + "/secretbench/secretbench.csv", + "/secretbench/bench/buckets/bucket_0", + "/secretbench/bucket_0.json", + ], + "env": { + "PYTHONPATH": "/app/" + }, + "justMyCode": false + }, + { + "name": "INCREMENTAL SCANNING", + "request": "launch", + "type": "python", + "program": "${workspaceFolder}/benchmarker/incremental.py", + "console": "integratedTerminal", + "args": [ + "/secretbench/secretbench.csv", + "/secretbench/bench/buckets", + "bucket_4", + "/secretbench/bench/verifications", + ], + "env": { + "PYTHONPATH": "/app/" + }, + "justMyCode": false + }, + + { + "name": "PER_BUCKET SCANNING", + "request": "launch", + "type": "python", + "program": "${workspaceFolder}/benchmarker/per_bucket.py", + "console": "integratedTerminal", + "args": [ + "/secretbench/secretbench.csv", + "/secretbench/bench/buckets", + "bucket_87", + "/secretbench/bench/verifications", + ], + "env": { + "PYTHONPATH": "/app/" + }, + "justMyCode": false + }, + + { + "name": "PROBLEM FILES RESCAN", + "request": "launch", + "type": "python", + "program": "${workspaceFolder}/benchmarker/per_bucket.py", + "console": "integratedTerminal", + "args": [ + "/secretbench/secretbench.csv", + "/app/tests/fixtures", + "problem_files" + ], + "env": { + "PYTHONPATH": "/app/" + }, + "justMyCode": false + }, + + + { + "name": "BIGRAMS", + "request": "launch", + "type": "python", + "program": "${workspaceFolder}/benchmarker/words_evaluator.py", + "console": "integratedTerminal", + "env": { + "PYTHONPATH": "/app/" + }, + "justMyCode": false + }, ] } \ No newline at end of file diff --git a/deepsecrets/core/engines/semantic.py b/deepsecrets/core/engines/semantic.py index a78c25d..56b7f78 100644 --- a/deepsecrets/core/engines/semantic.py +++ b/deepsecrets/core/engines/semantic.py @@ -55,7 +55,7 @@ def search(self, token: Token) -> List[Finding]: detection=token.content, start_offset=0, end_offset=len(token.content), - rules=[Rule(id='S107', name='Dangerous condition', confidence=9)], + rules=[Rule(id='S107', name='Dangerous condition', confidence=10)], ) ) @@ -83,8 +83,9 @@ def search(self, token: Token) -> List[Finding]: rules=[ Rule( id='S105', - name='Entropy+Var naming', + name='High Entropy and Variable Naming', confidence=evaluation_result.export_confidence, + is_dynamic_confidence=True, ) ], internal_score={'var': token.semantic.name} | evaluation_result.summary(), @@ -99,8 +100,9 @@ def search(self, token: Token) -> List[Finding]: rules=[ Rule( id='S106', - name='Var naming', + name='Variable Naming', confidence=evaluation_result.export_confidence, + is_dynamic_confidence=True, ) ], internal_score={'var': token.semantic.name} | evaluation_result.summary(), diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index 1e5e181..6372497 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -67,6 +67,10 @@ def get_reason(self) -> str: def get_fingerprint(self) -> str: return sha256(self.detection.encode('utf-8')).hexdigest()[23:33] + def get_fingerprint_v2(self) -> str: + base = f'{self.file.path}{self.detection}{self.start_offset}{self.end_offset}' + return sha256(base.encode('utf-8')).hexdigest() + def choose_final_rule(self) -> None: self.final_rule = sorted(self.rules, key=lambda r: r.confidence, reverse=True)[0] diff --git a/deepsecrets/core/model/response/dojo_sarif.py b/deepsecrets/core/model/response/dojo_sarif.py index a9bb618..9a4e087 100644 --- a/deepsecrets/core/model/response/dojo_sarif.py +++ b/deepsecrets/core/model/response/dojo_sarif.py @@ -1,3 +1,6 @@ +from dataclasses import dataclass +from typing import List, Set + from sarif_om import ( SarifLog, Run, @@ -13,21 +16,85 @@ Region, ) from deepsecrets.config import SCANNER_NAME, SCANNER_URL, SCANNER_VERSION -from typing import List from deepsecrets.core.model.finding import Finding from deepsecrets.core.model.response.base import BaseResponseBuilder from deepsecrets.core.model.rules.rule import Rule from deepsecrets.core.modes.iscan_mode import ScanMode - SRC_PATH_BASE_ID = 'SRCROOT' +@dataclass +class TierAwareSarifRuleMeta: + id: str + payload: dict + + def __hash__(self): + return hash(self.id) + + def __eq__(self, value: 'object') -> bool: + if not isinstance(value, TierAwareSarifRuleMeta): + return False + + if value.id != self.id: + return False + + return True + + class DojoSarifResponseBuilder(BaseResponseBuilder): report: SarifLog + def _get_tier(self, confidence: int): + confidence_tiers = { + (9, float('inf')): { + 'suffix': '-VERY-HIGH', + 'precision': 'very-high', + 'severity': '10.00', + 'label': 'Very High', + }, + (6, 9): { + 'suffix': '-HIGH', + 'precision': 'high', + 'severity': '9.70', + 'label': 'High', + }, + (3, 6): { + 'suffix': '-MEDIUM', + 'precision': 'medium', + 'severity': '9.40', + 'label': 'Medium', + }, + (float('-inf'), 3): { + 'suffix': '-LOW', + 'precision': 'low', + 'severity': '9.10', + 'label': 'Low', + }, + } + + for (start, end), value in confidence_tiers.items(): + if start <= confidence < end: + return value + + def _sarif_rule_meta_from_rule(self, rule: Rule) -> TierAwareSarifRuleMeta: + + base_rule_id = rule.id + base_description = rule.name + + tier = self._get_tier(rule.confidence) + suffix = tier.get('suffix') if rule.is_dynamic_confidence is True else '' + + return TierAwareSarifRuleMeta( + id=f'{base_rule_id}{suffix}', + payload={ + 'shortDescription': {'text': f'{base_description} ({tier.get('label')} Confidence)'}, + 'properties': {'precision': tier.get('precision'), 'security-severity': tier.get('severity')}, + }, + ) + def __init__(self) -> None: super().__init__() self.report = SarifLog( @@ -59,70 +126,38 @@ def with_current_mode(self, mode: ScanMode): ) return self - def _get_levels(self, rule: Rule): - precision = 'very-high' - security_severity = 'High' - level = 'error' - - if rule.confidence >= 9: - precision = 'very-high' - security_severity = 'High' - level = 'error' - elif 9 > rule.confidence >= 6: - precision = 'high' - security_severity = 'High' - level = 'error' - elif 6 > rule.confidence >= 3: - precision = 'medium' - security_severity = 'High' - level = 'error' - elif 3 > rule.confidence >= 0: - precision = 'low' - security_severity = 'High' - level = 'error' - - return { - 'precision': precision, - 'security_severity': security_severity, - 'level': level, - } - def _get_list_of_all_rules(self) -> List[ReportingDescriptor]: - sarif_rules = [] - for _, ruleset in self.mode.rulesets.items(): - for rule in ruleset: - sarif_rules.append(self._get_rule(rule)) - - return sarif_rules - - def _get_rule(self, rule: Rule) -> ReportingDescriptor: - levels = self._get_levels(rule) - return ReportingDescriptor( - id=rule.id, - short_description={'text': rule.name}, - full_description={'text': rule.name}, - help={'text': rule.name}, - properties={ - 'security-severity': levels.get('security_severity'), - 'precision': levels.get('precision'), - }, - default_configuration={'level': levels.get('level')}, - ) + def _convert_rules(self, rules: Set[TierAwareSarifRuleMeta]) -> List[ReportingDescriptor]: + return [ + ReportingDescriptor( + id=rule_meta.id, + short_description=rule_meta.payload.get('shortDescription'), + properties=rule_meta.payload.get('properties'), + ) + for rule_meta in rules + ] def build(self) -> SarifLog: # type: ignore - rules: set[Rule] = set() # self._get_list_of_rules() + rules: List[Rule] = list() for finding in self.findings: finding.choose_final_rule() region = self.get_region(finding=finding, masking=self.masking_enabled) context_region = self.get_context_region(finding=finding, masking=self.masking_enabled) - rules.add(finding.final_rule) + rules.append(finding.final_rule) + rule_meta = self._sarif_rule_meta_from_rule(finding.final_rule) result = Result( - rule_id=finding.final_rule.id, - message=Message(text=f'Secret in code: ({finding.final_rule.name})'), + rule_id=rule_meta.id, + level='error', + properties={ + 'confidence': finding.final_rule.confidence, + }, + message=Message( + text=f'[Confidence {finding.final_rule.confidence}/10] Secret in code: {finding.final_rule.name}' + ), locations=[ Location( physical_location=PhysicalLocation( @@ -136,7 +171,8 @@ def build(self) -> SarifLog: # type: ignore self.report.runs[0].results.append(result) - self.report.runs[0].tool.driver.rules = [self._get_rule(rule) for rule in rules] + sarif_rules = self._convert_rules(set([self._sarif_rule_meta_from_rule(rule) for rule in rules])) + self.report.runs[0].tool.driver.rules = sarif_rules return self.report def get_context_region(self, finding: Finding, masking: bool = True): @@ -166,7 +202,7 @@ def get_region(self, finding: Finding, masking: bool = True): snippet = finding.detection - if masking: + if masking is True: snippet = self._mask(snippet=snippet, detection=finding.detection) return Region( diff --git a/deepsecrets/core/model/rules/rule.py b/deepsecrets/core/model/rules/rule.py index 28822f7..00efcc6 100644 --- a/deepsecrets/core/model/rules/rule.py +++ b/deepsecrets/core/model/rules/rule.py @@ -9,21 +9,22 @@ class Rule(BaseModel): name: Optional[str] = None description: Optional[str] = None enabled: bool = Field(default=True) - confidence: int = Field(default=9) + confidence: int = Field(default=10) + is_dynamic_confidence: bool = Field(default=False) applicable_file_patterns: List[re.Pattern] = Field(default=[]) model_config = ConfigDict(arbitrary_types_allowed=True) @model_validator(mode='before') @classmethod - def fill_confidence(cls, values: Dict) -> Dict: + def fill_confidence_and_file_patterns(cls, values: Dict) -> Dict: file_patterns = values.get('applicable_file_patterns', []) if len(file_patterns) > 0: pattеrns = [re.compile(p) for p in file_patterns] values['applicable_file_patterns'] = pattеrns if values.get('confidence', None) is None and values.get('id') is not None: - values['confidence'] = 9 + values['confidence'] = 10 return values @@ -34,7 +35,7 @@ def __eq__(self, other: Any): if not isinstance(other, Rule): return False - if self.id == other.id: - return True + if self.id != other.id: + return False - return False + return True diff --git a/deepsecrets/core/model/semantic.py b/deepsecrets/core/model/semantic.py index 3b8447e..9e7ad49 100644 --- a/deepsecrets/core/model/semantic.py +++ b/deepsecrets/core/model/semantic.py @@ -63,6 +63,7 @@ def normalize_punctuation(self, string: str, split_camel_case=True): .replace('/', ' ') .replace('@', ' ') .replace(',', '') + # .replace('$', '') # TODO: Uncomment in v2.1 ) parts = [] diff --git a/deepsecrets/core/rulesets/hashed_secrets.py b/deepsecrets/core/rulesets/hashed_secrets.py index a3d6b34..e539d7e 100644 --- a/deepsecrets/core/rulesets/hashed_secrets.py +++ b/deepsecrets/core/rulesets/hashed_secrets.py @@ -38,7 +38,7 @@ def with_rules_from_file(self, file: str, compressed: bool = False) -> object: hashed_val=secret['hash'], algorithm=secret['algorithm'], token_length=secret['length'], - confidence=9, + confidence=10, ) ) diff --git a/deepsecrets/core/tokenizers/helpers/single_token_improver.py b/deepsecrets/core/tokenizers/helpers/single_token_improver.py index 22a1087..93d326d 100644 --- a/deepsecrets/core/tokenizers/helpers/single_token_improver.py +++ b/deepsecrets/core/tokenizers/helpers/single_token_improver.py @@ -3,7 +3,6 @@ from pygments.token import Token as PygmentsToken -from deepsecrets.core.model.rules.regex import RegexRule from deepsecrets.core.model.token import Token from deepsecrets.core.tokenizers.helpers.semantic.language import Language from deepsecrets.core.tokenizers.helpers.semantic.var_detection.detector import Match, RegionDetector @@ -18,6 +17,7 @@ def __init__(self, lang: Language) -> None: self.language = lang self.acc = { Language.SHELL: [self._curl_argstring_breakdown], + # Language.PHP: [self._php_variable_dollar_sign_breakdown], # TODO: Uncomment in v2.1 } def improve(self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token) -> List[Token]: @@ -33,6 +33,38 @@ def improve(self, so_far_tokens: List[Token], so_far_type_stream: str, current_t return tokens + def _php_variable_dollar_sign_breakdown( + self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token + ) -> List[Token]: + target_token_type = PygmentsToken.Name.Variable + if target_token_type not in current_token.type: + return [current_token] + + if not current_token.content.startswith('$'): + return [current_token] + + first_part = current_token.content[0] + second_part = current_token.content[1:] + + final = [] + fp_token = Token( + file=current_token.file, + content=first_part, + span=current_token.file.get_span_for_string(first_part, between=current_token.span), + ) + fp_token.set_type([PygmentsToken.Operator]) + final.append(fp_token) + + sp_token = Token( + file=current_token.file, + content=second_part, + span=current_token.file.get_span_for_string(first_part, between=current_token.span), + ) + sp_token.set_type([PygmentsToken.Name.Variable]) + final.append(sp_token) + + return [fp_token, sp_token] + def _curl_argstring_breakdown( self, so_far_tokens: List[Token], so_far_type_stream: str, current_token: Token ) -> List[Token]: diff --git a/deepsecrets/rules/regexes.json b/deepsecrets/rules/regexes.json index 6ffd66c..d6f90b6 100644 --- a/deepsecrets/rules/regexes.json +++ b/deepsecrets/rules/regexes.json @@ -2,37 +2,37 @@ { "id": "S0", "name": "Slack Token", - "confidence": 9, - "pattern": "(?=.{29,256}$)xox[abpors]-(?:[A-Za-z0-9]+-){3,4}[A-Za-z0-9]+" + "confidence": 10, + "pattern": "(?=.{20,256})\\bxox[abpors]-(?:[A-Za-z0-9]+-){1,3}[A-Za-z0-9]+\\b" }, { "id": "S1", "name": "RSA private key", - "confidence": 9, + "confidence": 10, "pattern": "-BEGIN[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\s\\S]{0,10}RSA[\\s\\S]{0,10}PRIVATE[\\s\\S]{0,10}KEY-" }, { "id": "S2", "name": "SSH (OPENSSH) private key", - "confidence": 9, + "confidence": 10, "pattern": "-BEGIN[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\S\\s]{0,10}OPENSSH[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S3", "name": "SSH (DSA) private key", - "confidence": 9, + "confidence": 10, "pattern": "-BEGIN[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\S\\s]{0,10}DSA[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S4", "name": "SSH (EC) private key", - "confidence": 9, + "confidence": 10, "pattern": "-BEGIN[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-----[\\s\\S]{50,15000}?-----END[\\S\\s]{0,10}EC[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY-" }, { "id": "S5", "name": "PGP private key block", - "confidence": 9, + "confidence": 10, "pattern": "-BEGIN[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-----[^-]([\\s\\S]{50,15000}?)[^-]-----END[\\S\\s]{0,10}PGP[\\S\\s]{0,10}PRIVATE[\\S\\s]{0,10}KEY[\\S\\s]{0,10}BLOCK-", "match_rules": { "1": { @@ -44,44 +44,44 @@ { "id": "S7", "name": "Facebook Oauth", - "confidence": 9, + "confidence": 10, "pattern": "facebook.{0,100}?['\"][0-9a-f]{32}['\"]", "enabled": false }, { "id": "S8", "name": "Twitter Oauth", - "confidence": 9, + "confidence": 10, "pattern": "twitter.{0,100}?['|\"][0-9a-zA-Z]{35,44}['|\"]" }, { "id": "S10", "name": "Google Oauth", - "confidence": 9, + "confidence": 10, "pattern": "(\"client_secret\":\"[a-zA-Z0-9-_]{24}\")" }, { "id": "S12", "name": "Heroku API Key", - "confidence": 9, + "confidence": 10, "pattern": "(?i)heroku.*?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" }, { "id": "S17", "name": "Slack Webhook", - "confidence": 9, + "confidence": 10, "pattern": "https://hooks.slack.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+" }, { "id": "S18", "name": "Google (GCP) Service-account", - "confidence": 9, + "confidence": 10, "pattern": "\"type\": \"service_account\"" }, { "id": "S19", "name": "Password in URL", - "confidence": 9, + "confidence": 10, "pattern": "://([^.\"/@]+):([^.\"/@]+)@\\S+", "target_group": 2, "match_rules": { @@ -94,7 +94,7 @@ { "id": "S20", "name": "BAuth", - "confidence": 9, + "confidence": 10, "pattern": "Basic @[a-zA-Z0-9+/]+={0,2}" }, { @@ -112,13 +112,13 @@ { "id": "S25", "name": "Slack App Token", - "confidence": 9, + "confidence": 10, "pattern": "xapp-[0-9]+-[A-Za-z0-9_]+-[0-9]+-[a-f0-9]+" }, { "id": "S26", "name": "Custom private key", - "confidence": 9, + "confidence": 10, "pattern": "-BEGIN[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-----[^-]([\\s\\S]{50,15000}?)[^-]-----END[\\S\\s]{0,10}?PRIVATE[\\S\\s]{0,10}?KEY-", "match_rules": { "1": { @@ -143,13 +143,13 @@ { "id": "S29", "name": "Ansible vault", - "confidence": 9, + "confidence": 10, "pattern": "\\$ANSIBLE_VAULT;[0-9]\\.[0-9];AES256" }, { "id": "S30", "name": "AWS MWS", - "confidence": 9, + "confidence": 10, "applicable_file_patterns": [ ".*.txt$" ], @@ -158,32 +158,32 @@ { "id": "S31", "name": "Github App Token", - "confidence": 9, + "confidence": 10, "pattern": "\\b((?:ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,255})\\b" }, { "id": "S32", "name": "Github OAuth", - "confidence": 9, + "confidence": 10, "pattern": "gho_[0-9a-zA-Z]{36}" }, { "id": "S33", "name": "Github Personal Access Token", - "confidence": 9, + "confidence": 10, "pattern": "ghp_[0-9a-zA-Z]{36}" }, { "id": "S34", "name": "Gitlab Personal Access Token", - "confidence": 9, + "confidence": 10, "pattern": "glpat-[0-9a-zA-Z\\-\\_]{20}" }, { "id": "S35", "name": "AWS Access Key ID", - "confidence": 9, + "confidence": 10, "pattern": "\\b(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16,17}\\b", "case_sensitive": true }, @@ -191,7 +191,7 @@ "id": "S36", "description": "https://docs.stripe.com/keys", "name": "Stripe Secret", - "confidence": 9, + "confidence": 10, "pattern": "\\b(?:pk|sk|rk)_(?:test|live)_([0-9a-zA-Z]{24,45})\\b", "match_rules": { "1": { @@ -210,7 +210,7 @@ { "id": "S38", "name": "Google API Key", - "confidence": 9, + "confidence": 10, "pattern": "\\bAIzaSy[A-Za-z0-9\\-_]{33}\\b" }, { @@ -222,13 +222,13 @@ { "id": "S40", "name": "LocationIQ Key", - "confidence": 9, + "confidence": 10, "pattern": "\\bpk\\.[a-zA-Z0-9]{30,32}\\b" }, { "id": "S41", "name": "SQL Server Connection String", - "confidence": 9, + "confidence": 10, "pattern": "sqlserver://(?:.*?);.*?(?:password)=(.*?)(?:;|\\s|$)", "target_group": 1 } diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index e00e54e..32aed2f 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -52,7 +52,7 @@ def test_1_cli(args_1): assert config.max_file_size == 500 assert config.output.path == './fdsafad.json' assert config.workdir_path == '/app/tests/fixtures/' - assert config.output.type == 'json' + assert config.output.type == 'sarif' # Starting release 2.0 return_code = tool.start() assert return_code != 0 diff --git a/tests/core/cohesive/test_specific_cases.py b/tests/core/cohesive/test_specific_cases.py index a7a88d3..60599b8 100644 --- a/tests/core/cohesive/test_specific_cases.py +++ b/tests/core/cohesive/test_specific_cases.py @@ -11,7 +11,7 @@ def test_inline_yaml_inside_yaml_inside_markdown(file: File): findings, tokens, variables = semantic_case(file) - assert len(variables) == 7 + assert len(variables) == 9 assert len(findings) == 1 diff --git a/tests/core/engines/semantic/test_semantic.py b/tests/core/engines/semantic/test_semantic.py index a4164ff..410f659 100644 --- a/tests/core/engines/semantic/test_semantic.py +++ b/tests/core/engines/semantic/test_semantic.py @@ -28,8 +28,8 @@ def test_json_2(file: File): assert tokens[1].semantic.name == 'accessToken' assert len(findings) == 2 - assert findings[0].rules[0].name == 'Entropy+Var naming' - assert findings[1].rules[0].name == 'Entropy+Var naming' + assert findings[0].rules[0].name == 'High Entropy and Variable Naming' + assert findings[1].rules[0].name == 'High Entropy and Variable Naming' @pytest.mark.fixture_file_path('1.toml') @@ -41,8 +41,8 @@ def test_toml_1(file: File): assert tokens[50].semantic.name == 'MATTERMOST_BOT_TOKEN' assert len(findings) == 2 - assert findings[0].rules[0].name == 'Entropy+Var naming' - assert findings[1].rules[0].name == 'Entropy+Var naming' + assert findings[0].rules[0].name == 'High Entropy and Variable Naming' + assert findings[1].rules[0].name == 'High Entropy and Variable Naming' @pytest.mark.fixture_file_path('2.toml') @@ -98,7 +98,6 @@ def test_go_1(file: File): @pytest.mark.fixture_file_path('3.conf') def test_conf_3(file: File): - # TODO: HOCON findings, tokens, vars = semantic_case(file) assert len(findings) == 1 @@ -106,10 +105,16 @@ def test_conf_3(file: File): @pytest.mark.fixture_file_path('cheap_var_detector_cases.txt') def test_with_cheap_var_search(file: File): findings, tokens, vars = semantic_case_with_cheap_var_search(file) - assert len(findings) == 2 + assert len(findings) == 11 @pytest.mark.fixture_file_path('5.py') def test_5(file: File): + findings, tokens, vars = semantic_case(file) + assert len(findings) == 3 + + +@pytest.mark.fixture_file_path('1.php') +def test_6_php(file: File): findings, tokens, vars = semantic_case(file) assert len(findings) == 1 diff --git a/tests/core/tokenizers/lexer/variable_detection/test_php.py b/tests/core/tokenizers/lexer/variable_detection/test_php.py index 53cd1c6..e55683a 100644 --- a/tests/core/tokenizers/lexer/variable_detection/test_php.py +++ b/tests/core/tokenizers/lexer/variable_detection/test_php.py @@ -8,7 +8,7 @@ @pytest.mark.fixture_file_path('1.php') def test_1(file: File, lexer_tokenizer: LexerTokenizer): variables, _, _ = variable_detection_case(lexer_tokenizer, file) - assert len(variables) == 12 + assert len(variables) == 13 @pytest.mark.fixture_file_path('2.php') diff --git a/tests/fixtures/1.php b/tests/fixtures/1.php index 30b3f33..ccab5d9 100644 --- a/tests/fixtures/1.php +++ b/tests/fixtures/1.php @@ -24,7 +24,7 @@ use Venturecraft\Revisionable\RevisionableTrait; -$txt = "Hello world!"; +$api_secret_key = "fadskfjhbryewhjahfjnasklnjfbjladksfdsafdsaf!"; $x = 5; $y = 10.5; diff --git a/tests/output/test_sarif.py b/tests/output/test_sarif.py index d2e580b..2392be4 100644 --- a/tests/output/test_sarif.py +++ b/tests/output/test_sarif.py @@ -1,4 +1,4 @@ -from unittest.mock import Mock +from unittest.mock import MagicMock, Mock from jschema_to_python.to_json import to_json import pytest diff --git a/tests/scan_modes/test_cli_scan_mode.py b/tests/scan_modes/test_cli_scan_mode.py index aa37906..864e154 100644 --- a/tests/scan_modes/test_cli_scan_mode.py +++ b/tests/scan_modes/test_cli_scan_mode.py @@ -39,7 +39,7 @@ def test_cli_scan_mode(config: Config) -> None: for file in mode.filepaths: findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file, 0, {}).findings) - assert len(findings) == 6 + assert len(findings) == 7 # checking through the 'run' method # false findings checked at the end From 46d7b2f3cb98da5cae64034b9bdaea0d8324449f Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 12:46:13 +0300 Subject: [PATCH 07/20] Delete benchmarker directory --- benchmarker/models/secretbench.py | 575 ---------------------------- benchmarker/models/verification.py | 46 --- benchmarker/per_bucket.py | 26 -- benchmarker/run_bucket_benchmark.sh | 1 - 4 files changed, 648 deletions(-) delete mode 100644 benchmarker/models/secretbench.py delete mode 100644 benchmarker/models/verification.py delete mode 100644 benchmarker/per_bucket.py delete mode 100755 benchmarker/run_bucket_benchmark.sh diff --git a/benchmarker/models/secretbench.py b/benchmarker/models/secretbench.py deleted file mode 100644 index 42aeff1..0000000 --- a/benchmarker/models/secretbench.py +++ /dev/null @@ -1,575 +0,0 @@ -import csv -from dataclasses import dataclass -from datetime import datetime -import json -import os -import sqlite3 -from typing import Dict, List - -from filelock import FileLock - -from benchmarker.models.verification import FileVerificationResult, SecretPointer -from deepsecrets.cli import DeepSecretsCliTool -from deepsecrets.core.model.file import File -from deepsecrets.core.model.finding import Finding -import pandas as pd - - -@dataclass -class Secret: - id: int - secret: str - repo_name: str - file_path: str - file_type: str - start_line: int - end_line: int - start_column: int - end_column: int - label: bool - in_url: bool - entropy: float - length: int - is_multiline: bool - file_identifier: str - comment: str - - def __hash__(self) -> int: - return self.id - - def __eq__(self, value: object) -> bool: - if not isinstance(value, Secret): - return False - - if value.file_identifier != self.file_identifier: - return False - - if value.secret != self.secret: - return False - - if value.start_line != self.start_line: - return False - - return True - - -class Comparison: - bench_secret: Secret = None - tool_finding: Secret = None - - def __init__(self, bench_secret: Secret) -> None: - self.bench_secret = bench_secret - - -prefix_all_found = 'AF' -prefix_not_all = 'NA' - - -class TestingScope: - files: set[str] - bucket_dir: str - target_dir: str - verification_dir: str - _bench_data: Dict[int, Secret] - _tool_results: List[Finding] - comparisons: List[Comparison] - verification_file: str - - verification_cache: Dict[str, FileVerificationResult] - db: sqlite3.Connection - - def _get_files_list(self, target_dir: str): - ''' - return ['arangodb-arangodb_e75b8f550387a7a4ea44a1a5ffa2e600eb645e92_3rdParty-curl-curl-7.50.3-CHANGES.0'] - ''' - files_list = os.listdir(target_dir) - return sorted(files_list, key=lambda x: os.stat(os.path.join(target_dir, x)).st_size) - return set(os.listdir(target_dir)) - - def _parse_tool_results(self, tool_results_path: str): - if tool_results_path is None: - return - - results = [] - report: dict - with open(tool_results_path) as file: - report = json.loads(file.read()) - - for result in report['runs'][0]['results']: - secret = result['locations'][0]['physicalLocation']['region']['snippet']['text'] - results.append( - Secret( - secret=secret, - file_identifier=result['locations'][0]['physicalLocation']['artifactLocation']['uri'], - start_line=result['locations'][0]['physicalLocation']['region']['startLine'], - end_line=result['locations'][0]['physicalLocation']['region']['endLine'], - start_column=result['locations'][0]['physicalLocation']['region']['startColumn'], - end_column=result['locations'][0]['physicalLocation']['region']['endColumn'], - length=len(secret), - repo_name=None, - file_type=None, - is_multiline=None, - label=None, - in_url=None, - id=None, - file_path=None, - entropy=None, - comment=None, - ) - ) - return results - - def write_verification_file(self, file, result: FileVerificationResult): - effective_prefix = prefix_all_found if result.all_found is True else prefix_not_all - path = f'{self.verification_dir}/{self.bucket_dir}/{effective_prefix}_{file}.json' - os.makedirs(f'{self.verification_dir}/{self.bucket_dir}', exist_ok=True) - - try: - with open(path, 'w+') as vf: - vf.write(result.model_dump_json(exclude_none=True, indent=2)) - except Exception: - pass - - self.update_summary_file(result) - - def update_summary_file(self, result: FileVerificationResult): - summary_file = f'{self.verification_dir}/summary.csv' - lock_file = f'{summary_file}.lock' - - lock = FileLock(lock_file) - with lock: - if os.path.exists(summary_file): - df = pd.read_csv(summary_file) - else: - # Create an empty DataFrame if file doesn't exist yet - df = pd.DataFrame( - columns=[ - 'file', - 'sb_secrets', - 'sb_valid', - 'sb_falses', - 'ds_found_sb_valids', - 'ds_found_sb_falses', - 'ds_found_extra', - 'updated_ts', - ] - ) - - df = pd.read_csv(summary_file) - data = { - 'file': result.file_identifier, - 'sb_secrets': result.sb_secrets_count, - 'sb_valid': result.sb_valid_secrets_count, - 'sb_falses': result.sb_false_secrets_count, - 'ds_found_sb_valids': len(result.found_valid_secret_ids), - 'ds_found_sb_falses': len(result.found_false_secret_ids), - 'ds_found_extra': result.extra_secrets_count, - 'updated_ts': datetime.now(), - } - - key_value = data['file'] - if key_value in df['file'].values: - mask = df['file'] == key_value - for col, value in data.items(): - df.loc[mask, col] = value - else: - df = pd.concat([df, pd.DataFrame([data])], ignore_index=True) - - df.to_csv(summary_file, index=False) - - def is_connection_active(self): - """ - Checks if a sqlite3 connection object is still active. - Returns True if active, False otherwise. - """ - if self.db is None: - return False - try: - # Attempt to create a cursor or execute a simple query - self.db.cursor() - return True - except sqlite3.ProgrammingError: - # This exception is typically raised if the connection is closed - return False - except Exception as e: - # Catch other potential exceptions if the connection is in a bad state - print(f"An unexpected error occurred: {e}") - return False - - def write_verification_db(self, file: str, result: FileVerificationResult): - - if self.is_connection_active() is False: - self.db = sqlite3.connect(os.path.join(self.verification_dir, 'db', 'verifications.db')) - - # Create a cursor object to execute SQL commands - cursor = self.db.cursor() - # Create a table - cursor.execute( - ''' - INSERT OR REPLACE - INTO FileReports - (file, sb_secrets_count, sb_valid_count, sb_falses, ds_found_sb_valids, ds_found_sb_falses, ds_found_extra, updated_ts) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ''', - ( - result.file_identifier, - result.sb_secrets_count, - result.sb_valid_secrets_count, - result.sb_false_secrets_count, - len(result.found_valid_secret_ids), - len(result.found_false_secret_ids), - result.extra_secrets_count, - int(datetime.now().timestamp()), - ), - ) - self.db.commit() - - pointer: SecretPointer - for secret_id, pointer in result.report.items(): - cursor.execute( - ''' - INSERT OR REPLACE - INTO Secrets - (id, file, content, line_number, line_offset, valid, extra, comment, ds_found) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', - ( - secret_id, - result.file_identifier, - pointer.detection, - pointer.line_number, - pointer.line_offset, - pointer.is_valid, - pointer.is_extra, - f'{pointer.internal_score} | {pointer.rule_id}', - pointer.found, - ), - ) - self.db.commit() - - def init( - self, - bucket_dir: str, - target_dir: str, - dataset_location: str, - verification_dir: str, - ): - - self._bench_data = {} - self._tool_results = [] - self.verification_dir = verification_dir - self.bucket_dir = bucket_dir - self.target_dir = target_dir - self.files = self._get_files_list(os.path.join(self.target_dir, self.bucket_dir)) - self.comparisons = [] - self.db = None - - with open(dataset_location) as file: - reader = csv.DictReader(file, delimiter=',') - for row in reader: - secret = Secret( - id=int(row['id']), - secret=row['secret'][1:-1], - repo_name=row['repo_name'], - file_path=row['file_path'], - file_type=row['file_type'], - start_line=int(row['start_line']), - start_column=int(row['start_column']), - end_line=int(row['end_line']), - end_column=int(row['end_column']), - label=True if row['label'] == 'Y' else False, - in_url=True if row['in_url'] == 'Y' else False, - entropy=float(row['entropy']), - length=int(row['length']), - is_multiline=True if row['is_multiline'] == 'Y' else False, - file_identifier=row['file_identifier'], - comment=row['comment'], - ) - - if secret.file_identifier not in self.files: - continue - self._bench_data[secret.id] = secret - - # self._tool_results = self._parse_tool_results(tool_results_path) - - def get_secrets_for_file(self, file: str, only_valid: bool): - return { - id: secret - for id, secret in self._bench_data.items() - if secret.file_identifier == file and (True if only_valid is False else secret.label == only_valid) - } - - def check_file_to_skip(self, file): - path = f'{self.verification_dir}/{self.bucket_dir}/{prefix_all_found}_{file}.json' - if os.path.exists(path): - return True - - path = f'{self.verification_dir}/{self.bucket_dir}/{prefix_not_all}_{file}.json' - if os.path.exists(path): - return True - - return False - - def start_incremental(self): - total_count = len(self.files) - for index, file in enumerate(self.files): - print(f'===================== FILE {index+1} of {total_count} =====================') - - skip_this_file = self.check_file_to_skip(file) - if skip_this_file is True: - continue - - path = f'{self.target_dir}/{self.bucket_dir}/{file}' - relevant_secrets = self.get_secrets_for_file(file, only_valid=False) - args = [ - '', - '--target-dir', - '', - '--oneshot', - f'{path}', - '--benchmarking-mode', - '--false-findings', - '/app/tests/fixtures/false_findings.json', - '--excluded-paths', - 'disable', - '--outfile', - f'./{file}_report.json', - '--outformat', - 'dojo-sarif', - ] - findings: set[Finding] - ds_file: File - findings, errors, ds_file = DeepSecretsCliTool(args).start() - findings = set(findings) - - file_verification_result: FileVerificationResult = FileVerificationResult(file_identifier=file) - file_verification_result.sb_secrets_count = len(relevant_secrets.keys()) - - for id, secret in relevant_secrets.items(): - if secret.label is True: - file_verification_result.sb_valid_secrets_count += 1 - else: - file_verification_result.sb_false_secrets_count += 1 - - found = False - # duplicates: [31860, 31861], [93022, 93024] - # others: bad secret values that not match with the original files - # easy to cover: [ - # 42815 (tokenimprover for links and querystrings), - # 94321 (rsa key without end header) - # ] - # currently unable to find: [94925, 24676(indented-md-yaml), 11549(html-inside-py)] - # potentially false positives: [32426] - # error files: - # - coinapi-coinapi-sdk_eb8cf18f1d5437e96e27df8a0d450cf65ae… - if id in [87344, 40571, 40526, 33888, 5956, 31860, 31861, 93022, 93024]: - found = True - file_verification_result.report[id] = SecretPointer( - found=found, - secret_id=id, - line_number=secret.start_line, - line_offset=secret.start_column, - detection=secret.secret, - context=None, - is_valid=secret.label, - ) - continue - - for finding in findings: - if finding.detection.replace(' ', '').replace('\n', '').replace('.', '') != secret.secret.replace( - ' ', '' - ).replace('\n', '').replace('.', ''): - continue - - if finding.start_line_number != secret.start_line: - continue - - found = True - file_verification_result.report[id] = SecretPointer( - found=found, - secret_id=id, - line_number=secret.start_line, - line_offset=secret.start_column, - detection=secret.secret, - is_valid=secret.label, - ) - findings.remove(finding) - break - - if found is False: - start_offset = ds_file.get_offset(line=secret.start_line, column=secret.start_column) - end_offset = ds_file.get_offset(line=secret.end_line, column=secret.end_column) - boundaries = ds_file.check_boundaries([start_offset - 50, end_offset + 50]) - - context = ds_file.content[boundaries[0] : boundaries[1]] - file_verification_result.report[id] = SecretPointer( - secret_id=id, - found=found, - line_number=secret.start_line, - is_valid=secret.label, - context=context, - ) - - if secret.label is True: - file_verification_result.all_found = False - file_verification_result.not_found_valid_secret_ids.append(id) - else: - file_verification_result.not_found_false_secret_ids.append(id) - - else: - if secret.label is True: - file_verification_result.found_valid_secret_ids.append(secret.id) - else: - file_verification_result.found_false_secret_ids.append(secret.id) - - for finding in findings: - boundaries = ds_file.check_boundaries([finding.start_offset - 50, finding.end_offset + 50]) - context = ds_file.content[boundaries[0] : boundaries[1]] - id = finding.get_id() - file_verification_result.report[id] = SecretPointer( - found=True, - secret_id=id, - line_number=finding.start_line_number, - context=context, - detection=finding.detection, - internal_score=finding.internal_score, - is_extra=True, - is_valid=True, - rule_id=finding.rules[0].id, - ) - file_verification_result.extra_secrets_count += 1 - - self.write_verification_file(file, file_verification_result) - - def start_per_bucket(self): - path = f'{self.target_dir}/{self.bucket_dir}' - args = [ - '', - '--target-dir', - f'{path}', - '--benchmarking-mode', - '--false-findings', - '/app/tests/fixtures/false_findings.json', - '--excluded-paths', - 'disable', - '--outfile', - f'./{self.bucket_dir}_report.json', - '--outformat', - 'dojo-sarif', - ] - - findings: set[Finding] - findings, _, _ = DeepSecretsCliTool(args).start() - findings = set(findings) - - findings: Dict[str, List[Finding]] = self.dictify_findings_list(findings) - for current_file in self.files: - relevant_secrets = self.get_secrets_for_file(current_file, only_valid=False) - file_verification_result: FileVerificationResult = FileVerificationResult(file_identifier=current_file) - file_verification_result.sb_secrets_count = len(relevant_secrets.keys()) - - for id, secret in relevant_secrets.items(): - if secret.label is True: - file_verification_result.sb_valid_secrets_count += 1 - else: - file_verification_result.sb_false_secrets_count += 1 - - found = False - # duplicates: [31860, 31861], [93022, 93024] - # others: bad secret values that not match with the original files - # easy to cover: [ - # 42815 (tokenimprover for links and querystrings), - # 94321 (rsa key without end header) - # ] - # currently unable to find: [94925, 24676(indented-md-yaml), 11549(html-inside-py)] - # potentially false positives: [32426] - # error files: - # - coinapi-coinapi-sdk_eb8cf18f1d5437e96e27df8a0d450cf65ae… - if id in [87344, 40571, 40526, 33888, 5956, 31860, 31861, 93022, 93024]: - found = True - file_verification_result.report[id] = SecretPointer( - found=found, - secret_id=id, - line_number=secret.start_line, - line_offset=secret.start_column, - detection=secret.secret, - context=None, - is_valid=secret.label, - ) - continue - - for finding in findings.get(current_file, []): - if finding.detection.replace(' ', '').replace('\n', '').replace('.', '') != secret.secret.replace( - ' ', '' - ).replace('\n', '').replace('.', ''): - continue - - if finding.start_line_number != secret.start_line: - continue - - found = True - file_verification_result.report[id] = SecretPointer( - found=found, - secret_id=secret.id, - line_number=secret.start_line, - line_offset=secret.start_column, - detection=secret.secret, - is_valid=secret.label, - rule_id=secret.comment, - ) - findings[current_file].remove(finding) - break - - if found is False: - file_verification_result.report[id] = SecretPointer( - secret_id=id, - found=found, - line_number=secret.start_line, - is_valid=secret.label, - detection=secret.secret, - internal_score=f'e: {secret.entropy}', - rule_id=secret.comment, - context=None, - ) - - if secret.label is True: - file_verification_result.all_found = False - file_verification_result.not_found_valid_secret_ids.append(id) - else: - file_verification_result.not_found_false_secret_ids.append(id) - - else: - if secret.label is True: - file_verification_result.found_valid_secret_ids.append(secret.id) - else: - file_verification_result.found_false_secret_ids.append(secret.id) - - # EXTRA LEFT - for finding in findings.get(current_file, []): - id = finding.get_id() - file_verification_result.report[id] = SecretPointer( - found=True, - secret_id=id, - line_number=finding.start_line_number, - context=None, - detection=finding.detection, - internal_score=finding.internal_score, - is_extra=True, - is_valid=True, - rule_id=finding.rules[0].id, - ) - file_verification_result.extra_secrets_count += 1 - - # self.write_verification_file(current_file, file_verification_result) - self.write_verification_db(current_file, file_verification_result) - - self.db.close() - - def dictify_findings_list(self, findings: List[Finding]) -> Dict[str, List[Finding]]: - final = {} - for finding in findings: - if finding.file.relative_path not in final: - final[finding.file.relative_path] = list() - - final[finding.file.relative_path].append(finding) - return final diff --git a/benchmarker/models/verification.py b/benchmarker/models/verification.py deleted file mode 100644 index 547d3a0..0000000 --- a/benchmarker/models/verification.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import datetime -from typing import Dict, List, Optional - -from pydantic import BaseModel, Field, RootModel - - -class SecretPointer(BaseModel): - found: bool - secret_id: Optional[int] = None - line_number: Optional[int] = None - line_offset: Optional[int] = None - is_valid: bool - detection: Optional[str] = None - internal_score: Optional[str] = None - context: Optional[str] = None - rule_id: Optional[str] = None - is_extra: Optional[bool] = Field(default=False) - - -class FileVerificationResult(BaseModel): - checked: bool = Field(default=False) - all_found: bool = Field(default=True) - file_identifier: str - - sb_secrets_count: int = Field(default=0) - sb_valid_secrets_count: int = Field(default=0) - sb_false_secrets_count: int = Field(default=0) - - not_found_valid_secret_ids: List[int] = Field(default_factory=list) - not_found_false_secret_ids: List[int] = Field(default_factory=list) - found_valid_secret_ids: List[int] = Field(default_factory=list) - found_false_secret_ids: List[int] = Field(default_factory=list) - - extra_secrets_count: int = 0 - updated_ts: Optional[datetime] = None - report: Dict[int, SecretPointer] = Field(default_factory=dict) - - def __hash__(self) -> int: - return hash(self.file_identifier) - - def __eq__(self, value: object) -> bool: - return value.file_identifier == self.file_identifier - - -class VerificationsReportFile(RootModel): - root: Dict[str, FileVerificationResult] diff --git a/benchmarker/per_bucket.py b/benchmarker/per_bucket.py deleted file mode 100644 index efc5b2d..0000000 --- a/benchmarker/per_bucket.py +++ /dev/null @@ -1,26 +0,0 @@ -import sys - -from benchmarker.models.secretbench import TestingScope - - -def run(): - - dataset_location = sys.argv[1] - target_dir = sys.argv[2] - bucket_dir = sys.argv[3] - verification_dir = sys.argv[4] - - scope = TestingScope() - scope.init( - bucket_dir=bucket_dir, - target_dir=target_dir, - dataset_location=dataset_location, - verification_dir=verification_dir, - ) - scope.start_per_bucket() - - print() - - -if __name__ == '__main__': - run() diff --git a/benchmarker/run_bucket_benchmark.sh b/benchmarker/run_bucket_benchmark.sh deleted file mode 100755 index 092b354..0000000 --- a/benchmarker/run_bucket_benchmark.sh +++ /dev/null @@ -1 +0,0 @@ -PYTHONPATH=/app python3 /app/benchmarker/per_bucket.py /secretbench/secretbench.csv /secretbench/bench/buckets bucket_$1 /secretbench/bench/verifications From 776afcc091875d527211f74c1c3be178cd83d596 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 13:07:21 +0300 Subject: [PATCH 08/20] Fixes for tests --- tests/fixtures/excluded_paths.json | 5 +++++ tests/scan_modes/test_cli_scan_mode.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/excluded_paths.json b/tests/fixtures/excluded_paths.json index c770020..5fc2266 100644 --- a/tests/fixtures/excluded_paths.json +++ b/tests/fixtures/excluded_paths.json @@ -126,5 +126,10 @@ { "name": "Postman collection files", "pattern": ".*postman_collection\\.json$" + }, + { + "name": "Deepsecrets Problem Files Fixtures Location", + "pattern":".*problem_files\/.*" + } ] \ No newline at end of file diff --git a/tests/scan_modes/test_cli_scan_mode.py b/tests/scan_modes/test_cli_scan_mode.py index 864e154..aa37906 100644 --- a/tests/scan_modes/test_cli_scan_mode.py +++ b/tests/scan_modes/test_cli_scan_mode.py @@ -39,7 +39,7 @@ def test_cli_scan_mode(config: Config) -> None: for file in mode.filepaths: findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file, 0, {}).findings) - assert len(findings) == 7 + assert len(findings) == 6 # checking through the 'run' method # false findings checked at the end From af53115ae29d7ab030dd226aa02caa2dd8f6685a Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 14:31:39 +0300 Subject: [PATCH 09/20] Updates to Readme --- README.md | 186 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 149 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 72f7c18..b71b6be 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,121 @@ -# DeepSecrets - a better tool for secret scanning +# DeepSecrets 2.0 - a better tool for secret scanning -## Yet another tool - why? -Existing tools don't really "understand" code. Instead, they mostly parse texts. +![Tests Status](https://github.com/ntoskernel/deepsecrets/actions/workflows/python-package.yml/badge.svg) -DeepSecrets expands classic regex-search approaches with semantic analysis, dangerous variable detection, and more efficient usage of entropy analysis. Code understanding supports 500+ languages and formats and is achieved by lexing and parsing - techniques commonly used in SAST tools. +## What is it? Another token-wasting CLI proxy to an AI API? -DeepSecrets also introduces a new way to find secrets: just use hashed values of your known secrets and get them found plain in your code. +Absolutely not! -Under the hood story is in articles here: https://hackernoon.com/modernizing-secrets-scanning-part-1-the-problem +In our LLM-hype era, DeepSecrets still runs entirely on your machine — giving you great results offline, securely, and for free. -### But what about Semgrep Secrets? Looks like you're cloning their thing. -DeepSecrets was released in April 2023 — half a year before the Semgrep Secrets release and I'm very glad to be followed. We share the same ideas and principles under the hood but: -- DeepSecrets is free, Semgrep is a commercial product -- Code analysis in DeepSecrets is wider and not limited to a specific set of languages like in Semgrep +## So why yet another tool? +Most existing scanners don't actually "understand" code. Instead, they just parse texts and have bad coverage. -## Contacts +DeepSecrets bridges the gap between classic regex scanners and full-scale commercial SAST tools. It extends the classic regex-based scanning strategy by heavily relying on semantic code analysis, dangerous variable detection, and context-aware entropy analysis. +This means secret candidates are always semantically correct. We achieve true code understanding across 500+ languages and formats using lexing and parsing techniques -- Nikolai Khechumov ([@ntoskernel](https://github.com/ntoskernel)) — creator and maintainer +DeepSecrets also introduces a new way to find credentials: the HashedSecret Engine. Just provide the hashed values of your known production secrets, and the tool will find them exposed in plain text within your code. +### Performance & Benchmarks (SecretBench) -## Mini-FAQ -> Pff, is it still regex-based? +DeepSecrets v2.0 was evaluated (May 2026) against the **SecretBench** benchmark outperforming traditional flat-text scanners: -Yes and no. Of course, it uses regexes and finds typed secrets like any other tool. But language understanding (the lexing stage) and variable detection also use regexes under the hood. So regexes is an instrument, not a problem. +* **93% Recall** +* **8% False Positive Rate** on SecretBench scope +* **~9K Extra Findings** *outside* the benchmark scope due to deep semantic code parsing -> Why don't you build true abstract syntax trees? It's academically more correct! +(You can read the full under-the-hood story and benchmark breakdown in my HackerNoon article here: Modernizing Secrets Scanning) -DeepSecrets tries to keep a balance between complexity and effectiveness. Building a true AST is a pretty complex thing and simply an overkill for our specific task. So the tool still follows the generic SAST-way of code analysis but optimizes the AST part using a different approach. -> I'd like to build my own semantic rules. How do I do that? - -Only through the code by the moment. Formalizing the rules and moving them into a flexible and user-controlled ruleset is in the plans. - -> I still have a question - -Feel free to communicate with the [maintainer](https://github.com/ntoskernel/deepsecrets/blob/main/pyproject.toml#L6-L8) +# Quick Start Guide ## Installation From Github via pip -`$ pip install git+https://github.com/ntoskernel/deepsecrets.git` +```bash +$ pip install git+https://github.com/ntoskernel/deepsecrets.git +``` From PyPi -`$ pip install deepsecrets` +```bash +$ pip install deepsecrets +``` ## Scanning -The easiest way: +The easiest way to run a scan: -`$ deepsecrets --target-dir /path/to/your/code --outformat dojo-sarif --outfile report.json` +```bash +$ deepsecrets --target-dir /path/to/your/code --outformat dojo-sarif --outfile report.json +``` This will run a scan against `/path/to/your/code` using the default configuration: -- Regex checks by a small built-in ruleset +- Regex using the built-in ruleset - Semantic checks (variable detection, entropy checks) -Report in SARIF format (DefectDojo-compatible) will be saved to `report.json`. If you face any problem with SARIF format, you can fall back to internal format via `--outfile json` +A report in SARIF format (compatible with DefectDojo and GitHub Security) will be saved to report.json. + +### Fine-Tuning +The `--help` command is always ready to guide you, but here are the key flags you can use to tailor the scan to your environment: +* `--regex-rules /path/to/rules.json`: Supply your own custom regex ruleset. +* `--hashed-values /path/to/hashes.json`: Provide a list of pre-hashed known production secrets to search for them securely. +* `--excluded-paths /path/to/exclusions.json`: Override or extend the default paths ignored during scanning. +* `--disable-masking`: Keep potential secrets unmasked in the output report *(see caution below)*. + + +### Github Actions Integration + +eq. `.github/workflows/deepsecrets.yml` -#### Masking secrets inside a report +```yaml +name: DeepSecrets Scan +on: [push, pull_request] + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install DeepSecrets + run: pip install deepsecrets + + - name: Run Scan + run: deepsecrets --target-dir . --outformat dojo-sarif --outfile report.sarif + continue-on-error: true + + - name: Upload SARIF report + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: report.sarif +``` + +### Masking secrets inside a report As of version 1.3.0 all potential secrets inside reports are masked by default, but you can turn this feature off via the `--disable-masking` flag. > [!Caution] -> If you decide to integreate DeepSecrets to your CI pipeline with masking disabled, you will likely re-leak your secrets inside your CI artefacts. +> If you decide to integrate DeepSecrets to your CI pipeline with masking disabled, you will likely re-leak your secrets inside your CI artifacts. + +### SARIF reports and the "Confidence" Parameter -### Fine-tuning -Run `deepsecrets --help` for details. +DeepSecrets calculates and reports a granular confidence score for every discovered secret. Due to the constraints of the SARIF specification and variations in how different industry platforms parse it, DeepSecrets has the following features to ensure compatibility: -Basically, you can (and should) use your own regex-ruleset by specifying `--regex-rules`. Building rulesets is described in the next section. +* **Virtual Subrules (`rules[]`)**: GitHub and DefectDojo parse security metrics primarily from the static rules array. To support this, DeepSecrets dynamically maps findings to "virtual" subrules (e.g., `S105-LOW`, `S105-MEDIUM`). Each subrule contains tailored `properties.precision` (strictly matching GitHub's allowed vocabulary) and a scaled `properties.security-severity` score (9.0–10.0), guaranteeing that **all** alerts are flagged as **Critical** in GitHub Security and DefectDojo, while preserving internal confidence variance. + +* **Deterministic Result Level**: The tool always explicitly sets `level: error` in the `results[]` model. This acts as a universal fallback for CI/CD pipelines and older SAST parsers, ensuring that exposed secrets reliably break builds or block Pull Requests regardless of individual rule interpretations. + +* **Contextual Messages**: The raw numeric confidence score is injected directly into `result.message.text` (and saved under `result.properties.confidence`). This ensures that security analysts can instantly see the exact confidence level inside any UI dashboard, even if the platform ignores custom JSON parameters. -Paths to be excluded from scanning can be set via `--excluded-paths`. The default set of excluded paths is here: `/deepsecrets/rules/excluded_paths.json`, you can write your own following the format. ## Building rulesets @@ -83,6 +127,74 @@ The built-in ruleset for regex checks is located in `/deepsecrets/rules/regexes. Example ruleset for hashed checks is located in `/tests/fixtures/hashed_secrets.json`. You're free to follow the format and create a custom ruleset. +#### HashedSecret Ruleset Example + +To look for known production secrets without exposing them in plaintext inside your repository, provide a JSON containing their hashes: + +```json +[ + { + "name": "KNOWN-PROD-DATABASE-PASSWORD", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "length": 12, + "algorithm": "sha1" + } +] +``` +Run with `--hashed-values /path/to/hashes.json`. DeepSecrets will automatically hash token candidates during scanning and flag plain-text matches. + +## Contacts + +- Nikolai Khechumov ([@ntoskernel](https://github.com/ntoskernel)) — creator and maintainer + +## FAQ +> Pff, is it still regex-based? + +Yes and no. Of course, it uses regexes to find typed secrets like any other tool. But language understanding (the lexing stage) and variable detection also use regexes under the hood. Regex is an instrument, not the problem. The problem is applying regex blindly without semantic context. + +> But what about Semgrep Secrets? Looks like you're cloning their thing. + +DeepSecrets was released in April 2023 — half a year before the Semgrep Secrets release, and I'm very glad to be followed. We share similar ideas and principles under the hood, but DeepSecrets is free/open-source, and our code analysis is much wider, not limited to a specific subset of languages like Semgrep. + +### DeepSecrets vs. Other Scanners + +Most traditional scanners look at code as flat text, leading to massive alert fatigue (false positives) or missed leaked variables. DeepSecrets bridges the gap between classic regex scanners and full-scale commercial SAST tools. + +#### Benchmark Results (SecretBench) +In recent evaluations against the **SecretBench** benchmark, DeepSecrets demonstrated industry-leading accuracy: +* **93% Recall (Sensitivity):** Caught almost all valid secrets within the benchmark. +* **8% False Positive Rate:** Minimal noise compared to traditional entropy-based scanners. +* **40,000+ Extra Findings:** Discovered tens of thousands of real, high-privilege credentials outside the baseline benchmark scope due to deep semantic code parsing. + + +| Feature / Capability | **DeepSecrets 2.0** | **Gitleaks** | **TruffleHog** | **Semgrep Secrets** | +| :--- | :---: | :---: | :---: | :---: | +| **SecretBench Accuracy** | **93% Recall
69% Precision** | 88% Recall
46% Precision | 52% Recall
6% Precision | *Not Evaluated* | +| **Price & Licensing** | **Free / Open-Source** | Free / Open-Source | Free / Open-Source | Commercial / Paid | +| **Analysis Type** | **Semantic Lexing & Parsing** | Flat-text Regex / Entropy | Flat-text Regex / Entropy | Semantic AST Parsing | +| **Language Support** | **500+ (via Pygments Lexers)** | Context-agnostic (Text) | Context-agnostic (Text) | Limited subset | +| **Pre-hashed Validation** | **Yes (via Hashed Engine)** | No | No | No | +| **Context-Aware Entropy**| **Yes (Assigned values)** | No (Entire file text) | No (Entire file text) | Yes | +| **Advanced SARIF Output**| **Yes (Dynamic Confidence)** | Basic | Basic | Yes | + +### Why this matters under the hood +* **True Code Understanding:** Traditional tools will flag high-entropy strings inside a comment or a base64 asset. DeepSecrets understands the semantic role of a token (e.g., if it is an assigned variable name like `db_password`), ensuring that candidates are always semantically correct. +* **Unmatched Discovery Width:** While Semgrep relies on specific language parsers and standard tools scan only what they know, DeepSecrets leverages 500+ lexers. This allows it to surface hidden, dangerous credentials in rare configuration formats and custom code blocks that benchmarks don't even have datasets for. + + +> Why don't you build true abstract syntax trees? It's academically more correct! + +DeepSecrets tries to keep a balance between complexity and effectiveness. Building a true AST across 500+ languages is incredibly complex and simply overkill for the specific task of finding secrets. The tool follows the generic SAST approach to code analysis but optimizes the AST stage for maximum speed and width. + +> I'd like to build my own semantic rules. How do I do that? + +Semantic rules are now effectively "variable evaluation rules". You can find them [here](https://github.com/ntoskernel/deepsecrets/blob/main/deepsecrets/rules/variable_scoring_rules.json). + +> I still have a question + +Feel free to communicate with the [maintainer (emails available in pyproject.toml)](https://github.com/ntoskernel/deepsecrets/blob/main/pyproject.toml#L6-L8) + + ## Contributing @@ -138,4 +250,4 @@ Steps: 2. Open the cloned folder with VSCode 3. Agree with 'Reopen in container' 4. Wait until the container is built and necessary extensions are installed -5. You're ready +5. You're ready \ No newline at end of file From 12f1a317df936ff6b1cc84006e8833cacd34e12a Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 15:11:48 +0300 Subject: [PATCH 10/20] Readme fix --- .devcontainer/devcontainer.json | 14 +++++++++++--- README.md | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6e40988..8a161c3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,9 +2,13 @@ "name": "DeepSecrets Devcontainer", "build": { "context": "..", - "dockerfile": "Dockerfile" + "dockerfile": "Dockerfile", + "options": ["--network=host"] }, - + "mounts": [ + "source=/home/khechumov,target=/secretbench,type=bind,consistency=cached" + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=delegated", "workspaceFolder": "/app", "customizations": { @@ -18,5 +22,9 @@ ] } }, - "postCreateCommand": "poetry install --no-root --with test,dev" + "postCreateCommand": "poetry install --no-root --with test,dev", + "shutdownAction": "none", + "runArgs": [ + "--network=host" + ] } diff --git a/README.md b/README.md index b71b6be..095c856 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DeepSecrets 2.0 - a better tool for secret scanning +# DeepSecrets 2.0 - a better tool for secrets scanning ![Tests Status](https://github.com/ntoskernel/deepsecrets/actions/workflows/python-package.yml/badge.svg) From 6a237e16dee6b3d218f2bbf7a7d71ad4cf795cfe Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 15:13:03 +0300 Subject: [PATCH 11/20] action renamed --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d339bd3..c2cd9a2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,4 +1,4 @@ -name: Build and run tests +name: Recall and Performance Tests on: push: From 66ed90f83d469343d5da0c8fe1ce7c1b8f9d1781 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 15:16:07 +0300 Subject: [PATCH 12/20] Another fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 095c856..71d80cb 100644 --- a/README.md +++ b/README.md @@ -171,8 +171,8 @@ In recent evaluations against the **SecretBench** benchmark, DeepSecrets demonst | :--- | :---: | :---: | :---: | :---: | | **SecretBench Accuracy** | **93% Recall
69% Precision** | 88% Recall
46% Precision | 52% Recall
6% Precision | *Not Evaluated* | | **Price & Licensing** | **Free / Open-Source** | Free / Open-Source | Free / Open-Source | Commercial / Paid | -| **Analysis Type** | **Semantic Lexing & Parsing** | Flat-text Regex / Entropy | Flat-text Regex / Entropy | Semantic AST Parsing | -| **Language Support** | **500+ (via Pygments Lexers)** | Context-agnostic (Text) | Context-agnostic (Text) | Limited subset | +| **Analysis Type** | **Semantic / Regex** | Flat-text Regex / Entropy | Flat-text Regex / Entropy | Semantic | +| **Language Support** | **500+** | Context-agnostic (Text) | Context-agnostic (Text) | Limited subset | | **Pre-hashed Validation** | **Yes (via Hashed Engine)** | No | No | No | | **Context-Aware Entropy**| **Yes (Assigned values)** | No (Entire file text) | No (Entire file text) | Yes | | **Advanced SARIF Output**| **Yes (Dynamic Confidence)** | Basic | Basic | Yes | From 8f430fd1b48f67493ba501ad93737d7690fe47b0 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 15:17:40 +0300 Subject: [PATCH 13/20] Build fix --- .devcontainer/devcontainer.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8a161c3..29a2bf6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,12 +2,8 @@ "name": "DeepSecrets Devcontainer", "build": { "context": "..", - "dockerfile": "Dockerfile", - "options": ["--network=host"] + "dockerfile": "Dockerfile" }, - "mounts": [ - "source=/home/khechumov,target=/secretbench,type=bind,consistency=cached" - ], "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=delegated", "workspaceFolder": "/app", @@ -23,8 +19,5 @@ } }, "postCreateCommand": "poetry install --no-root --with test,dev", - "shutdownAction": "none", - "runArgs": [ - "--network=host" - ] + "shutdownAction": "none" } From c39eedf6311b1da0fff38c5f0ff154b7f18642e4 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 15:20:51 +0300 Subject: [PATCH 14/20] rename the flow --- .github/workflows/{python-package.yml => run-tests.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{python-package.yml => run-tests.yml} (100%) diff --git a/.github/workflows/python-package.yml b/.github/workflows/run-tests.yml similarity index 100% rename from .github/workflows/python-package.yml rename to .github/workflows/run-tests.yml From 397b8cdbd6d618e3a4941f9b555a5ad67e1c8e3a Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 15:21:32 +0300 Subject: [PATCH 15/20] svg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71d80cb..963deb1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DeepSecrets 2.0 - a better tool for secrets scanning -![Tests Status](https://github.com/ntoskernel/deepsecrets/actions/workflows/python-package.yml/badge.svg) +![Tests Status](https://github.com/ntoskernel/deepsecrets/actions/workflows/run-tests.yml/badge.svg) ## What is it? Another token-wasting CLI proxy to an AI API? From 637433e08d0617b1d70b5cdf040d17c9e80cb6ec Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 16:10:42 +0300 Subject: [PATCH 16/20] Another readme update for clarity --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 963deb1..783e493 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ As of version 1.3.0 all potential secrets inside reports are masked by default, ### SARIF reports and the "Confidence" Parameter -DeepSecrets calculates and reports a granular confidence score for every discovered secret. Due to the constraints of the SARIF specification and variations in how different industry platforms parse it, DeepSecrets has the following features to ensure compatibility: +Every finding gets a confidence score. However, different security platforms parse SARIF metrics differently. To ensure compatibility, DeepSecrets tool does the following: * **Virtual Subrules (`rules[]`)**: GitHub and DefectDojo parse security metrics primarily from the static rules array. To support this, DeepSecrets dynamically maps findings to "virtual" subrules (e.g., `S105-LOW`, `S105-MEDIUM`). Each subrule contains tailored `properties.precision` (strictly matching GitHub's allowed vocabulary) and a scaled `properties.security-severity` score (9.0–10.0), guaranteeing that **all** alerts are flagged as **Critical** in GitHub Security and DefectDojo, while preserving internal confidence variance. @@ -179,7 +179,7 @@ In recent evaluations against the **SecretBench** benchmark, DeepSecrets demonst ### Why this matters under the hood * **True Code Understanding:** Traditional tools will flag high-entropy strings inside a comment or a base64 asset. DeepSecrets understands the semantic role of a token (e.g., if it is an assigned variable name like `db_password`), ensuring that candidates are always semantically correct. -* **Unmatched Discovery Width:** While Semgrep relies on specific language parsers and standard tools scan only what they know, DeepSecrets leverages 500+ lexers. This allows it to surface hidden, dangerous credentials in rare configuration formats and custom code blocks that benchmarks don't even have datasets for. +* **Unmatched Discovery Width:** While other tools scan only what they know, DeepSecrets leverages 500+ lexers. This allows it to surface hidden, dangerous credentials in rare configuration formats and custom code blocks that benchmarks may not have datasets for. > Why don't you build true abstract syntax trees? It's academically more correct! From d7cb50a44259205dcb32e054b0f48d7c273d6eb7 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 16:30:23 +0300 Subject: [PATCH 17/20] Another set of readme fixes --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 783e493..ca65428 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ In our LLM-hype era, DeepSecrets still runs entirely on your machine — giving Most existing scanners don't actually "understand" code. Instead, they just parse texts and have bad coverage. DeepSecrets bridges the gap between classic regex scanners and full-scale commercial SAST tools. It extends the classic regex-based scanning strategy by heavily relying on semantic code analysis, dangerous variable detection, and context-aware entropy analysis. -This means secret candidates are always semantically correct. We achieve true code understanding across 500+ languages and formats using lexing and parsing techniques +This means secret candidates are always semantically correct. We achieve true code understanding across 500+ languages and formats using lexing and parsing techniques. DeepSecrets also introduces a new way to find credentials: the HashedSecret Engine. Just provide the hashed values of your known production secrets, and the tool will find them exposed in plain text within your code. @@ -99,22 +99,22 @@ jobs: sarif_file: report.sarif ``` -### Masking secrets inside a report +### Masking Secrets in Reports -As of version 1.3.0 all potential secrets inside reports are masked by default, but you can turn this feature off via the `--disable-masking` flag. +As of v1.3.0, potential secrets are automatically masked inside reports to protect your pipeline artifacts. Turn this off via the `--disable-masking` flag if necessary. > [!Caution] -> If you decide to integrate DeepSecrets to your CI pipeline with masking disabled, you will likely re-leak your secrets inside your CI artifacts. +> If you integrate DeepSecrets into your CI pipeline with masking disabled, you will likely re-leak your secrets inside your CI logs and artifacts. -### SARIF reports and the "Confidence" Parameter +### SARIF Reports & Dynamic Confidence -Every finding gets a confidence score. However, different security platforms parse SARIF metrics differently. To ensure compatibility, DeepSecrets tool does the following: +Every finding gets a confidence score. However, different security platforms parse SARIF metrics differently. To ensure compatibility across modern ASPM dashboards, DeepSecrets does the following: -* **Virtual Subrules (`rules[]`)**: GitHub and DefectDojo parse security metrics primarily from the static rules array. To support this, DeepSecrets dynamically maps findings to "virtual" subrules (e.g., `S105-LOW`, `S105-MEDIUM`). Each subrule contains tailored `properties.precision` (strictly matching GitHub's allowed vocabulary) and a scaled `properties.security-severity` score (9.0–10.0), guaranteeing that **all** alerts are flagged as **Critical** in GitHub Security and DefectDojo, while preserving internal confidence variance. +* **Virtual Subrules (`rules[]`)**: Dynamically generates rules like `S105-LOW` or `S105-CRITICAL`. This forces GitHub Security and DefectDojo to map semantic precision variance properly without breaking native parsers. * **Deterministic Result Level**: The tool always explicitly sets `level: error` in the `results[]` model. This acts as a universal fallback for CI/CD pipelines and older SAST parsers, ensuring that exposed secrets reliably break builds or block Pull Requests regardless of individual rule interpretations. -* **Contextual Messages**: The raw numeric confidence score is injected directly into `result.message.text` (and saved under `result.properties.confidence`). This ensures that security analysts can instantly see the exact confidence level inside any UI dashboard, even if the platform ignores custom JSON parameters. +* **Contextual Messages**: Injects the raw numeric confidence score natively into `result.message.text` so security analysts see it immediately on their UI dashboards. ## Building rulesets @@ -123,7 +123,7 @@ Every finding gets a confidence score. However, different security platforms par The built-in ruleset for regex checks is located in `/deepsecrets/rules/regexes.json`. You're free to follow the format and create a custom ruleset. -### HashedSecret +### HashedSecret (Zero-Knowledge Scanning) Example ruleset for hashed checks is located in `/tests/fixtures/hashed_secrets.json`. You're free to follow the format and create a custom ruleset. @@ -141,7 +141,7 @@ To look for known production secrets without exposing them in plaintext inside y } ] ``` -Run with `--hashed-values /path/to/hashes.json`. DeepSecrets will automatically hash token candidates during scanning and flag plain-text matches. +Run with `--hashed-values /path/to/hashes.json`. DeepSecrets will automatically hash string candidates on the fly during its lexing stage to match them. ## Contacts @@ -154,7 +154,7 @@ Yes and no. Of course, it uses regexes to find typed secrets like any other tool > But what about Semgrep Secrets? Looks like you're cloning their thing. -DeepSecrets was released in April 2023 — half a year before the Semgrep Secrets release, and I'm very glad to be followed. We share similar ideas and principles under the hood, but DeepSecrets is free/open-source, and our code analysis is much wider, not limited to a specific subset of languages like Semgrep. +DeepSecrets was originally released in April 2023 — six months before Semgrep Secrets launched. We share similar principles, but DeepSecrets is 100% free/open-source and leverages a significantly broader multi-language tracking surface. ### DeepSecrets vs. Other Scanners @@ -184,7 +184,7 @@ In recent evaluations against the **SecretBench** benchmark, DeepSecrets demonst > Why don't you build true abstract syntax trees? It's academically more correct! -DeepSecrets tries to keep a balance between complexity and effectiveness. Building a true AST across 500+ languages is incredibly complex and simply overkill for the specific task of finding secrets. The tool follows the generic SAST approach to code analysis but optimizes the AST stage for maximum speed and width. +DeepSecrets tries to keep a balance between complexity and effectiveness. Building a true AST across 500+ languages is incredibly complex and simply overkill for the secrets detection. The tool follows the generic SAST approach to code analysis but optimizes the AST stage for maximum speed and width. > I'd like to build my own semantic rules. How do I do that? @@ -192,11 +192,10 @@ Semantic rules are now effectively "variable evaluation rules". You can find the > I still have a question -Feel free to communicate with the [maintainer (emails available in pyproject.toml)](https://github.com/ntoskernel/deepsecrets/blob/main/pyproject.toml#L6-L8) +Feel free to contact the developer directly using the emails listed in [pyproject.toml](https://github.com/ntoskernel/deepsecrets/blob/main/pyproject.toml#L6-L8) - -## Contributing +## Contributing & Core Concepts ### Under the hood There are several core concepts: @@ -212,11 +211,13 @@ There are several core concepts: Just a pythonic representation of a file with all needed methods for management. ### Tokenizer -A component able to break the content of a file into pieces - Tokens - by its logic. There are four types of tokenizers available: +Breaks the content of a file into pieces - Tokens - by its logic. There are four types of tokenizers available: - `FullContentTokenizer`: treats all content as a single token. Useful for regex-based search. - `PerWordTokenizer`: breaks given content by words and line breaks. - `LexerTokenizer`: uses language-specific smarts to break code into semantically correct pieces with additional context for each token. +- `CheapVarDetectorTokenizer`: uses tight regexes to cover limitations of semantic variable detection. + ### Token A string with additional information about its semantic role, corresponding file, and location inside it. @@ -248,6 +249,5 @@ The project is supposed to be developed using VSCode and 'Remote containers' fea Steps: 1. Clone the repository 2. Open the cloned folder with VSCode -3. Agree with 'Reopen in container' -4. Wait until the container is built and necessary extensions are installed -5. You're ready \ No newline at end of file +3. Select "Reopen in Container" when prompted +4. Wait for the automated environment build to complete. You are ready to develop. \ No newline at end of file From f1075183d8b2d8b082d975f479af9fca5c0c2f5c Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 19:10:13 +0300 Subject: [PATCH 18/20] Partial fingerprints for sarif --- README.md | 20 +++--- deepsecrets/core/model/finding.py | 5 +- deepsecrets/core/model/response/base.py | 12 +++- deepsecrets/core/model/response/dojo_sarif.py | 4 +- tests/core/model/test_response_builder.py | 72 +++++++++++++++++++ tests/output/test_sarif.py | 16 +++-- 6 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 tests/core/model/test_response_builder.py diff --git a/README.md b/README.md index ca65428..c2640b3 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,17 @@ Most existing scanners don't actually "understand" code. Instead, they just pars DeepSecrets bridges the gap between classic regex scanners and full-scale commercial SAST tools. It extends the classic regex-based scanning strategy by heavily relying on semantic code analysis, dangerous variable detection, and context-aware entropy analysis. This means secret candidates are always semantically correct. We achieve true code understanding across 500+ languages and formats using lexing and parsing techniques. -DeepSecrets also introduces a new way to find credentials: the HashedSecret Engine. Just provide the hashed values of your known production secrets, and the tool will find them exposed in plain text within your code. +DeepSecrets also introduces a new way to find credentials with zero knowledge: the HashedSecret Engine. Just provide the hashed values of your known production secrets, and the tool will find them exposed in plain text within your code. ### Performance & Benchmarks (SecretBench) -DeepSecrets v2.0 was evaluated (May 2026) against the **SecretBench** benchmark outperforming traditional flat-text scanners: +DeepSecrets v2.0 was evaluated (June 2026) against the **SecretBench** benchmark outperforming traditional flat-text scanners: * **93% Recall** * **8% False Positive Rate** on SecretBench scope -* **~9K Extra Findings** *outside* the benchmark scope due to deep semantic code parsing +* **~9K Extra Findings** *outside* the SecretBench scope -(You can read the full under-the-hood story and benchmark breakdown in my HackerNoon article here: Modernizing Secrets Scanning) +*(You can read the full under-the-hood story and benchmark breakdown in my HackerNoon article [here]())* # Quick Start Guide @@ -114,7 +114,7 @@ Every finding gets a confidence score. However, different security platforms par * **Deterministic Result Level**: The tool always explicitly sets `level: error` in the `results[]` model. This acts as a universal fallback for CI/CD pipelines and older SAST parsers, ensuring that exposed secrets reliably break builds or block Pull Requests regardless of individual rule interpretations. -* **Contextual Messages**: Injects the raw numeric confidence score natively into `result.message.text` so security analysts see it immediately on their UI dashboards. +* **Contextual Messages**: Injects the raw numeric confidence score natively into `result.message.text` so security analysts see it immediately on their dashboards. ## Building rulesets @@ -158,14 +158,10 @@ DeepSecrets was originally released in April 2023 — six months before Semgrep ### DeepSecrets vs. Other Scanners -Most traditional scanners look at code as flat text, leading to massive alert fatigue (false positives) or missed leaked variables. DeepSecrets bridges the gap between classic regex scanners and full-scale commercial SAST tools. - -#### Benchmark Results (SecretBench) -In recent evaluations against the **SecretBench** benchmark, DeepSecrets demonstrated industry-leading accuracy: -* **93% Recall (Sensitivity):** Caught almost all valid secrets within the benchmark. -* **8% False Positive Rate:** Minimal noise compared to traditional entropy-based scanners. -* **40,000+ Extra Findings:** Discovered tens of thousands of real, high-privilege credentials outside the baseline benchmark scope due to deep semantic code parsing. +Most traditional scanners look at code as flat text, leading to massive false positive rate and coverage issues. +DeepSecrets acts differently. +#### Tool comparison based on SecretBench Results | Feature / Capability | **DeepSecrets 2.0** | **Gitleaks** | **TruffleHog** | **Semgrep Secrets** | | :--- | :---: | :---: | :---: | :---: | diff --git a/deepsecrets/core/model/finding.py b/deepsecrets/core/model/finding.py index 6372497..1f2f951 100644 --- a/deepsecrets/core/model/finding.py +++ b/deepsecrets/core/model/finding.py @@ -67,8 +67,9 @@ def get_reason(self) -> str: def get_fingerprint(self) -> str: return sha256(self.detection.encode('utf-8')).hexdigest()[23:33] - def get_fingerprint_v2(self) -> str: - base = f'{self.file.path}{self.detection}{self.start_offset}{self.end_offset}' + def get_partial_fingerprint(self) -> str: + var_name = self.internal_score.get('var', '') + base = f'{self.file.relative_path}|{var_name}|{self.detection}' return sha256(base.encode('utf-8')).hexdigest() def choose_final_rule(self) -> None: diff --git a/deepsecrets/core/model/response/base.py b/deepsecrets/core/model/response/base.py index a248b0d..4587640 100644 --- a/deepsecrets/core/model/response/base.py +++ b/deepsecrets/core/model/response/base.py @@ -50,6 +50,14 @@ def _get_context_boundaries(self, finding: Finding, start_column: int, end_colum return boundaries, line_partial - def _mask(self, snippet: str, detection: str): - masked_detection = '*' * len(detection) + def _mask(self, snippet: str, detection: str, symbol: str = '*'): + length = len(detection) + if length == 0: + return snippet + + mask_len = (length + 1) // 2 + start_len = (length - mask_len) // 2 + + masked_detection = detection[:start_len] + symbol * mask_len + detection[start_len + mask_len :] + return snippet.replace(detection, masked_detection) diff --git a/deepsecrets/core/model/response/dojo_sarif.py b/deepsecrets/core/model/response/dojo_sarif.py index 9a4e087..400652b 100644 --- a/deepsecrets/core/model/response/dojo_sarif.py +++ b/deepsecrets/core/model/response/dojo_sarif.py @@ -126,7 +126,6 @@ def with_current_mode(self, mode: ScanMode): ) return self - def _convert_rules(self, rules: Set[TierAwareSarifRuleMeta]) -> List[ReportingDescriptor]: return [ ReportingDescriptor( @@ -167,6 +166,9 @@ def build(self) -> SarifLog: # type: ignore ) ) ], + partial_fingerprints={ + 'dsfpx/v1': finding.get_partial_fingerprint(), + }, ) self.report.runs[0].results.append(result) diff --git a/tests/core/model/test_response_builder.py b/tests/core/model/test_response_builder.py new file mode 100644 index 0000000..94d1462 --- /dev/null +++ b/tests/core/model/test_response_builder.py @@ -0,0 +1,72 @@ +import pytest + +from deepsecrets.core.model.response.base import BaseResponseBuilder + + +@pytest.fixture +def base_response_builder(): + return BaseResponseBuilder() + + +@pytest.mark.parametrize( + "snippet, detection, expected", + [ + ( + "hellomydearfriends", + "hellomydearfriends", + "hell*********iends", + ), + ( + "abcdefgh", + "abcdefgh", + "ab****gh", + ), + ( + "abcde", + "abcde", + "a***e", + ), + ( + "x", + "x", + "*", + ), + ( + "xy", + "xy", + "*y", + ), + ( + "xyz", + "xyz", + "**z", + ), + ( + "hello", + "", + "hello", + ), + ( + "error: secret_password_123 found", + "secret_password_123", + "error: secr**********d_123 found", + ), + ( + "token: 123456, old_token: 123456", + "123456", + "token: 1***56, old_token: 1***56", + ), + ( + "confidential: admin master 77", + "admin master 77", + "confidential: adm********r 77", + ), + ( + "hello world", + "not_found", + "hello world", + ), + ], +) +def test_masking(base_response_builder, snippet, detection, expected): + assert base_response_builder._mask(snippet, detection) == expected diff --git a/tests/output/test_sarif.py b/tests/output/test_sarif.py index 2392be4..a8c8ac0 100644 --- a/tests/output/test_sarif.py +++ b/tests/output/test_sarif.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, Mock +from unittest.mock import Mock from jschema_to_python.to_json import to_json import pytest @@ -30,11 +30,19 @@ def test_dojo_sarif(config: Config) -> None: mode = CliScanMode(config=config) mode.progress_bar = Mock() mode.progress_bar.add_task.return_value = 0 + mode.progress_bar.task_ids = [] findings = [] - for file in mode.filepaths[:10]: - pfar = mode._per_file_analyzer(mode.analyzer_bundle(), file, task_reporter=MagicMock()) - findings.extend(pfar.findings) + + for file in mode.filepaths: + findings.extend(mode._per_file_analyzer(mode.analyzer_bundle(), file, 0, {}).findings) + + ''' + # checking through the 'run' method + # false findings checked at the end + findings = [] + findings = mode.run() + ''' sarif_response = to_json( DojoSarifResponseBuilder() From d7e7d55f945ad865ba2e6a509c1f6544d02c647e Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Wed, 3 Jun 2026 19:25:36 +0300 Subject: [PATCH 19/20] another set of readme fixes --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c2640b3..eda9890 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ jobs: As of v1.3.0, potential secrets are automatically masked inside reports to protect your pipeline artifacts. Turn this off via the `--disable-masking` flag if necessary. +Masking doesn't break the deduplication logic of downstream platforms (like Github), as the `partialFingerprints` section in the report is correctly populated based on the raw data. + > [!Caution] > If you integrate DeepSecrets into your CI pipeline with masking disabled, you will likely re-leak your secrets inside your CI logs and artifacts. @@ -158,8 +160,7 @@ DeepSecrets was originally released in April 2023 — six months before Semgrep ### DeepSecrets vs. Other Scanners -Most traditional scanners look at code as flat text, leading to massive false positive rate and coverage issues. -DeepSecrets acts differently. +While other tools scan only what they know, DeepSecrets leverages lexers. This allows it to surface hidden, dangerous credentials in rare configuration formats and custom code blocks that benchmarks may not have datasets for. #### Tool comparison based on SecretBench Results @@ -173,9 +174,6 @@ DeepSecrets acts differently. | **Context-Aware Entropy**| **Yes (Assigned values)** | No (Entire file text) | No (Entire file text) | Yes | | **Advanced SARIF Output**| **Yes (Dynamic Confidence)** | Basic | Basic | Yes | -### Why this matters under the hood -* **True Code Understanding:** Traditional tools will flag high-entropy strings inside a comment or a base64 asset. DeepSecrets understands the semantic role of a token (e.g., if it is an assigned variable name like `db_password`), ensuring that candidates are always semantically correct. -* **Unmatched Discovery Width:** While other tools scan only what they know, DeepSecrets leverages 500+ lexers. This allows it to surface hidden, dangerous credentials in rare configuration formats and custom code blocks that benchmarks may not have datasets for. > Why don't you build true abstract syntax trees? It's academically more correct! From 2e0ed5d469afa9e1812114e7722ddbb0cfc242e5 Mon Sep 17 00:00:00 2001 From: Nikolai Khechumov Date: Thu, 4 Jun 2026 18:04:53 +0300 Subject: [PATCH 20/20] 2.0.0 RC5 --- deepsecrets/cli.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deepsecrets/cli.py b/deepsecrets/cli.py index 965f54e..cb76d65 100644 --- a/deepsecrets/cli.py +++ b/deepsecrets/cli.py @@ -349,6 +349,11 @@ def start(self) -> int: # pragma: nocover '[bold red]:warning: SECRETS MASKING IS DISABLED. REPORT WILL CONTAIN SECRETS IN PLAINTEXT. BE CAREFUL!\n', justify='center', ) + else: + console.print( + '[bold green]:warning: SECRETS MASKING IS ENABLED. FINGERPRINTS ARE UNAFFECTED\n(downstream ASPM deduplication will work normally)\n', + justify='center', + ) if config.return_code_if_findings is True: console.print(