From 39675fe46b5b7d3c769a3e80c767619c9b915dad Mon Sep 17 00:00:00 2001 From: eezd Date: Thu, 5 Nov 2026 11:22:46 +0800 Subject: [PATCH] Fix official news retries, date boundaries and collection status Bound document retries across realtime and history lanes, preserve safe completed reads on failed rounds, and retain unresolved gaps. Honor the longest access-block cooldown and measure retries from the actual failure time. Filter dated official listings at the collection boundary, handle empty Federal Register days, and distinguish active rounds from waiting or failed processes in the console. Add captured-response regressions and isolated checkpoint validation. --- backend/src/alphapulse/api/crawler.py | 7 +- .../src/alphapulse/crawler/spiders/base.py | 28 +- .../alphapulse/crawler/spiders/us_official.py | 103 +++++- .../us_news/collection_failures.json.gz | Bin 0 -> 87771 bytes backend/tests/test_us_official_news.py | 334 +++++++++++++++++- deploy/README.md | 17 + scripts/smoke_us_news.py | 39 +- .../pages/crawler/components/SourcePanel.vue | 21 +- web/src/pages/crawler/index.vue | 9 +- web/src/pages/crawler/sourceStatus.ts | 21 ++ web/tests/crawler.test.ts | 37 ++ 11 files changed, 577 insertions(+), 39 deletions(-) create mode 100644 backend/tests/fixtures/us_news/collection_failures.json.gz create mode 100644 web/src/pages/crawler/sourceStatus.ts create mode 100644 web/tests/crawler.test.ts diff --git a/backend/src/alphapulse/api/crawler.py b/backend/src/alphapulse/api/crawler.py index 3881564..7b319d2 100644 --- a/backend/src/alphapulse/api/crawler.py +++ b/backend/src/alphapulse/api/crawler.py @@ -185,17 +185,22 @@ async def list_sources(session: Annotated[AsyncSession, Depends(get_session)]) - select(NewsItem.source_code, func.count()).group_by(NewsItem.source_code) )).all()) latest_runs = (await session.scalars(select(CrawlRun).order_by(CrawlRun.source_code, CrawlRun.started_at.desc()).distinct(CrawlRun.source_code))).all() - run_stats = {run.source_code: run.stats for run in latest_runs} + run_stats = {run.source_code: {**run.stats, 'round_running': int(run.status == 'running')} for run in latest_runs} statuses = await redis.hgetall(STATUS_KEY) db_size = await session.scalar(select(func.pg_database_size(func.current_database()))) attachment_stats = {} for source in sources: retries = (source.history_cursor or {}).get('attachment_retries', {}) pending = sum(value['attempts'] < 3 for value in retries.values()) + documents = (source.history_cursor or {}).get('document_retries', {}) + document_pending = sum(value['attempts'] < 3 for value in documents.values()) attachment_stats[source.code] = { 'attachment_gaps': len(retries), 'attachment_retry_pending': pending, 'attachment_retry_exhausted': len(retries) - pending, + 'document_gaps': len(documents), + 'document_retry_pending': document_pending, + 'document_retry_exhausted': len(documents) - document_pending, } return success(SourcesOut( manager_alive=bool(statuses), diff --git a/backend/src/alphapulse/crawler/spiders/base.py b/backend/src/alphapulse/crawler/spiders/base.py index 1472c75..221425b 100644 --- a/backend/src/alphapulse/crawler/spiders/base.py +++ b/backend/src/alphapulse/crawler/spiders/base.py @@ -7,6 +7,7 @@ import uuid from collections.abc import Callable, Coroutine, Iterable from dataclasses import dataclass, field from datetime import UTC, datetime, timedelta +from email.utils import parsedate_to_datetime from typing import Any from zoneinfo import ZoneInfo @@ -67,6 +68,9 @@ class RunState: history_deadline: float = 0 official_cursor: dict | None = None stats: dict = field(default_factory=dict) + completed_documents_safe: bool = False + cooldown_until: datetime | None = None + document_retries_started: set[str] = field(default_factory=set) @property def full_scan(self) -> bool: @@ -188,6 +192,7 @@ class NewsSpider(RedisSpider): def _on_item_error(self, failure: Failure) -> None: if self.run: + self.run.completed_documents_safe = False self.run.errors.append(f"新闻处理或落库失败:{failure.value!r}") async def _on_closed(self) -> None: @@ -238,12 +243,21 @@ class NewsSpider(RedisSpider): if history_saved: cursor["history"] = run.official_cursor.get("history", {}) source.history_cursor = cursor + elif failed and run.official_cursor is not None and run.completed_documents_safe: + # Retain completed reads and explicit gaps, never failed lane positions. + cursor = dict(source.history_cursor or {}) + for key in ('checked', 'document_retries', 'attachment_retries', 'bill_retries'): + if key in run.official_cursor: + cursor[key] = run.official_cursor[key] + source.history_cursor = cursor if history_saved: source.history_position = run.history_position source.history_done = run.history_done if failed: source.consecutive_failures += 1 delay = min(BACKOFF_BASE_SECONDS * 2 ** (source.consecutive_failures - 1), BACKOFF_MAX_SECONDS) + if run.cooldown_until: + delay = max(delay, int((run.cooldown_until - now).total_seconds()) + 1) source.paused_until = now + timedelta(seconds=delay) source.last_error = run.errors[0][:1000] else: @@ -293,10 +307,22 @@ class NewsSpider(RedisSpider): headers=getattr(self, "request_headers", None), ) + def block_until(self, until: datetime) -> None: + self.run.blocked = True + self.run.cooldown_until = max(self.run.cooldown_until or until, until) + def on_request_error(self, failure: Failure) -> None: status = failure.value.response.status if failure.check(HttpError) else None if status in (403, 429): - self.run.blocked = True + now = datetime.now(UTC) + self.block_until(now + timedelta(seconds=BACKOFF_MAX_SECONDS)) + retry_after = failure.value.response.headers.get('Retry-After', b'').decode('ascii', errors='ignore') + if retry_after: + try: + until = now + timedelta(seconds=int(retry_after)) if retry_after.isdigit() else parsedate_to_datetime(retry_after) + self.block_until(until) + except (ValueError, TypeError, OverflowError): + self.logger.warning('忽略无效 Retry-After 响应头') message = f"请求失败 {failure.request.url}:{status or repr(failure.value)}" self.run.errors.append(message) self.logger.error(message) diff --git a/backend/src/alphapulse/crawler/spiders/us_official.py b/backend/src/alphapulse/crawler/spiders/us_official.py index 5224add..1ec3d5b 100644 --- a/backend/src/alphapulse/crawler/spiders/us_official.py +++ b/backend/src/alphapulse/crawler/spiders/us_official.py @@ -12,6 +12,7 @@ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from scrapy import Request from scrapy.http import Response +from scrapy.spidermiddlewares.httperror import HttpError from sqlalchemy import select from alphapulse.calendar.umich import pdf_text @@ -105,6 +106,7 @@ class USOfficialSpider(NewsSpider): self._starting = False def first_requests(self): + self.run.completed_documents_safe = True if self.name == 'doj' and self.cursor.get('transport') != 'doj-api-v1': # Retain retry evidence; HTML discovery cursors cannot use API pagination. self.cursor = {k: v for k, v in self.cursor.items() if k not in ('history', 'realtime', 'checked')} @@ -115,9 +117,18 @@ class USOfficialSpider(NewsSpider): self.run.errors.append(error) return [] state = self.cursor.setdefault('realtime', {'entry': 0}) + retries = self.cursor.get('document_retries', {}) + due_documents = [{**value['document'], 'document_retry': True} for value in retries.values() if value['attempts'] < 3 and value['next_at'] <= self.run.started_at.timestamp()] + if due_documents: + state.setdefault('pending', []).extend(copy.deepcopy(d) for d in due_documents if d['url'] not in {p['url'] for p in state['pending']}) due = [value['document'] for value in self.cursor.get('attachment_retries', {}).values() if value['attempts'] < 3 and value['next_at'] <= self.run.started_at.timestamp()] - if due and not state.get('pending'): - state['pending'] = [{**doc, 'attachment_retry': True} for doc in due] + for doc in due: + pending = state.setdefault('pending', []) + existing = next((value for value in pending if value['url'] == doc['url']), None) + if existing is None: + pending.append({**copy.deepcopy(doc), 'attachment_retry': True}) + else: + existing['attachment_retry'] = True bill_due = [{**v['document'], 'bill_retry': True} for v in self.cursor.get('bill_retries', {}).values() if v['attempts'] < 3 and v['next_at'] <= self.run.started_at.timestamp()] if bill_due: state.setdefault('pending', []).extend(d for d in bill_due if d['url'] not in {p['url'] for p in state['pending']}) @@ -136,12 +147,21 @@ class USOfficialSpider(NewsSpider): def advance(self, lane): state = self.cursor[lane] + if self.run.blocked: + return [] if configuration_error(self.name): return [] if lane == 'history' and time.monotonic() >= self.run.history_deadline: return [] if state.get('pending'): doc = state['pending'][0] + gap = self.cursor.get('document_retries', {}).get(doc['url']) + if doc['url'] in self.run.document_retries_started or (gap and (gap['attempts'] >= 3 or gap['next_at'] > self.run.started_at.timestamp())): + state['pending'].pop(0) + return self.advance(lane) + if gap: + doc['document_retry'] = True + self.run.document_retries_started.add(doc['url']) if doc['kind'] == 'bill_finish': self.finish_bill(lane) return self.advance(lane) @@ -213,6 +233,8 @@ class USOfficialSpider(NewsSpider): elif self.name in ('congress', 'federal_register'): data = response.json() key = ('committeeMeetings' if '/committee-meeting' in response.url else 'bills') if self.name == 'congress' else 'results' + if self.name == 'federal_register' and key not in data and type(data.get('count')) is int and data['count'] == 0 and isinstance(data.get('description'), str): + data[key] = [] if key not in data or not isinstance(data[key], list): raise ValueError('官方 API 列表结构未识别') if self.name == 'federal_register' and data.get('count', 0) > 2000: @@ -228,6 +250,10 @@ class USOfficialSpider(NewsSpider): docs.append({'url': f"https://www.federalregister.gov/api/v1/documents/{doc['document_number']}.json", 'kind': 'register'}) next_url = data.get('pagination', {}).get('next') if self.name == 'congress' else data.get('next_page_url') else: + heading = ' '.join(response.css('title, h1').xpath('normalize-space(.)').getall()) + if re.search(r'just a moment|access denied|verify (?:you are|your)|captcha', heading, re.I) or response.css('#challenge-form, #cf-challenge-running'): + self.block_until(datetime.now(UTC) + timedelta(minutes=15)) + raise ValueError('官方列表返回验证页面') if not response.css('h1'): raise ValueError('官方列表页面未识别') pattern = SOURCES[self.name].article_path @@ -238,6 +264,16 @@ class USOfficialSpider(NewsSpider): continue if re.search(pattern, urlsplit(url).path) and url not in SOURCES[self.name].entries and not re.search(r'/\d{4}-(?:press|speeches|testimony)\.htm$', urlsplit(url).path) and not url.lower().endswith('.pdf'): doc = {'url': url, 'kind': 'article'} + if self.name in ('treasury', 'cftc', 'fincen', 'whitehouse'): + container = { + 'treasury': 'ancestor::div[h3][1]', + 'cftc': 'ancestor::tr[1]', + 'fincen': 'ancestor::div[contains(@class,"fincen-news-article__content")][1]', + 'whitehouse': 'ancestor::div[contains(@class,"wp-block-whitehouse-post-template__content")][1]', + }[self.name] + dates = a.xpath(container + '//time/@datetime').getall() + if len(dates) == 1: + doc['listing_date'] = published(dates[0])[0].isoformat() if self.name == 'fed' and '/monetarypolicy/fomcminutes' in url: # The URL identifies the meeting; the calendar gives the release day. match = re.search(r'Released\s+([A-Za-z]+ \d{1,2}, \d{4})', a.xpath('normalize-space(parent::*)').get() or '') @@ -261,6 +297,12 @@ class USOfficialSpider(NewsSpider): return True return day >= begin.date() docs = [doc for doc in docs if in_range(doc)] + if self.name in ('treasury', 'cftc', 'fincen', 'whitehouse') and docs: + begin = self.run.history_target if lane == 'history' else self.run.cutoff + dates = [published(d['listing_date'])[0] for d in docs if d.get('listing_date')] + if len(dates) == len(docs) and dates == sorted(dates, reverse=True) and dates[-1].date() < begin.date(): + next_url = None + docs = [d for d in docs if not d.get('listing_date') or published(d['listing_date'])[0].date() >= begin.date()] if lane == 'realtime': # OFAC's recent-actions entry is a descending day-indexed feed. if self.name == 'ofac' and docs: @@ -271,6 +313,9 @@ class USOfficialSpider(NewsSpider): threshold = (self.run.started_at - timedelta(hours=24)).timestamp() self.cursor['checked'] = {k: v for k, v in checked.items() if v >= threshold} docs = [d for d in docs if self.cursor['checked'].get(d['url'], 0) < threshold] + retries = self.cursor.get('document_retries', {}) + docs = [d for d in docs if d['url'] not in retries or (retries[d['url']]['attempts'] < 3 and retries[d['url']]['next_at'] <= self.run.started_at.timestamp())] + docs = [{**d, 'document_retry': True} if d['url'] in retries else d for d in docs] if next_url and official_url(self.name, next_url, response.url) == response.url: raise ValueError('官方列表翻页重复当前页面,未推进断点') visited = state.setdefault('visited_pages', []) @@ -287,8 +332,54 @@ class USOfficialSpider(NewsSpider): state['day_complete'] = True yield from self.advance(lane) + def document_failed(self, request, error): + doc = request.meta.get('document') + if not doc or doc['kind'] not in ('article', 'doj'): + return None + state = self.cursor[request.meta['lane']] + if not state.get('pending') or state['pending'][0]['url'] != doc['url']: + return None + retries = self.cursor.setdefault('document_retries', {}) + previous = retries.get(doc['url'], {}) + now = datetime.now(UTC) + if not previous or previous['next_at'] <= now.timestamp(): + retries[doc['url']] = {'document': copy.deepcopy(doc), 'attempts': previous.get('attempts', 0) + 1, + 'next_at': (now + timedelta(hours=1)).timestamp(), 'error': error} + self.run.document_retries_started.add(doc['url']) + self.run.errors.append(error) + self.logger.error('文档缺口:%s;%s', doc['url'], error) + state['pending'].pop(0) + return self.advance(request.meta['lane']) + + def on_request_error(self, failure): + status = failure.value.response.status if failure.check(HttpError) else None + if status not in (403, 429): + requests = self.document_failed(failure.request, f'请求失败 {failure.request.url}:{status or repr(failure.value)}') + if requests is not None: + return requests + return super().on_request_error(failure) + async def parse_document(self, response): + try: + async for result in self._parse_document(response): + yield result + except (ValueError, KeyError, TypeError) as exc: + if '验证' in str(exc) or '登录页面' in str(exc): + self.block_until(datetime.now(UTC) + timedelta(minutes=15)) + requests = self.document_failed(response.request, f'解析异常 {response.url}:{exc!r}') + if requests is None: + raise + for request in requests: + yield request + + async def _parse_document(self, response): lane, doc = response.meta['lane'], response.meta['document'] + begin = self.run.history_target if lane == 'history' else self.run.cutoff + if not (doc.get('document_retry') or doc.get('attachment_retry')) and doc.get('listing_date') and published(doc['listing_date'])[0].date() < begin.date(): + self.run.stats['outside_range'] = self.run.stats.get('outside_range', 0) + 1 + for result in self.complete(lane): + yield result + return if doc['kind'] == 'register_issue': root = etree.fromstring(response.body, etree.XMLParser(resolve_entities=False, no_network=True)) if root.tag != 'FEDREG' or published(root.findtext('DATE', default=''))[0].date().isoformat() != doc['day']: @@ -459,7 +550,7 @@ class USOfficialSpider(NewsSpider): context.setdefault('unmatched_texts', []).append(copy.deepcopy(doc)) parsed['extra']['selection_reasons'] = context['reasons'] begin = self.run.history_target if lane == 'history' else self.run.cutoff - if doc.get('attachment_retry') or (doc.get('bill') and self.cursor[lane]['bill'].get('retry')): + if doc.get('attachment_retry') or doc.get('document_retry') or (doc.get('bill') and self.cursor[lane]['bill'].get('retry')): begin = datetime(2025, 1, 1, tzinfo=UTC) outside_range = parsed['published_at'].date() < begin.date() if parsed['extra'].get('published_at_precision') == 'date' else parsed['published_at'] < begin if outside_range: @@ -487,7 +578,7 @@ class USOfficialSpider(NewsSpider): retries = self.cursor.setdefault('bill_retries', {}) if not context['bodies'] or context.get('missing_material'): previous = retries.get(context['url'], {}) - retries[context['url']] = {'document': {'url': context['url'], 'kind': 'bill'}, 'version': context['version'], 'attempts': previous.get('attempts', 0) + 1, 'next_at': (self.run.started_at + timedelta(hours=1)).timestamp()} + retries[context['url']] = {'document': {'url': context['url'], 'kind': 'bill'}, 'version': context['version'], 'attempts': previous.get('attempts', 0) + 1, 'next_at': (datetime.now(UTC) + timedelta(hours=1)).timestamp()} else: retries.pop(context['url'], None) if not context['reasons']: @@ -511,7 +602,7 @@ class USOfficialSpider(NewsSpider): retries = self.cursor.setdefault('attachment_retries', {}) if incomplete and retry_doc: previous = retries.get(parsed['url'], {}) - retries[parsed['url']] = {'document': retry_doc, 'attempts': previous.get('attempts', 0) + 1, 'next_at': (self.run.started_at + timedelta(hours=1)).timestamp()} + retries[parsed['url']] = {'document': retry_doc, 'attempts': previous.get('attempts', 0) + 1, 'next_at': (datetime.now(UTC) + timedelta(hours=1)).timestamp()} elif not incomplete: retries.pop(parsed['url'], None) if reasons: @@ -546,6 +637,7 @@ class USOfficialSpider(NewsSpider): def complete(self, lane): doc = self.cursor[lane]['pending'].pop(0) + self.cursor.get('document_retries', {}).pop(doc['url'], None) if lane == 'realtime': self.cursor.setdefault('checked', {})[doc['url']] = self.run.started_at.timestamp() return self.advance(lane) @@ -556,6 +648,7 @@ class SecSpider(USOfficialSpider): class CftcSpider(USOfficialSpider): + custom_settings = {**USOfficialSpider.custom_settings, 'DOWNLOAD_DELAY': 3.0, 'CONCURRENT_REQUESTS': 1} name = 'cftc' diff --git a/backend/tests/fixtures/us_news/collection_failures.json.gz b/backend/tests/fixtures/us_news/collection_failures.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..fec6ab5157a9b174d9cdcb5b1fd543a6a81317f8 GIT binary patch literal 87771 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%0D%p#LBdW{|l(O*haNvq-Bi?0ezy*&2hEYi%RXEz> zg56el!G*k~nK=twpc2X{)G1x)LjFX8IEw+Z_DsydJUm!@ggmxOZdNl4$#CNK=49+; z%Hvl!P0p#{xhS~yEad5I3a6n&tPb5h^3gCS1s>B*tf@*T7%S?tDsNzZLp%T_&bWA5 ztlAbPR=4R~MK&n=l~u3uAr-`-uS4@YP$9k!as`6Xpv$7!cwSoWPAFd1!W`_Dczr;{ z?@7WI>iN!8ZEmXtnd~9p@XW9yi zyLd#;qRMTyu5nuPI#G%|Twq}F5T;LFn>?ep#WD&n*$*#a9r$ycMBb21qy|Xx2|AN# z@r%NwpCu}aybzPJseZ`SyfK$|^3UR5%u1Wr)a72Qe3W+Rj|LQy$PR~G|KoGhfA*|8 zH?;jX8Q{=yMHZu}LCR0_2Rf;UoftB_h*B+Vaq=l|aEcpuSc}`I!ug7|;`;CS)&*O8 zmUsh(8=2vQ;6N$maP3sDt!Ntj?>&+zy{gr!oG-I zZ5q0YTsf7@r7PxMNobCFcCy(~PP|^A>>r+C?ks7kx+_y3qM=)7z|f*TP_kemmYPxbbSVXPP} ziAl}Jib>6KOxoRex4*km!07A<7V2 zu8Y38T##iki!Pb+Vib9?bVbatul_i(uTj*E0QNN=EB4iH(TyAs1%tzO-NFp5UCZ~C zPE3*B376)pO#naPrOU`kxzI?4gSE$rgF1?+Mtw!ANgUK4D-N3XR+sFBBQN%>z$#Ih zc${=*N|Sb8XR;ke?0Q05LIZh(sHmOlts*(p9xEyuvr+NG2<;DIB^UDg6v!E3lFf2F zV5i3dN5+FXvI2&F;as%X4Xja*M3-&!M)UwHq4sEi^6<$BZ|{(F0**ox&}N31#-l_` zy^V-jH&!&sCF{4QziD3rTx}Md%zg8Ds?y zeJs1lTrcpXSdQVCa(8!n{=VdKVXNL=F}0Pt{@Afqn|EQd8^i!R@H0wI3npW?&=xSDd5`nG<6L3_0Oyv29=hSoCZJ+29QsBy9>SYW9J2&3 z5rbG-MslvUyr8hv$x!!kUh8P2deRyzW_s8^R@IX>?^=h47~Q%~K%S3*U}DBfl@OB9 zR?^`kOd~?Lmm`Xg@LC5L7*j*VdL1yUjhF>Ir1@AiNm~6IDm6RN1;3wu6XGRyK|VM2 zf_T2^^}NLLImL^Y@dn8D!cna3Gk85|AEwnGC7v2}v{lqhad@Mw^Jow6+I;)4sE2rJ z#eHS_t+GQN2Pc(IWts4DW9#q_5kp*qv(Fh?0yjXVb9B(IcnzPL|OwinPw}8w8mpa+Bw%l1?ySB-ZXhD#(oB& zCl>=c!zkl%ViXrZw3T{OV$`D*K<3@+l;1Q;bJ~e3#1n=Y0TJ6|wM(XC$tdz9EyF+k zQR1K8B>Zcwn1;YV{jpk~Y1*yF@RZ_F3cSMP=hCBlMpWPcY>0Iyp_^P;QI6fy9w(mF zk=@hv6}=|$?2(EYM!S7)ad_JY2pld97bM@t#Jx0E!f<&58M@s8W_U%XDv@`0-3n6L z$4B%Cak3`PUVbl)I9YqFI63dSWrxcY`y)T`hCWl?U`i#Qbz#hadmSKePOO}UiRDu& zWbT!hiM<(`wH_y$>10#ZTB)}rn(2=f&E{Q?bcRDXLD6<6yGqW9x20I8WXRQgw8&Lk zLGzk_kZZ;T2D&~aAd*^6I7;~Cnirs7lpk|DMvzGv9k1~1>?L=Dubfnr&M4_4WL#g8 z-Y7G)q9MyNG<_IaQ8K0^D*mY8QNYLZ2r*J49Z5sDf+9w0j};^9i#r@6)(Ou$?%Q$} zc$6<3^>)IbR@&1Nk7p-g7YLzYsD>#V_2563MPx5K>DHG+%g1@ofo5IiO0BkHO0;~e zW?e?Rb~gbeo(jrP=Ges3b`3VzSF5P5!{W$MYGy`qMjOL0d1#8WCtaxpHg#rhy$nb_wWJeaxsfd64 zDj7FG!KFGwJoC{aUSmb8t<;T2k9fxI#)>(i2I%BI&@p$cP$pS^cZZ${oI(<*vjQM~ zz#MFbzHdb;_v>fZ6m;nbm`!z)VdmC=3~mtj~5WE^-}-3&b+=f#RfQg^MsQj_*K z+M^YW^to4cAi1E_EeK+#XL$jj62~Gz3=t*wxfS9zh)pb?&nH8)$9RL%QcXg%dTmAH zjAg58K3YVZcZ0GG{A3?+Ng5GSdRh++Uoks8_Ok2SWE(VBO4B4m{vs~bU2XsFLVOPS=~deb0r1hEXxOB7k6RKf_|3A2Q< ztki^pt@h(YK^=L$T4P0TOB8&pG_KW}8Rhc_%2OmV3fM z4?UNGI~*nGCmMO&fGaLLjvJ2_&uS}%zGBuNJ)YGUmu=%zxE0|aI=(evj)k`S+=ySk zApQ2~TfjJV8zXgMHZO^Ci(a3=qll;uPYBuCbWYeTDt~UtFpqIM%~p?5I%5S>dZ{); zO;45&8a{YEnYn#Bi8Is|Bwd|Moc*j{;{oQ&%H)y)#3Rf1$7-4e`zS5`tlF>1#&TJi zv-U6{0*(Lt9CIwiB^hcL|CP>8E^~9PaV1Trtd!()UI4XIT>BGSUB%pHlnWK!Z1A+q z3Qx*BBUST6j^B7_1#@O`a%hDvCb9)%Q)_xLg>GqjY)-^{WH6_jxqa3TWHSP$vf9K& z@fw&iDx&1bR1wu>rd3k)j!U%AG{N_YY*&+$RGZ#-SgANoQ_b8yNqe%9GDv4n5moyz z(zI@Os$zbEN~Y&ob>s^f1muPZUOdglmdYp7(#pjAR%1qD)r{}ClEpk@v4>*bb)0P$ z8?S;$xnxD-Qrb~yB4RxhFZdr7Rry^j0%0=BEdohV(-Kh^9a~kOxrsUeQBZ|GNA!=( zw^ddkHzJypzA?M3tZ)EQu!ws}cL_0wONNm2GVs#X$_Y@|fI>}Cs|o@F{~?I*XD(&U zqkM@Nbh|(evye}OM^bq_jw!RUMM<7#SCk#6lGsdHX);BbwN{Q_CFe~6*4sck88T+~ zS$xXk#2bXcxawh0r%FE=+y&67W)K9fNzGk@^R+%^11q|CK$ti(-DUW)#+t;#g@%?A z3rPqvl2J|+@(BiS7vsx#0NlvAtjfL`4^-nD6A2($VUVRDd?rX(em_(0MVWdr;vsHu zf@AuePy0xL=y@gnuSlO*_Reuzdacp0RK3|?YOSs{)wXLJs#UKyx=lx~J8d0WW>(~S z*J6A!TVwetSImn|x;L&*Q!&%Cin=HHg0f(f0_n<2`f5BH4AAaUd?f-Y4jTDRFmcoe zpPTSn6_Y{e7UxrB3sjjVLXsvQZ|!wXw>Fi%-Gi4Wlb8m-0M!#TzDsz9d5QLW)>Rh2 zs+tS-pMMs*z5L!@%jX5?WOoyb_uT1*qoj&TL~aLIY+fiA>BH=HMO*zO8OAHs>X1lA zr59d0%tzDiaRpGiYS}hA62#TB6Ku8`kGioPd0iG&19tUi5)M849F~B8KG!Mh@QEyh z>_{Hi0%=vY-~jPu$cVpvpvL4tcUV9%dG9hvL5H3V*M1GUF|x_SluId+98Q|@Jjpy_ zMbxI05PnD|$yxS1+1fKV3^DLvmYA0Rggp;OH)VC7kw~>jo_M{vWQiB~DjKAB-A3DV z8*0sLxN6OA8fsf}Y}IbI>Mho6T3z>tra^oP)covbA0<&L42cd#$_bl%Bgde=jGKLvklm+)f-O3XtHh%rb=U7Q@72QU28N8R~2#- zU0q#OY&W58RJ~%|7#bYnRhB&-(in&jqTdQ1REqjD2ck3EGMY`h<-%xcOw%>ZXfa1? z>3XAGGuc7|;k_5g*&Ehfcnof>Yu0N`t6tOV4ZR8I)3qB;t?ks@MzdwNi}yC$hr5NI z;CJWtKH6@#4*z4i)z!Q0Zns|V)(q3B>z2k^PQ6v^gOlp5twa0|Y5&&Y?OkYYORGDr zE=*}nv+8ZuZrb%)P1kL=saZ9>*ab`wpC9bvAl!x>0;fs=vV&0B-s&84A_X6BA3~GW z+ZG@YAXLq%HLQkXHdwdbbQ%q}&6@TsC_A=|IYhdi?7A zM%{H`0GizdjEA{59m_FVj^@&u4sS!PH%D6?#HE6lrpSVVq--nbqphDjlKH5HYX!p2 zhlvzl5cs)!8E^MS(L!6e#_BZ-7T173R<~~1UAN`dYo^h3+O@hhy@dsI%yuJ!&hB|? z*9)pdz&z*r&)#sAIR2B}&DFYN8>a0xx^=DItZABQ=q6&VR%fmQI7GCw^?J2hSBOewPwB9(9E)p0f>ffONd>EL*&aw0r_nf29zsw`zRdEGMmgW z>UFbO>#}-RgIQ_R+Ip?dTJ2_ICb1pG7NHCjI6LX!4Ma!L z-JdjjT43#4aHe)0*w2m)2VA3WI2xSxwXWgTbhFvIbvd>@7|*E(h)7n>z^xRw2C&5g zFHxX`P#ka?m=<1Oj7_&`nL3bc7*P%IwAQpuK$f~`nvUCa+~OE_17{Q`(fE*DN_+hz zeFH=Ff?jc>h|c`C~(DpCa{p zGg(3r3iPQDG|X-^O(YIo7dXDqP5Nv%h=;INVI*Xd0{H1iDX~m!7Tf??+b013zBMdP zAkK+3wRAGfL8QQ6sB!T_G6W&T_UG{= zQ*Aj83UGof(4q63SsBhu@c$Kf)IU8JVV22*cQ> z02SdW!p;AUDS*9b8JUx+O)FC&bU|CX3QA&$)l>db94`j)GG0-FFks3r-T)6uD@gwM zf)X_~bq|>u2ZtCc#KmX7sHy^|Ocw-wL{J6$e%gb7D<9(}1-^x0&&MQDmOlmwELKz% zC3>8KVWL>^IIsnR=LtlQYC$UlE5HjJS~qO9`Z4|!wGJKC(6w3<$g@h^dzCksHBIBY zf?XP3Rgie?0bWMR>h!Cd8|6R${R2z8qkuE7*wNFaztS?5;VAAuwW8jLPLw5_L4}w4 z?PL7gvI1yBUM)|TmawJlE9`mK>p_X%3M2jvi{-|Q|4%cy**QWkx))gkoG4{-rt(Yw z(Ymzw$CO6MDRiu1#o^(Q0r`o*iueM6$2?aOYWC7aQTM$7*6U9Tvu}-8$&6jxkes{B zIk$rIk#!Cb6{lL_gj8ejk%}=|!=cOq3yVfuBCo`b;S5Kj3nkMx37Uh(DlfhE7v3Nw z{SQx8;HeAe{aFM|^2&!NA3nfwvCO|70a-@v1-{02;`ZT)SIjBKq0+<^MzQp6+;IjT z^ta@KYynHh?E&qE-m6POUC+QqO@Ni`Q_-gjdg#fxjD&u!4is zz95)g*!&oEcF4zpgepGoTk$zVV9triohM|DKMaMDH9kDo@s_=rE10VnjAtF?+& z(MnIwBERV{*Bbdr)dd+R20{-1JKwy%3cNSLr>*Ao;qJld)$`W}$*ZxeZO7hLYhz$O z|Ga*FqV5_O_AYi7+dyVkIom+u4G_+N{?@xt_v-Zws+ugLAiTf^TB=x@i;%)!v#q&4Y=Z8g#97#wVjNY1EIb&7Jp~?0j!{^uB5K z=3s~U*s!At2h<2>5ors^2UW=qoTm%`PH_68;zmS-9J!~H;PdC8ea}T354Shw*4y=Wx_NA0O@1*5BCnoJFhu?=KojMBY)Fs#2Pzb6h2(mZ?De|74+HSNX0!Ft6x-0!^Z z8}DA*Fy$Xlo}XKLSG(@3=a-io?<-c<-UeC6TWM%t{&*^-DG;PQ_vtSVuyc5zSCB1O zidZs=f;Nby(2H2NWDi`rdR#@0eG~zOtfGk);(ug~ zSEtuFUQMC2BL=MZ7Pnw|`WtTv#J;$KTQU4oj&-ygF41sTMt`K0(KDw4@)KZu3w1F{+(_Ll(sS(tpdTFz!rnd}} zHCuMg1hh2k-6qqTM!V*;n((68Hndi&rPs8!F@5aRbwfRSd2rF%>0V!S)~{dMZ{Lmf zE)MkuOv2vI*@b=i;!QF)(@S|WxcxYT|Bq0tl`Oq zE&#jz?akgyUqkF}otvU#|1`tE2;5&b;Fze>*UO>sOFp#21$ z#D*ja!_Tm{qR1Y@9&ie*8pQvi8~$gATHLk))kF#)eH4>5%qVeHG{mlc1MSR?+I-nQ zX&mpIZoMp5&MdB&Sy(Caq6RjKS$c2rvjo9nimYMela<-DiW(gEefwjbZb_X5z1+~8s7v2Aqn4g__s}dexdkH0tu(N zlAEOHQglsung@jIzu=haEtR#kA9jIJ!yxYi?=#BcxfV&QUsmb46*zr3^S2`pP7!}x zsKqrc)0NOm7nQ`*U97^E@oc8jk7b2&`J&1W@QbYy*_}c_&!%~j_a^er0>p117JTih zu62pUD)3LHF{c}i>LoF@NRUG}sffm^k^!S7oUklbZj@{t(KR}ePPn;Ik=i@%Gb9Dd z02pj*mDF9ik(M#Zc1-`BkD`yA^Wl3-Zy5W%k(yjN?>-;DefN4@bDm$jhdy(TUcEAt z_(<0(Q2tNO7Hf0ZVm=d-jPZfD9lz{tc3xd?)X&}?t#8E9>yIz3adLQg8MT4@s_Ta* z$9w&c&);@NAJ0B+zCOHuadOe!{?xYyXBW=d*y(y_dT^j`T_(q87d2Ns{Cu@LI(%d8 zp1eD0)n0C>&5`x?!0sG&cE<7hk_G(AscNdu#oN7uP4(rA{c&r{efiPvehSZCkKUVa z-y0{pZ-CY9bUu5R+C^@v^q*#`a)N*quY9i?S(uBa>Y^s>ip3$Ey&vh)F4Md;K%hqk z3Z{)kbA+{-tzFjCxbF?(J2&}_t_S!oqjexVH^DBia`Hy+hdrw{!3LCHCvYC4>{4N_ z^|&bgODbdlTaOHV;rb5%QwPJ5A2ZUjbVHcG2r#Sq;mIE$k^tF9z5a(M$Y7olNnB!; z^~1yc<8yxauNHsa-2C-rx%jGrE@nqu?wk2#e!a@j_Evw~Cvz2`4FE%jh?@9nnw>iy2?sk*uIdK2(=IVY2)d=(P{TEu6N&5~h&Ac)JR zr9W6jSc!*&2o8ZrwUFmxB-`YNR)O;yUeg*QjA22lVN1&qy#4Ye9ZQ1ii0?=;43?8+ zYuQ_N(jhG$ismQhpb`fjw4Cg-q#rt~>`ypGTw=~V7+A8J;mLBq;Y?PYIRGXIU~qFI zVf0)MVp0wwUk4xLtteT2Dp}8^N{=O{-T-EJ8A#R5jYGmOPDpMRlBF-pzlats$04^3 zjEsUD#IL`~4@qL#efsw6uObXY7*$Z|j6uSa%7BF1>i)^@mhzuQ^S>WHRH{ot%vR>i z6aWoiU51_mlOHZicxlCOBCOfyu)BTS+24Xf%T|Tc7Ypq!R=PmF@fN|VNIyFIQR*oP z?=$NIW|7i<*;{>TRb1E`va_2TYpFuV(Xv8?)80HiDIV|l3+dZ7Z&ZX&B&?|O?kjaLYf6?>J z=P-Gx@C&N)QRXY-^wCWlj3A{F7E*}<1r*@1Uw>s442g4+z#(*VQ??UvGJnZezPTy< ztO&KjufP6Mwr#eqnLUETu{eO$VuL{L%j%xYCJ*zvLc&@0{WTxxs$N^3BuJ}>XSW;% zL}I<%_U4z{CxpvDtK@qaq}Yb>`uNo99l*ZK)5`y7|6LAOJrHoQ`a$S0r0RN(YEMxP zZpyWUMBJ$Wvx(UA>#rZ4$Q8yT8HIt+RnqSjyg)|}oL_&Pe)p8<3P+wLYzc|A7bwos zT+%lLRC7rCHcis0lhs_i@WvvmBLO)6o{9Z%w3U9QTk_&ToIL$STl%uJ9CKs<3OcDe zjh1+sWs{jp3!F7Yg6D|c2W{v}xpQ-BH_v#V0c91o^7P5HH2o^Ssja0iUtn~298^%z zn)KJSC1{A@f`OckJS5tT)5_6S&b^$09)&U$m_lhKev0Akdg~(>7~&HUo0k?Y2_*5C z;sWq3&T_k(qsh=8^}HZnE&QVIzZ70oIxjQsl(U;$0{>HA(Uuc&K%(v6C}VTYPK?Z@%mN z*&AXvHU485g-OV-;eoTQ>Xo$bh%U*Y^`_Z&Y`d1e$I5!Gu8F~bRI?KB=RtPS55Ks6 z7vz6G1BX->E46lAR`2mU_jbLaYkI{nn-#s04&l?tvo8y6CVtFcz=K{fgq@BQ`d8~m(9w_0(hxRM~vv)BZ_FgwHPxgZS_ZR)P@p@-Nx7(-P z*KbLdvM)-{pz~EvE6RA z!Z)nFqx-uD8?TOb+_#bY=4fn$jbQtrZvTG#{-t@k-P_#j9J{;vb@Ddaez*D2+&@q_Ao8`!&BwC-!GbQ*t{p z-HF)>{PzzIV#EgFB||6~oK=5J67ki)tPA)UuY}9i#pc1uQN*?x2nd_Rf3JY33Jb6@ zd3aYmIYojAk@mlTAREaXIvi967UKDoI}rfUTqOCG^C-J5l$ert$DH z6ZwBpbd1zODs5;KTWTDszz>X-i2x}I8T+BBnG#=kLm4aw6+tKP8!e?NIEHLA;9(6& z1(&zgc09`udur&yQE3GrnDIy*{8`>2mVaH#Oj!#};;6e;ka_n`eYG-k%{1B~1->k4 z@i#ilK_g}@<89xLw&%Wqqi z6Go;yd>0qrl`k8%2Yr?7)BLGlti$bk)-ZWpK>KAQ@?fNem_r9FxeB9;IT0JD@pl%} zLvioANzPqTGCCPz0`)4?5vCwz>{up73o_gSUtmW9H{8VMrmoiKxyOTs03>JuUl0^zEW4R}z z6P5BuvuJ@lwThEhvX(H0bLkZ|eY}-oehtG<}(6xl_aN_BBgGxwYi zub;tM7EymXuw}XYq8EdqOHxz-ITtV?og3jHtQ6M|HY%W*GA&5v$=h_OwaHeAB$YPJ zzbIw!-BB13O$HYw{ZPJ#MJ18}>shq*_3~h|YvjN$thq`F&BW@@iaS`gE_6!dM=(97 z=(CMsIrE}LzL{BzqD;0>zAA-}6YEmCLfOehH|*jt&5iuN(`c2<;8f7c?+0O3c+cGq z={_&`Qv!Jfe5iWGK-jw-&Ns-jzG2E{_)~&P{s)0)-45ukDEU)@$v%xNUuA=#AC8e{ zQo}Sz(fD@IDWcg=2(>NpHRYi73Za5$z$^1SDTPo@Kjjn%DQC<;Y={_|Q{N80;N{;z z9c4FAj;Y%Hp)bVpz0+L%Z?F_IGS%e6$2}xJVfUMP>Vm`47n8YOU-G*Zl9HP z3%2bEvm<7YG1Q-k8wNxyZRBT}h;HaD051!eP$M2*fjk}eywEleSFElUb0nfei^qBJ&QW z<&6j&H3qp;FuP;*f{j(!zl&`2le9_7pMgz^8B(5}bdHyP!b!3hqHrfz>}TEQvFd}^ zNw!mc%+9=!wgt_hMrdh)mzss>;XlVD6ha5fvuqK?=|rG;EOk$ya76crIpQc5x9*tA+Qn zDO%rBrMOeO$coz0XwaR%QL}RKo$S?Rt^4oM5_ro8{qWYl#pLP;e{LW%$B5?SQ*KH! znfla3G|go6v>e-|7@8F&GRCMw)-kk6`NA@z02^4!Ij=XM&2 zQ8~%mIqx{lq#tUUODrQ7i;#ia+#(lnG}etWPAH$$X8@Co>Bv-IT769Jk~WfV<)XJz znWCxuXqZFHQZ7Ap@vIlYD$D?9FUwuj#o-`bP$lFOVt`(PZ?p`3J#7hu`W|#a88%8B zZpf8hx(bylbb!$5dEc+;qX}WacBa9&?RX;S?|(3fu)Shp^C_bxGL{`ZGSjqzE7pxw zBRghFR~IoZ9;gPE6=!$)q^dj{Oj$p9;C*Y1)|uUrhy0Xy35;1}sXXg6y}^(MX)pr; zuQ~`R45mbfC^y(UGuq`D#k04wq1Ux?@I~UEd%-^o@|>gR9bx>CWdHwwIl|>Bu!gD9 zxbewvvBJ|lqSOCLf^kcGBKPMg5ZT9K;Q3;ZbNO`9JCrmx=rwAyC_oFg zDa*EqeHwW@m2AAMq+JX5gk>eC0TY4~6hn<(G@?|R6=k3L2&0}Ac%Q|v5bE4`pnNrq zs4sXGqa0FjO{Jd+<}`$(RGLYb5)uQ;6Uq=`T7p5vBH3K`7jN* z93HxhM|oZ)H(QfxxwUla(g{Xx1u-IP02@!j6;jdvUlv|GH^RwJ>FBVripX#+3-7wA zOL7nbVkmq}<=`!YJ705xB%N;7p3*#&Cfnu|i3}z~YWV%^r@hSObBurBo zkK32=6iS}NaBR#y3(A5|@|J_YN=>uxECv;kD@o;13?+g&NELA+@}DZHG(N^O9MuUi zTdmr58!#6cG#V|}?zY^fuGh7i-R_!Jw^?TmLvK2*R=s90tJY$ySz~s+ z)#~Pko}E}Uan#$yj9m}Im^pkPUFH^FApQ#_%Z!AG)2*m8<}*{%%(z`DDq%Pmai`|X zl{?dHCpAM#RG9hf2R#v?#~i2Wx9m+?RGdrrQjSC=l|-SznWge5lh@;54y8Gj3RH>L z6UROOh2GG3PGF41GOZ6G!bB=qTygAc=-C&rTWIVAE4eT8gofqHpBeaY8w^6T4Ycm4`$MlhrZ^hEEaDLO2u3FlUi^3t-Y> z%`8HD%52ML;#rX*^QloWe8*5I0%X<#K54gO(}HkJ;?siF3iyR}&eJ;QxjN^(&Usm24B}SGH1IOO@~4j?FP;Wn91?iwu)qTKMdFy#3@R~ z9xLl3m7)!WVA_=;`S)PNJcYS#;oj!mur(L*>9z;^EUaku?B4`@j9iCf`TadABA0?wj+nf z&%1eD^INDQ-0R^tPQMkswpliLgW2v&7Gz}>%OU{#uV+K(D_(CNWiOQ|B7LOMOVz+1>eu1EVN)g+=c);~J8{r7`2Zy*d$4nQct9cSYNy$_=0;^wo^rjw5(Y2h>JS8khwDu1=l$Dw z7__9&{`9my-R_)jFX0?;9aI)V56u!#r$^1O7=?2$?z31aI2|eQA#kk7;cBgHtrb7F z4mO`%xe#uCA(G-kWJPg;5V42(>7KlHS$NYfv`&h9k2gZJ-F z@w${JLV--~=2Lh>VV;^{7 zDf$2=g!h3VG52LRUq@edb1lJ!^24Bqeo{&Z`!2%JnjY*@q8c)EV!X_MlUh7MJ-tw4 zo<=l0;PC7)NtAVD?SLR9p<|5!$^bU1P4N;hDfp%cQtn+cQpa;wHEJlb216hW3e7R6 zCw&VDw-sZ&#jfZ3M6WO@Na*`kRGg2B(utKJfGWrJ1_OyH&qIv!mZN0Q=a!EtA5h?? zsb0=NK;-s<5g!`T(|?G|PfuJ7eUi8s`rk-gFoP(+k-)3PaNyZ$I7)0lY9R~9s)v4> zYTUP+@Cu_$GNd(hoW~`^5VyL*5{uIW?&4LW445?+G=lY-6*MOZZCWpQA%^uS-554+^JrXKeBkrh}2OC|ca6EOw^xC;~M0HbgP96>Zt zURuCbTzFh;l0TM}tG;LViDC$X4Rw%a*ud-Kgm}QaV916cG8EErlDH8J*Art>Q^y0j zFY$qFdAR3*`RnIq(gG5MRe&_~czRc$SxBTuL{;1o1}#qTRuGE823-EO#N#_|A-z4h zh4l8{xP=mdCTih!LmkCx`Xwh-7)=chV>&F<5?9%h9hIVC!4PAGV{Q5|Ct3U(`hb&% z`oKe|%zbvx3oZZy0WoB_>yu>Ukhg>9@Pp?oci{s2Dm#ApaZHCQ;^^IE3EWia3_FT7 z97f?aoKp#dy+V^Q$R;D3^PI$y?zdr>aBpm=>8De~k9@0Vp=6TA>5j#O!ERJ!(1MD% z?xrzCnC>*fCP3gIc>%XA9p0ji|Aq6L8l2n{#6+I&;-_;ue03dz2j>oRcJSu4e|Ee( z=P<7|eze0{uTN2$`j2;5!%;$(I0FJnBJ4Q_}h4i=$X_;?m_&#PTST z!NOx7Q%Pe-jwfznBToPH;=iRg3Ovz> zm~x^^Xe1jCnGNECO`JX;A{?!WhY8*;y(k$$K_2S>uS!;wUxRKu{A2#V|Im(~pLR^C z;f0?EA#M=NbiKqej&^K z#tj+nEG#{hFP84OnSI*F6G3V@jp2(hN3&qvI!_hKOhR?`OSG438*d^pMsvm4n2sn4m%IR)d9N>_i#92gL(CkX0!M?TX7b zVGZ?TD^<`u3ZSFAz`UbX0qeyIR|!mBQ+>0p${A3Pw^aO889FYEx_p|*%Z#o?xP`}D zjw`+0Ko4CMt6~^kVM!I5HcERcu0&92^A8pI>8Xg;o}?mL`@2z*@^kObe26{+-iyP5}X~u8&y_&d{RBWGiR1TJkV5tO_AP{(aFl##K zK7lJbIK|-Ctz#lk#rsyhS*fR*-uqp+{%=?&;I3&+?V8s2+co4_93=i&wPKiPHTCVr z+d?a%47Ml{5KgL>Eb?5BoLPnP?Dox+_MmjI2jx}jJ$@{EAS6$*{&m_(s=eHZn23mu z`Us2*fLmjOQgXr%Y3IOQ#$~8v(sKUjN4mH^jIYoq7*juAg}`KA&6~ET+nrbO5?yYL zh7R#DVE}ynHJrIPnebU8igp~b4#9DXTSW@=z>u>fzQo(?wm zL^s^TGBco-4P@vbb*cXtFZ@Kt3qKk$;%3akOg^<*fP)0nmJ{AF7d8`GG{Aot1dc`A zW#iJ5DNz=&V}@-eoFY>7+)>tvvc)^0o5kHErg#**Bc&rc%6V1&ED}R-J0HNM1)3vO zOpKZVuS(zrYe3~>KnegCQv%P>C%}ncU=Gr2S#k4|PQ5g7kfI0W=F03D|B)Gfewo3X zA~@f{cq3^U=S7C*B}sK#AdK zW?1wf+SEJVhm5#47jjkq(2Jj+UKmsK!ub2?MZ^S)!lUVNlLIaq0q+h7%mRC?4&p8{ zF@Wo89FCIy6+;^^sxq5i9E+xvLo5eBMboeivZi_9ul~zYA*>=E<1J3>-{=1)^T^~oqmUw#FDV*h5Pn6c8RGwmjjd`jV0&)Pn1TdD#Bt|| zKH$U9>aiJFKqiMNz17!r9%wb~oJTBu;7OPSQ7;7%gGSBlHwD1>5dbhs0WcQ=!2A&a zFiQcbEd)UAM*yH!3P61!0O~&i0QFJ;8Vdo?_z?hTlmgIP2!Q5~06?=8fYw3)w0;Bt zTBQKA7XqNY003VF3@RyIAH&5L$H+hc8^#o3!XZU58gYTNhp{VBfr*e!p5f0#5|YA@ zh#e`Es!kO9_CQ&Y41hoXaGTAqh-7g2mjQ;c6=!*2st(|TB0fyOQ49=S){QCxq)x(* zjZU;xEt%eD@nx^jzGoB9Mg!!Lbtxh>#rumwimVQTR!N7?Q00A%Ls}8@q*S?Z1+sZ{ z6kjHsqQV zV%KBTZ*wBryq5mXMO@YuEB7L`iO{(#(xw*@IhBGTe1=O8QOZj^GTH4W2)5&#ET{4 zotbO#`;2rthhpiN7fPG>Js>Dehgbr_A{h|B$M|PuHk^s+Gm{y9kI~OeQdpKAZ^68T z3&4@4%#(_QugSDG>OK-Oft*JNkR$F&$r|np-R%_J^tJXA_Zgd;2-I^J8eNH?^4mjuV zrd68vXAx$kVc~zc@kmTT)5|h(6xN~a9kcCLOKUY$v)gK_HJA;x)o#{QyI$9wX4AGc zr}2|y=OCo8CiUfAD*%*7(?Lg>eg*xZgCOr=`Xh3!$mcO zgnE`24PkJMW&ru=^v;RCEAqP{_vs|^?W-V1~vu4XN9j)1D|2$+a7dM&DAk7TlQMd1MERA+=>6TvT zRu3g>Z&zW2OPTR6fel2di(!kgGc@a`7ViqMSRVMLN*Y1I+`RrFSFx7G# zkH@Cqbx>~+-xq03$90{iSqF01Xs9*dQq*qO&{f^6YnD^%HtQ5M{AWN~GLOjTsIA&D zGRGA>KVnH5|KcO0K=Uw(E8O894%a{U2gi%y)ETcRuFtN;JA71m1U+6Qyb@nN0@i!( zSk5M|rLw?*?_WLy#jY*cdD;gpMXspCf_NZ{(q33m%=%CchVO^MD~^gQil*>)Ul74* zI@YE#uU-3UTfqmvCmsflcz{o#>!R(FDN7W7LqYjF%|V>vVoGT_Hj<4`S&}CqQISC) z>Bx)I~M>4bKzMOXSX+k zBOi06gD{D9AkwZZD^FQ?s1-Db*v&aoVs^Ht+lRZIr5U|*X(vS2G_;CM!}{X%pJ0U8 zBk?U4k+&RvIqz3jR~6~kOR2DPuDCoW*t}K4+FlgPio3@ri`5Z9I>^gi)IBb~y1fmV zyBq4Bopf%ma|y^QE4&uZ>D1)+OnwJXYQIlHI?;FnyluY8cWs__prCLrAG2%OtQ&?G zgxVZ4^umHm?TMG)4z>hNB^VBY#i@(PBK!a#T^C?^I|+ndpD@~+4aS{w z<=u@#S}fx^hq@%q$1^0@!!(P#H5~fFMW^~PCXi>O+eqIecugCty!6^%C_yIu4^LL$ z2_x*DMZi|Be0cKV18nzY{`Cmf)w|}e@tx3mAMuK~k`9CNhThin$pX7^#~FCg)rxER zF@uj@yd8ze0Htq4@jWZ(jp%-Ch1>Ie!T$YGWciy6gLM;Gp|OoLO*5%0v}#B7<0iw< zBQz6Rm?8l1Yz6PLphzzadp610M ziG4@>O!|ZCn@`E$&zs14I=G486^JD_aMs==Fn|O`1jvU=dy`xzH&@o>&DFqy|KRV? zxw-bK-J34dyY_i+@Bm$05B!_YHvmkw7=V}Ck<}Ad0W_tE9@aJ7tk z>n(>FwT7iNoEmGGwRYF+*1NXmHf-kB9ouRdO~BzVz+TdxUCVZ_BxHcman<1#qJo}% z#Q&Wij*ea&Mi=9^2iM2fozec=jbzVGc3w610<(4LnZ0=b<>uMNo6&w)`56Bh+dxWJ zInnsl8VvscPB5`nHG1~NwpwKY{U#xFm7g#4f=B6t?=P9l??)?R`NR1*dOM!}cBcMo^{?+O<>k|V zUcRucUq-<%AIA2|(|`Hx+0q_<|L3=X6+Cp$QB$63Aj*YaP(?=EMu@nooeJAD7- zQWUMB9a-P~IC-wWe);ow{Pz2W`~3Sq|Mkazzj;1$e|d8OuFLSfqly0TMWp}olfC-y zD`x!swfiL&whOR_J4nRF2pTEkNN(Fq-1+7>%%2sPZz9wkLK4 zaMVs`C`!2|-sbPrXoL_S+~|hj^$W6#xO+&FdR4mB~JwAp>`n*~b#Mbi(uD$0f#>+fs zG+L9xF-;JVkvRzeSd)HTv)-_rjsL#37XLwDK5)B%2#YcF+sQaNKKuA zo|~VA5uWn8;W++h<>HMX{2t|eyFbPH`B%I(@=cPVHbs^$ljMiS+>HB1kAFzWUTzm{ z{olI|vv1SH4?pIpJJ_BvZ9=h`eDOW3Jl!Z&!;iy1=!)Iio^I5IbmD*y)Z{y!D`I24 zzBV7=OKd>kngo~-@JD9wYXaUhZlBX$+aTSE`Q;Xyi0zCMn_2KJF&^TtBWFEAOAI50 z&FA*bMr$KQ5o!oR@&ujXf0w~!ms#NvVZ;%N`H`*gAug4*CI)4gn6|*dTq%BMRSLdl?VL3rrpEPTXiT zeZYN<2gk>wF>n(dw;64#RQo+>ExBUkuSQcIqk?NuKF_ZmUx^;lvZL|=e%78$*658> zq?AC#mEs-ZvTxM2ZB6?CKO45qEA8C2B7b1~*z^W%^GZ9n&7ccx(|+?xyS8mAAIuH8 zO;vVnS2sWC=4S0umCo&E>1zR-tj&1K+bOjYLykQV?jLcC>Osc}S7f&F0e<2hq{#87 zm3`W!H2_;`i=Oq~lB#@Al|oDOtoN1}kg7yKeAa(UidAe!v0$uQom={lNMUPZ7%Gry zNV7g@H7jpiNO}G)5$tji9*E_|n$kpF4Jo!K#rE}+3cno}XfZ6t4{}vMpXm6ns>(df zHu@I2@U&cAe#TVgS#|Hr0A7|BZz`xf4%#F&i;EK! zuR>uIP|=&8Qy7s*Y)yjcz}AN*fp8%Y^M+Coo{8!HN>eAKdElCw zietUx+VVnpaeh7VX0wxcznN6zXN7p=s%?ShT68bZ5EPOs1jZTx%XrR(Tl~V?Q+Gz|E zQ~>%K##|B}M-h1V>jc#ieWEfzMErA#dpI$MviwB_T#D1VZYWaM7tvqLu7p|!5B(3Q z9I;ot&p@varzNz-a2lW{kxz?Zf+I@Kb($3vxve{!;#wf;n&}y}cuMMxmI%dUfuHXq z^w9ir6oej=&iX04T9^1nPrlm~|ndWYCf;v}WO39oQ-Hz**P zV=1Iqu&Y!b;`SYT1bgb}YUXF9NQ-GH*&i>Xiz0?C2V; z*7sh7hAX_l{o<9fDb?H_TebNLD1oz!`fh zl={?S6z&2QZgyV#s|Y26FzT<()>%43bit~ajwLb!_zcxyTH!hZC+Ib#e^b03!Ffco z{JYTeYzAu9iB1Ui&1QCZ$=sy|@`i4!gHve_o&>%hU5n!7R7@NR7`+5U`$eti%a3}f{b5PiBVfeHJDKRIm*n2H8t@a5E|SYXelND8SYs{m#SLHaOXmqKZU^n zIuy$>2_29vXjk;A97?7u(xiF9j@jDAN$>(kX!P)51!8NtI}${pt^g9@=;&BE+8*16 znaYO$I(Se*ySvyySYj47_B88Twim1$l-Y6}%G~TWdDIQDK|R=2)M@tZFnGn5vAT3- zF4&~b)lntuld%*Z#5xg`N^$6e9OcEaeu)xWLETA1b2%^{2lIRPYUtzfc>*HbE(JDf zupg=Wh?gnCP~nbX-?2UEGq)UFRI2n$@DsEaGc2 zLn+-#n5?^OH5_9thqjz1sbCwK7TR)a*P+8?CJXY;9XbJVeAp;GN4GqvdPMXtVb~x`xFrk?gAbp z$$1df4ntK{KDskb#8GZhO_SwzfiJAM@m-eOf?NEkFf!Wc_Lxsg6|0amP>V~D^su!4 zxRvp@u9P~Uy8K13ePM{#HTY+lT2Bs--d%KR-j*0Oprw;nT9!o;x@u56`&Y&%`O&gvhdagvCZ6XPJx12QEK z*X0$Ciqm2z0QovcxkqPspnKemC^C6nI5t6Cke`Mcnsrp%=IVrBzRot{ zUh)gNiFw_8Q%jCy060UKkynv>nrsZB`JyM=>C|)jgr`jY_n`=Hw(nKVT4{D{#{9`I zmDX^m(^Bc}$5L?;Y!ybuw4P#=fN7ZaFiskdhY%}cm1M(GP2K7zzKR*WBEK`nRnb)- z<`EA{t&gx;m#GQ9$%I(ZZxJ8dQ~^kKNS&kWg3H6RaotxWKygPu*|Ohldvo3I&9*q} znbNrHXj&}q6R+W5-0D<0BK;D$Tyfn}=I#Z6UFq3R)qVJ*R%kNn`8 zdP8h-PhhSibGMh_i4AQtJi8J>ikuJr!k=H!v$M!KuUZc(uR2n&C52kz0!)*&l5<3x z8g3ZMI-w?C{oN}E~qJWEQeJv^ zoh)7cLJ$SXK-7RclZ%R`9<)ft8-?>Ib#j+p`B7&v3gn0!&Vuhn? zy6hkY7gs!Cli=p2z0J%N1s#0M4<67G_n^Hpnm>hF*;4_@NY88~$c1NI-7EEDu?6gQ#LskkEkfzehJWTeOQIJ#kCH3hao~Wj?KbnwS98%;pW2 zly0i^Sq_(CTzoBMj!rkOv07;VgCvlZembgjiECC6*K8xM*(-6aPnNh=1#zu5;#$2D*ZyRQYgZ80ZX>SUD{-0p>3R`zl^Y?q z`Vn%!1pb8Gv`Rm%-BIh;Q)8d5tH!E)HP-5^v3|WZ_UXE7tjb?wtqvRO*JER!uFJ-% zd^XnVw6T7@HumYdZLG>~W37%G>(_H*pRVi1s(d%r>b$XSyf@lUE1`09ceLFYGn4*B`=DBb_hCQc+!?NH9nCax2$0yjSKSxm{fLBDrvOwqr{W}el* zxY?i&aR5pjjTySLNfIP?yWL6J&9p+BoExBy8Og76n?%{%n}=}9UcxDFFDi0FP7BU; zRoLdHP`)gjO{&PQ8Xv-wHw68dCZUsw7r-lugFDKmkL?m6%!7lg0sxvKpN-P_&MtL2 z%pyYNF)`u-Dc?9M;cfz1;|aB6mi7E0qF_YA~0|!I1t?H+L(@8#e*(j1=40^vL@=J0TH1?3!)PQ{$=&CzkKGY z(X^MQcmS0be}k^M7XjvAU8^Lz>h^ygvZF2ATTs+^Yq$8&-nv)oR{H5tF2JZJ!`ND81Pm} z2DVHYz?-bk8&DL2LDNuL&4-vutL%`_{BS+6ZnJe2l`+)&9`OGa<^2(msZ5C z0w#a$cs!;veYhTrFU>gOHW8&ui9hr7au;6c4QKAr4%EDphSDi?qsFd%92e2miKOYB z370@GnzeMl2?M5jO21 zPy7iw0VmU4fo)2zI2F&Wo2{jpxz%!|TwQqNUFj!)Nb@aTphXC9DglY0`cmgJr#hdXi)xqcCtEMHva z>L-!C{FRXLzSgB;U4=IWWS*8%4GEvia<}C@pNo19;>76B7`tU^P07)!D zQsqz>t%lwRjP^f0H+U#ZvN{lCNrztpfgK4j?_D~^j`rt&6{DH6uDZiO;vxb99Rh5v~ zW{|e+ViPY2vud>UhIet7*7BXhvV6;2XEoBKj@C;8Fu$d~XDIs12q1__x|$l}y`_52 zlF1|~Z94SD*S>keN|OTQJ2#i+mD(v|aVEjy;B%(2i8fAiJ1XsKI}O#>*QIosy9Rh6 z%+aPvMqj444hU~x5Df6@0Wov~Z<6tfB-6rFphYL8TWV0r(h5!oOHckWBoIrl*C|_@ z>+!n6bVwu2_n656S+)|G&b87YCSmb$3J{*f2_!ccn2q!Nja_%iIX#@MfK&3Asmhwq z+tuCz@0>ZB=7R1Zr5B(zpqE$90-GNVQPK*T@$-rCyJAEN$-S=3tA?44wg3qt??Cs) z*YQvoyAx+Mi%wJH+tN3I3Mwc9Co7R%yv~oCJ0gCrOEXY)6gfVjgE?Dg-tq)B*h6_h z|5A-1H)qXg#dhrSYt7S@5k7z|QjKXsydKTm*tV_0rkK2}%K~kZ8>ujbOj+qTeq560 zw^|Ry!Bpv9RPA0=>t0mvUexGXG`$~(pJo;miB8krOc@nit4~^a{5O&I%SV?1h#*jN z?vLHyNbd_F zqbG!neh|72e3%5>6K2U{_k72N@_2xE&t;NP`R-*HTUY#+$_>ozwCdRz&a)L8`4ujwI}uq zZvy|4$2!op8ztj9x4T!)Y+IG7qGLo?Hxkr#gQHv1mz#}9o$jc`oenpuElq*;S4%&= zc~Z_F70y^)V1o@ZKS%UR#Ej_RC_!&A{YHADNIxbYE1msKTc**w#yzGJ3OeaB3ndSQPopXz6ete<)xSwHo{Hdtl{Mc3AMh2`(v zlZyc)EioHgr4rREl8aT{Y`u+uot#iaA`GMvRGooPU3b0zaOak9v(a!hoO@B}67)@V zPn@E~&E*I@#F=dwXV!0i`A&@EJ9RL1MOR1pxWUtGwzBS7COfq%xk2E!3U%O)k8Z(P zFW8Z7j(U6Ab6x3>_MDbdywUO3Uw6WC;`@lI)z^w4tSuDUT z8@Y6q>d*H<{LSSi`<&R$>-zom=|%zIF}JC669pr91WeC*Bm!_ zz!Yu3ETgW}*a_YP3z>}IjG@C?3#Nc&bHEH)(pzdM1X_12v$bFX+6L%q4sL8J(TvIO zxuez0Y{EAfmT1wTamQ?By!0}cB{zU!k`91KH@LG{>iC{Rew{vvDMfen0yl{1KECRG z^@^+DUDqu^zk)(7QCvtiVNU1Q(=>#08YZ- zb7#oL6F1k?)cF(1ak<#Gor&X&9Bc6T*cr?2C`t08>VBJ zEC=6#N!A+ynOtYSHTdz^v>oJz=IC|~`~n3koI2yJaaUE@Hh?xN8;wVk9Pqg}UL*p$ zmGmgGY5`dk%hVA8#}TM3*LY(%uNU7ziU@8F0&C@HpIKvzqfARXdNsu+=3d#?}7ZrF;CK6Szf`%#0!5OD<&j&W6m6W*{-(Qb*GpiI#zq*lQSy z)PeddHp>;7!*EN*d9N!QfZmkL=T?fY8`qI;B)&!jByn)+;smT+saS7P-O+jpzh9=j zU{c1q?s97~an{(9Zx+ANG_9~elpBk=OVTLjNiXbU$2VN2J2UWJP*mK}){-6VQZjM8 zM+$qlKH!)n2?kFc{=1n*^I39Z4&MXHEN`ZZojl_%t-a=I@a60O_b6NG_d&9#{qD2e zj`O?Ea<7EKyGDj(elF=0{E+yr*C~=7dro`wtx2kbG^5=7Z(P6LheC7|qBIT<@4T~YENtwO! zP!z-eten6-5!h){DNR&Q1npRDEToFsiJ%^9Erk>;q%;;%usNlrIcA79;B6e+jm2yz zcp})x%s8!F?iyHX7@kR&X z^NDGV9rLsy3M_EPP;N9)z!|PCH(P9=5Gqqt};+$C~v7|D84;xq))e z>}SuPTh-<0hzpc?Zap`iw+J@@d!gdLLcw&EqqKUKE~q6URqU}UfdEOo$Mle@J> z_N=q|(9#lg0lLKuJtL0VK-1y>BU*H(#(_%mz)(61=y(;r@6bhcz?ib`qHUutV79Vu z*#Pxm;UH~mn$)2`a$A%DwX0Fv>vkjpCk^+qk$K2nxTNs1Ag_ z2Ep$#0w9{kVd#=(>j*=Yn9>Nvz>Y{%rml#IeUVhFp@;=VbV)WEifH`&dmVri!K#1D zWISKr@E%kEQySjG(7;Wu+V=)7fz`iP0C`1g;x>VZFPb7=h)l99opZyK3|m2Nu=qGz zEjLtL)@$g93azL{(|eIwDIziN0U3}GD;5n~vTQD0&4QuHB;*OGH6+Vq91`9(mcVKT zWf27vP%$JGq%|fG12MHslGkQ9Wr?>+!$xf$G}WkBx*4N__Lk9U=;+289W%j4F=If6 zq}wWo(>wsRvg}yEBMwC|B-PXqjMf4~%`nS~(Ghitsd2w7$*@=lKnWC;Lx3LHagQv? zwA&$=O0@`rw8(NSIf^YA#BQybHULY>z)E^$N-8sANSee8OyaMnwx8u+=L^7}F*2Fk+!rC0nQN87;#Vp{YuKQUSdv$t=c81*#cpS5WJr zrY1BuS&WooNxI28LQ24B>7Zv;OekA2txiys%0UYft;C>o7=RiHlqt1>DpSdTQIcp8 z3n$`^kV-^iF!a<|r>VBYIoA#-rIC~lTeUR)+ra1*JyR4IDY1MT8sdhKY&?P62CLjR z3uGxCb-E$Rn${WCTB*cR0Q%lc2yO!R)vlltIC@-`1k++MEEUw9(cHzUGxjghv5!^( z?oGg4Npy2iuWj}dp(YNm#tG=&#KzFELKFC!^oo7< zpeBDJ*u?3sP3P5y6tC$POB5SqO9T;zv3a^p`o*mo@^?-0XNbr8?hs)wIM$$ zmJs{rN<|~xcvG{$*~X!kbu$)o%o=U~9LoUj#;L-JaT|wf7MSac zZorYaLXi`a(#Q*5YeODnHx~M5LKb>AE)+L9+SpLHG15?W14bk@HsnNHZ9`V%cNn%{ zMw`)>Z=f4L%G(}o9I4+ZX)Imf3bVRY+QyJtw^-n+DjC{o_pO>+U{JA%ZY-+WLuzAE z)4o$n`2wS4t7EkSwOPv;%2eHO92NJzZjn*SR*X`%V3e{Uqm<1?RJK}4*_lQsPAOY+ zO1*tfsatVM-GWo6M)7#3+4hMj5weeHU9u_+)hAld%P#j1Bo@Y{w^~ ziceNIMj1_3VeBw3kF_wBj?? z*8(rgbF7H*37+(dsUoKuiY3e`6uI=qZX1}i%+aZkoX5%^<+B+hrnZjpHrW#X0w!72{I#RHhS0zBjHOy#7y|v+p9WNC)|?Sl^DkCSkB^SZ9FU9DmG1|!YR;r6+=XTew7eQ@HfdZU6A_V6CIJ)jEMYV7+ zY3n9%3arm*sOzkLs=C(_I#j-6k~B{(T%P$Pr?)C0D`{%`9BjEBa-4#tq_?s+50y1o z_U%oz6x;?)s=)V3nyLnFlS1X0m&}OFcW#vwrsg9&te@T#RWeMfZPHt0F zH~cR0{8&yC4+(TX%T4k1`d)JZf1hU4Q73b7u@aKm3m<+t5XRm`n&ki)U&H{lz#0R} zS$$J@-3+Xpbw_ARAl_DNoi@D2kL|SgmrbO)>Di8)ciXvcO(NA!0Gbl1b_0-2q}uEU zH7NWFpQ+7#=nO4r#jWg>H3Ce$vlb7fGd%8U6`lDm#4n~_zHxF|u9N^0lvz9oyakl= zqEf;rVSPj6&6EvYAL_3qWCvQxVwh1^lL;f}H^ca&YZqx8ZZr5Elfy>LgV;*Povchp_EqwdNbwHNNF zy>LhEg*$2$ceHJ|qwUHaZCCDSy>LhCg*#d=+|jDIqi@3T*SHq9r3|lt{`P+?#;yS@oQ{y9tZv4!)>#W9}FHF4iig)gO=#FdN zojbFc8(h`ArTm&T?;=d8UGwh38@pL-Qs=j}T+guqP+p~KyvBw? z*KwNI6=g{IG*TVm6?0FOpl%AV6&BB}wF$H&8&?TlCJ$Cec)843O;WR|z*In^q@W!E zQP~O@n<$Aot&k)Y-9#d)wPbf7)%7=+k80+i)K1?-QPjYq#HJnLP}v5J+ZmyZiByKh z=l}K_kb1BRc4?%ut*}cY?H0r?(7V|cyR@*mwXsWUJJ}#THr&}V*{RJf?3LYFxrr6C zvuJK&+w9)8&KA$^O|{!WJ87oQ*3mAlY+y6((o9!NYUegO*;l)?(%I_jN0e)Av0cD7 zTWGsLZnfKXmeqP|ZWquEHr_5^TP?qxm20CtxC`_SR$?FXUX$&ZS?<2>xc1%MX50dJ zcUy4_;2mtlEueO^4Yz>2g-y6cE4$f(yEoX~2Hd8tZEU}-S+$YPx2>pcWb19+mF_m) z)=hM!o~gvPe4`0 z`=aj3qumhPq@~WbSN2|{KGCZHUnOJhHzw^SXYKH-X7Z|wly<_S5@Mx)Q@jFcr)JCG zYTHaMLh7Nt1XPm@;?+m}h#Nwx)8cf~(N4{*!Bn)7ztd=^cJiRA?1lJNqn%nwfK*Gt z&a&p#!F6WQ&ae?k1$=%R(N54gh^qOTB3RM@mg~yUm%2-Ls(8p#Uv3VNSF9ROl!9(^ zfK;yn-iNIreyD|+Nr}59DXDS=3YhBy zE1N@2>o&kuc7Uriz*TpEt2V&Zc7Urjz}0tvt2e+kc7SVCj*49Bplp!s^)4xhnp?v2 zYrQl@2jME#5x}U}8a`Goz#Oh-Gbk5FstOY60deSIw<32gT)$fg@Lzfemx3mKX^~qa^uV7+V2w~aA!{BUAS3DahY69Et6zGqOv(?39)vCi602j z)D?JF(#Dd!1#u{^EU_9$0O;LHVYUoVyikDuul!XYBqSNS(Iwu1jv>fl3cZ+CHQ$k& zcZ+w6Hrk$@#TZ`~spAYof3}J$s1qk?DZijt=!)mL&~ICoEAaf>ot?d(e)DwlgY{$h z%XLD%59{v9^2IbM-FPj$VDWq5-z~=8#lI;E{mV!1KKm0Sd+dHV5frAGnxz_=`R=pd zdB0Q1!f@ykb{s)x0VeKZ6o)fA0gqM|q~i0lGhq6ER<4VI(WCbO-&uHb3bz(%z6m3` zrElr1LU+98Q}#-E5%07=JA+O}Li`3l`vF1#dBV7B01U&6t0y;zypc1@W1gj6WxwJ( z=TBfrKa9t9U`oz47nU-ahZd~vo~Q|>X+^sYh6yQe$^|cs_9$+dw#@TBjT-UcEv@3HX(d9`4Am#%8IN78`LM}r{ zrU~KZMu-JJdoYBPBsScH)>H&X*VX4{0R7`HKNmunsYmWFKfihuXEdQXCrmQRj6yzR ze|1dHAHyg5Ab4Y#mH?!iuz^SUkYB1b?*@*GWrU!*WO3aGXhsIuiEmzqi5z^nCH9vGc7bacb_-ja`y0?&@W{4e1xT5>q<(452UIpVnseTRBu zYD7wFOI-NDS*59nMjPKY0Q^TYD7AE8Fo0?9Mh|toeL0@R7|eTdx)~8aqb|lwPA4#Q z$L=CI90pKQ!!EJwGUJdBHLQP$mVGP_0l53wLilP37``vPyJ_qRpBc(wC!f2no9P^l zsQ8VhX{X7M=2O_(83PMC={=>*BWo0|-OO6U9K*ExcrB-Z=7+#AWodCV^Fwf8ydoG* z3<8~)j9`1hefiMl+$)g9;C|ZUm$H$kKm(qGFl+OK&<1;iWz0vX4UmwTA3{=E1I3#mGZC#eP zi+Bv0%Q+!P8t>HH;h29tf5c2%HZ0ZDtp`GMwR9gnKuTh%2LdT^!uo-5Oyg!0c*{r_ zJCTFK71KjS?)eoAqp>@2gLj_^^fw+^>zO@k3x=Y zB0!K>mdEhi_s_xAa?a<@!Z~*XY2?pIS!@mjj8$iL1so4kAn-3rh)y(}kC0N2|7!w9 zE%xx?XfPdkgZW^2bUlAGl3<_$N1pS!?&0)ku!M)_ZWKQWzqxwrod4j=U3m9z`S-zG za>A>{=n;cYNPcgyl$K5aMSt+eu7t`dh`u3<=OH$7^Kc9UA3+V?eZ~=c_t{|G8<@K1 zOD?zAiDSf9jQj46kN*gdfmK0xtmwL|o1>9xvymfDoY9aO_RwOZW7nBjif!manf|b$ zW4W#|naC3M{OB~pC!)?Ah!4iY+(kM&LFj@d7npGE`fTxMzP2h|`jd$QO*{r4Xw(jO_kKQ6t##`_~B_ebR- z6w&@@Rmw+WR_2lZSm}-D3_nh+`=oewQ4p7`Y1w}6EZ49ykD$VJ@&M|ez?XmE(myU; zp*b750qy;I(G|RH>wLOA{zrH`_QEJ$kQ3+rcr|ki@Q!J5i&o0x^;g5u)X~tf6?bgP zY@#Zou}y1;bts8}zlv~ixnIm;uw?|tuA|Fxg~dN zIGQkpjcmsf$xK#8 zs-o&Geaq+_{(eL4b1MUole%^crbJC~UGJlgW}x-B9wNOb0@<7ymH0;-pfPfD@r3U_ zTf58xu4%mcESq06-s#JRY~ZDs^gY=^3@@GKDX{ls70ez!xbv0q2S*qlICr9l5903w z;a)H)AaXBwL{SzEDB+^`KS*=N25vN(CS1MQRw_K2KKD&3vXlC~3PlEy`;>_ojk^(v z3Pqvczer@=uzMAW3?la_5-}S0rX}`9B=$yEl+d|<@wjijVcfrvbqciqMU);8?z#xFVThV9_ zytq5@XvO8uq@xj&I}(mPSlDO{W4UM9xDUnAgh)@4@q-_XThs)*14*a~0lHm=SG0Jm zd@Z!+`cbAix*KThjzxb7p!(or#fZLpKHlB%t!?)Xc^bER+nlW!p~{_dHWM^?u zc}M)@4c@~by$4p_$0#i}e3xv!zj3PGDRbAMhIhr)w8cAOC$)DEgY@qCcpqc5(C!`b z^!`SucBh=ZL*KW$(L3U4(&Rl1)w^ZreGJpPAENg+JoP)}=H2b0SGIXa+>Beihp~Ad z+`NwwdbfMsHSONRXf^NHOuCzWr-~Nu%rt`b?qQtXJsmFlN!8-L3$6Yyq8g0X!tJKy6ZU2rvFMp@h`o_*uRvP&bj-{i5%gP@bBMG z)5nPtYmre5FNX~ft`~p6_^1=vHf;|)-Jp1wWQ{LBCz5`f37zjz? z9^oM+?0XkoIDvp^vO{R(p-@EVZ+|O3l9qlLy`>6ADH87@0wDoeo;#!I!%Q;-T!_nG?@gN+`2Hw&2 z<-cdYKf+)C_kVyge&9`CKRA!(66Jw=aPn@ka4!Yuru*;+(-yt;=J092aifBNgb!|x zPLXJnN8{?uMj>T3dbjuk3gq)4g-3(&?9uGM9z96=|KRHff15pe@b}rtg9oR7#MzK= zKM%gp;gQ9IwscR&9Ivl2!PnotzmGx5*XB_AV=y7&9X;fX7^LZx9&qwK7-Vo8JoxtQ zYw?Gl|M~5|zW>+j2StJ(W3ofagY?zVA77>CFq~jC#T3XROwQ#8SLp|_Dp%3^Et3UI zl!!x0gn~MO)FS?4eZbCW5U2-G_ z^PDBc)+cm6$83e`Oo3vA$@!tq<2Ue3i2NkI(^tpNp z4^weYRI&HG;|3#A2)ygiWkn5q;7sd zK3)IqKPx{v1w#6>*)uP2Nxa?yy}~J&S^VvZKHwjq9&jmii|;MDP~e-@Wa0+JVv2l| zL)SwLM$T-883{uPAADCt_0oX}AyF6C-YoyB7g6#$XaEc&acqAth#MO+}u5|jfGQ*fd*V{$&qPAz%i&-lHfWFZ4^rpC+U z)f0NdlhQ?r#J!91DKyJcWnkuP4@M&|-Y%lq@{f6?;_zv#nHU!_rW@7Atdp5~o3!jM zR-g~h&u4B90D$26X0wmhWZ>^8vu;A6$%lzbF!#?uRAFv^&lZ;?6KA-(RjS z=wht=4H5V)PY0NV@Hh|dtMKAnsNjl7b|s9M5A)d~ETjQcB|kVnR%BT|hU!oVkA)8X z4>(T=vY_DqnYoL;q_Y}+d;AqDNdZTwURzL=7<>&7DwD8SaFu?Xcw8;50<<=sVxCn> zBe;gk6iMqBH1_pzH`terT%d+V!!|v;rZ6jnww@oGx83-{Qx@EuORALF@NL9sBbwo(k6e5Qr z+w|SGv79OCrfSh&P1T8KvcbuwAo&c58CX8~PJ~dB@~?>8Jj@pyQKS;qBBFexo}HzC!@nLIs+qomhIK zXcf3ZSN!qf17U;P5eh~Ea+@+x7tu*md4R)oeCZCC#d+{u1ddkdM!RP$0pv1IMa>iu zXQ`^u<0A4YlgY=zEYHQ4>#5K%%WpByfwwq^*DgA&Mk!=0lfwxr(pT_r#6_t zCK##7d&baOCD+3;RDz@0=q+iI4JP(0wA9HTvL(Y-6|~Tq#uWT+*+>X);`1;G!hoQx3%6p`K}*mXOqk4y!=pHNp7zr{vkaM2^7?)Z2SWmcm?#Oogt;3{edG&m&I#N? zYgt<;EedRkBB;~~C;(@McF%hAv1-c^deKpefV<08lv+_(^W>{zYND!bUbu5Jh735xNL)wAB42V z)MPw8<^&TEOw}$J&VHRX@{=i&>DeigzU8M#1`1X$PLbu=4?H9Qj>=0>G*fvBo|$1; zX7XJ?N7Xd&yQ-L=)W9>-6!KzpRZ(E}0H4ZMQ~`9q#cTtoKX8(0dIY7e2=W%GNSXlax6_rM~{I$cB}af0f{?1D@c-y+R$Wa`^Bbl)t1}ifzU9 zb(l|0mdpb20~H*5TTg34eMK~|r_6ly3s<5U8F z-;@-UsVt>pfy$dr2~{rNY8GF*iP1e(S|^o8EFn-aaqNcgBOjCQe>L+m^M);oLr_2j~_=h`+L=4Fl;_jT`mopw|D&*UE{FT!ol{n@#KduZr!+Qo? z+T5)g`u-BHB*wkw*H{j})I*uM&V}0r3i&cW07?`xc|7xYs`L^GRg~WL9IppK=124%O3~Xjff$1%bB}@H*)rjhHswy`doOzQEL%Im;pOYBBNCp`SUUX zOV4A7NJ1XO4p7gAGl!zxd2G$X$4xitZh^{}8*S-~M*eCMg?#yJzho286Q0EQHB(k+ zvexquk3n`F;(%*(alDTdeEVzr%wqIEvlv}zhl|4CTsRQ?h+Cdi1@zt~xSfz=D_r`&~|4=oiNW`nk>O%`Fk{!=Vf3uFUL+Z4!@x9##v*xaJzO*@ z)cO$~t?S(CT6@yWD{t|B2M4PRCYP0{kkiOp?1V`}UZiR~_^N+STdmoNLDuwTG4@7I zM7oY21EpMIP!==7Ase!%ts3gbHr8hDkCC-F%#y)j2xg`0lbHg^cz*@%fi4&@Q83x& zUu2jEESs@cRG^D~kM%V_A&jFvRq?B#7!PSO&Z`z1fG%!|1BOBXaO8 zvHOAN=zILab?~hD_%PtM8gP7qtr>EagAS~|PC;>z0BAs$zw^Og`17lZQI{l(ECJoS zc`m?F61s=Eu5PaTIVMnMNwY?7G)>Wa4hc*^?w!~;GtXTdgr-hto)CH3MFywczN1KE zkb5UGGiN9QJKu{q9f^M6oqG!aJ^?TE!W)asK1frIG`(_$!YiQMiGb<1-U9iAA36C1 zeVhn*+*&n!#IdjjW~VpELIK}C&r2Jgz|%9Mz_&a_|3!fKA5H2TL>=~ z6K8(aNv8CEJ(L9E9gEcFVJIkd<7vpVVfmK_ajTgN!AcJ!cYGrJ<_BSfDl5V)g7~F_ zQ6(E%-$qpoa_@B3<@D;Hv}&a2?bHi}m!a_EDgbrWO)>R;wkZa=cY-qtyt%hB{%9M5+GPRe!+63Ac!>Q{YM5$JkUI2}RH$?Wy<3~}EDYwpgR1!$v#Ts7_xc^DD;${AmQcT5E9 zELL{!V)ZfD41bKp3p`qVkf<7o`qdk`!dhK@|4jH5>D4Mwrp$Vu0NF<%P^)n297L)) z;Kl{RaLl#TFF>x25@qzA*fEBvlYF=i~ozLx!EERIx>RpERa-Gg!jK&8c zYST%0Li-2u=bV}F^lIn^!ZR-bPWwSOX|j8lCVM}nX*@biajoK%iqf-SbuK*R8Im`d z;S{-Vc~a#4oZc3ac%!`_iz_o@YShA_##X3ve&S9Bl z@0M9G4S;i4O1Xnr1H5EM?*HDNy;SGh-x2f|Sqm@UR#d&2qT4-Z-iRUYoy2T^A=h6d z5@qT~zEHj(tWs>4q8gj!06N2fMqF&KkRK@GUgWRuOd8lbF}m=^u78Mju8$G`@(8cI zVc@h&k)r5w&)O#exqnGIx4W3_FOr9|>Wa8(#UtBV&v9=F$oSN@t?0a%I|v)^k{70&@R? zj)IXhKIonLq^n4tO-|-0HZ#~=tbrvB9Tvg%ZV~LK!1Dty^4vp;lsmcIM`a4TqwqfE zFy!`f$i4NK!eQ9mM=YPB^uDFImwAc2FkHFeapaDs!2gARk=!bH7@k!=zUz#W{PeRi z-h}YxWw#w_wRdq+_gI`<3NpGnh*7l|eH-|fBt|?Kt7DLZ*1HI4dn`hWI4DF6T~PC0 zbP%Sx14t3ff#)Dii*+E)o0w+Z1grP3bw%G}!5aIAe3qF z&yTvvQSWiJ;S>U>?>U}Gy80J1JsEOl6hxB<3xjQ4^tnjlt^UghZj`2ut5N&*y3u+hpuC@{E z!#2V`ZX^8Dy%Juc@^+Bshkb;*+DFiIS?T$f$pqvc2D3X0AQsSZbg(*W?=hIUl))NC zW8|LW3SFFOKC|NTHbclR)`O|^97~vh+&d8qf#(jl*AEibkU!|nlV6{wt1rNwPEL*5 z`x#1gFXIaR;qZ{Xq%I!S58mUgw=bNzHv=18c-UoYnXsMs+D+ zkvgE3!uP@pKNxk99bLD3x4}8c{WFkwuAqbT)UD8Q`kV+)gdf~XFuG^p)(TI$s328S z)!wgnSNC&~E*wpLe+Iw2gDf@35)w&xN>u7*VYA*XY_^{>nCp)HxuVMl*{Pewh(ZF8 zsGH>U9*nBqLojOO1m`YN>1;T27ViO2;POR0tWLzO5H80jtwi^AGrw}32FzoURG^^i|=Kl6#!pN}}iX5HBt zZ?*RWORBM-Hs|4_Qd7Ll>nGnm0lkjF+gk8!VM3ZCR;Cf_dtApnqGzALVz4`stIr1 zFzU9B(*F*mY47JLx0qg`7@}1P?m54ndl1bK~Kf!_l|9T3%6XX7#*mo`BrHbMkBXIw<(sj4*%rfsh%A zm^?3D0qtNsfO745u`Y)Aad^qj2n~oRJoq*dc+x~;_Yo~cpQK9-djlBSUp9F}5Q){D7F;Sm(!DUhpOuyjRNdJeHm zKasZK@2Fa<U$?^UvI8*$BuW9qq^liI)*DmeqxZn zTPZTLL*akn{dCh67jV=1UN}xb?q4YIXzIBa?!onI%qrk5LR!RolA+cnm(Q4*CEvvo zUfj-M1#jAoow3qm&)gc-@f>;jK4!XWpXq znnGRHdiPXfkb5UHQx8un95SBONzHehK7v1?sQX_QK)tc>CUPRT^~OR=w|hPo6hqv< zK)W>k+d+_O1nI|BkjawQdMmSS$#-;Lp(P(KPuowUkvp@0``l8Pc^V2Y{M7=FZXE2! zJK6)bwQhG7b=Ti9#63=Va?sy>nE3mY$0l@+`FjlXCm+hQ478E1 zKvZDN*ezyl>9C8eso)y-eEcW|xp$Iy>CE2aWIBP_^dM?o&LI5;6hjGn0TgRn3dGaZ zcXI+HZs@QSxOYo|-}y6d>|BL{bJ#_`w}cx^){MS)lI8uphjTOyyfB(O2g{-Mz+Xb4 zsv+4a35VK$5{|t*T|{3+`YyT-rZ5xT^K&uqXNTjYO>TEapoHf{H{tpDx$ra4t-%=W z5R7KpcN2_e9&S_IOJm|&H^N|Re|F)H57xsS#Go4b))YL=P}6ri=tjG55iL;JkR{g#f-LS3J%JX<+4qNOv~swnkt64hxs%>Mi$}J zTOKB{+R%mNa&|>Y4jg`)G$&a5Vz}M66eS?{Fss{QEY7dQz{PxAATAEJe2}QR06icV zp7_KR=UIZaij;0DX8$*kn}-|7Khizn%d5T*ZoAsOIrBcQygLX;`*PZw$hm?U z45qUAVLj~*)8CuD-|lAa=k0Dlj&KxsL-(MuYcd1R9d9Q5vK*HV<<*Lg*1PE3zX>_N zl1Bb~zFK$@eZuQ-W@CQ=zbLEG*U_WjUjA?pqM9u?_d_HAeK7h^(=}5j=j6%E4WceG zW$S%M=O-W!3nZW70!f`_(t6iS+99TEFYU$`{%q!7QpRY1giHCa-LdC%-`b~d5&!qq z@xr-?)M}f) z#1FE3Q1 zVjRob@o(RWul=Dnb48dErRC!M>&r)RGYR{hC~`(qOp_qaFo~xSdVjk7BMR*dF@X;V z*IS&!mzneD70S0e^5Z9@q&cP`B?0XTSpiYK90+~&9k+7-bRX%eJ}_iifWQ3%Hw|lG z8BBn`mq-$LMpRx!{y=y*cRq;3?i1?U^1~7Ql`jAd{*^oJWicT&@Z)6S(cD!sbt<$3 z*Az~xp15D#5TJPxx(Qv`v1;f*phXBHTg1;8hb^WzWQgQig<{}BqYH8F1n)y};a-Yv z;NY2va5WmaAy)9ir5k{2c6{rqhkXjFM`LzY^Juh3v%TVB$GT=)hG{Uxw$7MtSn_bF zFzw8j{#c?8;_cmn3aTGHAWgzEjOzC%0wx*UY1HiPTsxkFT^L6ESk*CKj}ZUh<7V5j zkoA)PDs!=^=az3R?D0&m{Nc_nUfQ4R!6N|`17LOW^)V@vJ zJ@ie0o^3f5_d(gjF3=da3pCa|bpy7UnVY^fsve$@GR1>u^Ztlu?Shv1V=P`ci;;Ur z`sI_DaCJ%^o9Y)s+jYYI)f>5MQ5V0Lo=M`b$Q6|ERD@cHE&o-_ncme+V&8*le>T*Is%too zZJhaguW6bQ-$}`|(B!uDORo;j$GT*=;oxS6b;HBD;bGnI6EZu_0~gb=f&5KJPSAr- zqS(w}2RmWQ>GsViaNR{X^&=4DATUKuuV(sUK{IEeF`QfxlXch6;|Y zwdcB^i0V!wzSQsA9@kmF;5QKak9d;5zzn9Ci!)hP%x{5);=~KWNW5~L0M{XXbd7p& z5;~6agEg@63};JB#~F@&%+^4z6Srp#07YVAM`?S`Y!?JqS9J(lLFb-%mFiBS=ojhi99Y2w~A#7iDM{_p>=tNK2qlI3v z{s7%E(3vH`-#cv=7eoz1ZC?-tf@GsH@LTgfY;uo-tlHU&o<8F365Lr9qp2%q(mx&- zzu%RrIv6=sLx5~-k!);{E;JOs0jvYxpNo``!}A02SI@l^U-|xfypH7!UZ*YovV<|= z?tcJBAKlx@FzSWAw{0?EW>MUmJr|yTfbXH_FFp;6$y9_;VisZ&1U@Gl8<--UuW{X8yWv3rf6HXav|Dw-`)gMMDVA< zO4Z1X45xOE9*+wYOk(Dhi1`}Fb%oglK^sLrgQq% zsDNq{%j3Tt12>(9=%&fvU~{3v|HU8O~5wPMleoWCP0O17enB%S>Pr z)77ae8P-g+Bv}(c?(J8IgCbd)F%?ZolZ~0EOBQ_8Elo5eL$w6@{G9}I_{@?F(UvUr zl}ymIDZI0_nJy_B3{S~cm4CfvrerWdktJ|`riujK@+(8qEX+BiXy0j)Vq5>pCmG76 zW2&U@%o(~fA*3-Z1|*$gF&9 zv=P{I3g}5mHe^LqB&J!Wq!=1}v~jJ*P&H99Ez6Qr*#zY)X}YOPCKXi_3xr9vOjDE$ zLs#IJiEBDg5TC1tCE{yY5wI{6Qfyt5Opr`b(rn#;LI%c#OS+ClYz9rqx}gb(5;O)l zSvD53ECs(PhM-Bd2FSx_6iVGPBw3MBK%r^ukD~J;Dimh~$I!7m0`*M6{^-<{Y(j@D z?3oR{5vX4(^~Mr-*AS$Q{faw-e`FIz3%<7Kp@p4Muv@$<0L8`;1n^ajdPO}^u~)nw z8sKFUiiUzcGN~IT5G3xCVc=&)HhDkri^Yf{_|bsp*b}6s!HFi@@B+G}QeQ;umBG6M z&rK!n3^BrD*d64RrSQ%mLsiuzHk!6hOto;J>(B!A4LW3q)H&=ANrj?ee^72L*~b1T zSOBas_^gVEHt&uq5{p#q4->I#2JenZg$$i{hy>849%-=v@(ytkG^tBm2yN^U$zUv6 zrV-2GQ-z^=QixTg!hnIcB16wm>zTk!h8(o9*OID$Y*>;agT`abHU$P6P|=V%8Whq& z0ira?V7b5-MK!QL@PbLUiP8rQ0oDTqSvC#$s50!LE~LXswdc?g6nvlz=vEwM*b`Zo zZTM)(I#5N?bnGKw4{tP?*eF9&3WHxZ{Ai}hsGAHrZUK8FO@V=+F=VY_F$PbWg@v&c zK4hS|u@nwk)lf7XjJict7;g%3u#xRRZDat^6KdfAGi_ju#kpr9^JEo!t0N!OP@E%bYs11d8K{uEReJ~X44dG9HfhmPqh?4@;2mCeai$)@d!eYy^ie13o z=s1RD9d!YWE*!-Q>JjV?4ha~WC_@7Ez@**?Ne@h0pdP5y8-aSD6C1IFtz&Oc?y(cV z7W{~8fevXpwPRt2kR=v&2fLt|I-!YufbQ@fXw25I1MrBM24b)Cu9%3w$_oMVCiMh6 z4+;R`8$?hg?gt>OlCIP6qiqN=oq>J4A0&c0?+5Bh&@)L_iApG53Q8|_LnncvW^9cl z3OjL@6XT-A*#V-3LZ_i**(6OS?+tMpbQrKgBWZ%J#gYZQ zut>0o&??Clb^&`va;1ZsCJm8_8xZ+5(pXmo9bOyS6By)(AN&G?1%F_aq2gqk=49W& zKU4^+Ne>k5HP}EZYG+WdOc`M-$b3y!Q5BjdNQ(jXk8a{06HOlmI8q6PhPk0r0qh`@ zX7mLxrP3-dAaZzAg5et-o?!vBy8xWU3sBqqwU8DCeac(5UR#8uY?_WH@NObYWf>W6 z+X{Pve{4gD|HxD%t<<9-_A)VYb~VT%?|tOIzddV=!zWYhALM?0*Sm@TO=^mPz)()u z3pc=JTUlY2uM8* z50r9MrD0^CJ)_yE95m8~D26DEFu#%Q0ORCBhtX`~^kgdpmu78C$EG#-3`KEt+YG+L zWGhRmhQnPm5FyZUIQ<%^EMTsYamIvZEEHD2)`0mCiy*c%d4fSDBGNc`VT>aLh85s9 zO(BT2qN55iHPrPmNcAz6hPf#F$M7jt)-(XK;327fErA_ z@L7l1MvH%g;zy-pATWawfmN)_QIj>53?iZgqgE^!nb;zO!0ckHf0EMcL*>2o-oN$V@2u|v+)?6BZb$1;3mo9Bs=Yh+KoxBO zhQzNRv{Mmn>4BL38Qf$q1mPB$!?10Cj3odMe@v zkTJc_Rg5=NJSp*Vly2;8Elt5{4n(^hIpVNMghn6l>KITn!(Y(mI_fAeC^g+0f!YE0 zO+aO?YYONM8?`Q~MKE$vO_?zD;iLctKA4b}MYcEnAv01ClQQvNbSW7q#N=;W3fxBq z%95-<17}XzpgKA|Q*_j83VzfST7uA+<|u$8n9D-)H*FAn8jweD9szyE(34Sc)>dr9 z&8Bq=l`l`&iUU@;zqu}h_ke@gscj(`y$(*!6Lw`MaeB|qw zHW+>k1=6tD1y~Ta^&Lzdig`iHOy*RDYZY4Bs~7a*L9j_+Zx^55JM1LhDaAv?+SwnsH&_I>A#=BB%ruJZ5Mi-@wCW;3nWF^eHqT z9MP1FGL^R7@c$5ggyazv9m%Lj6f_DgLe8(7nB3 z7dP;c+idyy7A7R}B17C`0=cwt0bH}6fXW3Ylm7;6&`SY*ttd}r^nYma1{Y(JzoCqd z56);HIHu{~GMGTWu(HBkSdc4dPC`k7Mv^}vG>o&gE#ab-J;G&I6mS~bI1|&_JuL}K z@OLMX|6hOg79^=G@o~QDbZa=-Fka-hkF(F$nX0?&A!BatnRAuI zYBa=pgcyE@naX($9_~@B=PyL4>y6%5r1Xv}KE>A+^>|M3F~}&zTZr%7tFeE%s0Z{< zUVwvoye2{N>8oSjNlur=S98Z(@NDtWpcuaQmYgplAM91b_8iY_2_eQH#*qsGUM_S# z^M}CIr8CALkIYUw6vF&z-ohOVc_3nCAsT?W{eEZWoi8RB{e%sHa&UPVgHXl9$X_lo zgzj;|_r|aRUi$3JTY$2o{vZy)`i7XU04h>}*JVwgvQ7|@QNW90>g?PtttfCnxpnNs zlr?5+@6_@J7MldQy)c|3$Q~pY7R15W)mm!3SqUSNR6DG4OdYF~q^gFuSi&g6Fs;XL=n?2j1oF6=%Ij(&ji$iig9cC|NCN$y`{Nv8 z8^A2-CW1tf)ZwvX;9iC}u{*Ps%R|TTDvE5o3ujK>;Ulhs#dM|V zoI07pYUdbf5abTB#htmNR{5(;!>i%ki-;Q8TeCQ%PIPTHnUJjU>n#I=NEp$p1?P>BI+((V! z#u{yffxQZzu5H`6l>1bbOvC>F?0pG$+eWhPU%~U7H<`ttfJS4J9p@^N9BU$38cDWq zGS2aVKu|tQN15&(x zP_!rU>H$$8bvpEfJ^+hn^$A-Ss6d$OKf2LO}VfjtP+ju(^L>S`Dt-afB> z0kN1DjJL5etzlrtttbW|LMk6(X&aXW6qz~>O2r;xvyPbWOv0Rr%fDj;WrMl;l31SXkXpQAqb6<~u zxu<#|T&}7p6t2(Y7tq!psiosfh1o z?X#=<7-eHDNE*N;)Slt9V#tQf+{;Q+U1LuSNT)5?;@;>y=bT2@ZIN97YOB|q-8d`Y z-Mr!d&S=yJo8C7n$EM-WjL+F9&pPCtlr}qVs}4x|INZLiIz2#-D?*;ZJ`w3aVxvs-mw9dZX> z0@8z`pAT4^mNu#3A|H%D&3t(|?a0iStwwiW0x4!5_OpI5N(SxZ-U%@?uMetjz6=5s zWCQe*JJ0+Z@NTqzH*_~DA^8a@(;;fuWjD({!5nywZ);At?a!{_`TX%upy9dQ&yI~a z>7M`{6OFhXKICVyqIa6AU+TP&55%Y#$VX&V%1b+|`tZ+pPIj-{|J_Ui&$%t>JiGe>a$v7;_LWv$h zCK-U%BIT;!Jxd+a5Ve3b*foKd2UX_3T3!0V=sJ=W;%_QitvT8DaZ*is_|FYng&~dF zXBen^6^jkheO5x;f%s(#eRyw!KBxS=Z{UcLs)*G$~ zHRHxEqtCcbz5ZWs-^_Z;hD0-N?jOe1tsLyy)HR;*ABF(6PhL}?a;9&dB-OShf}l}g zao^YMewU?=_OyKcFZ<5*{w{3c`2`lpzkjPXuh8QhXR1w4_bEo%#>FHXT{Vil&#vS+ zt@_ERm{2Wyl%31o6Ru%9%Qh(RtC^EWiVxQ8TYL5`ob_dv>98orHgUnQ2lskai!-H` zv`h>*i`QHQ9KPghhj%zE1;OG9T!b%qTZO!VuQMs_;pB`gV;OdQQHu!S^O5IMJ_L)9g7muf1RU-f} zMp=83W^KD#+0B2==J_cf_k^C-QDjWTPQC3NW1EO#;Tu<6O78B zz+QzV#|PaoFj){~sXn`g!ORguGjNWRUkWmLuk31SEKKfEzx*ho6A5J>_&7PIhL(BD zOvvRMe5XKQ#~$g6WXyZJA6wOC-sHY)uQ@OqFJ++X{jW>MnE`F{rh{is$9X@4^9 z1`h)ELBQUVfW;xo=B)RD-OdO+NqUVl;LW-4$Rj}$nA1os5xS=@cCD?0-Q&IOt(Vr$ z?)LuH@z&|y;Y;iAx%K>bcX#LTz}kChog8g#?>@-k2RVFuIUH>I&Zhr4<**m{;SF*a zMIPkvgB-pWIqYmC?Vl&*7%k`hVKEw)5Wu72tQhAhGK#n9?TCYQN!kS|uIyyc#+mr} zhzN)ZxF=J2SPT%|ga=#jnfYWoj|n?oaVl3B&O85jb#l7*@;htq;OOx9^aR5D`0(4q z)4lDJ$0WXY==Jg5*1mPHwf$o6&6y1QeYoNk@&o;+xP2Mur=4dB=+f(CHBO&lJhMFSkB;{pmmMW0Lqc+QO)fQme5 zfCmk5Pa2>z8IiOe5e)6TbA=utyvwv8-XdK<%t0`UULadNX(x6$Ir}+F2{w`Qpdp&1 zw%e0&o|UXhNXDR;icguZ~F(99sv6JA2Q6fSQ3`IN3ft+J&MyIzHTawJl1AoQSQR zE$igfxBuAPKDCSr+1lCJgHJ&UXy?`G-tGzOsT0yFRN(OW^ON0EsksLqWbb7Ac<*5E zrMMwm-)-%^JUQid5F6S(-sWBxRFm~)@AQRrynAwFdOkQjJv_Ev@4ndE-ha?s51Q-N znu~lFf75q2op@1meLCMo1?ncvMMWMo*MsJ|C(R`~gkF?c(kTi6Gax0gI!^!6RIpB@ zf^~oj525-8F?%O5t2jjDZYs4XX4R(?vp0u`)IEoY=J$m?NZLP`q-|&EC`(E-jT-n( zsppcq8QC+T{9#rMdn+RLJGq z@{--8Gmy^jDZOXGtfNgY`mBOEaBjALoqM)_mu;tf5YK-w@jUagonbmx0hZxpR8Eos zhfK)D>;RWX)!*2xB-vqX6~B$JNu){T9~CYVXG588wuy=fQytQ_Yl33>j&ZPIVn z8{r!gV2Aa9UU8n6yo8V@!z9heBmiA6Ual8rMAl9deCx=7Z;cBw+PNc(z_*HyMc`9m z2H!fW!GOraTY&%AN=j=>;D2nlOB<+i_h+%3VL}b zTP4d)v(_6l`{zGJ$_<~C)(dVOj=-L@ZXKG|NqAdZy`FW%twZ$hQ|k}1`yqOC7YXjh zr>?uH{7pYxkl=3o=_I&+vjq1aB=~~_zefp9Vp_3w={Y&YRiJ3~a_e5BdA6>#CcgB+(DLC$sm>=P%od)4wLgLe7VS4%L^{B&g^;PM|Hs)1mZ%w zg9$!ZV7B5>pH)PBObmY~y!23%x_#VMOS<*<3H2B=+(G2nmhX1!iMd~(wu=`RNF z0D9zE5X?>gd9W9mT#^;s0f}anJ|N8S`TdG|1b8PK^SS?3y$=c9&lR8MN@-O1Sl(S{ zs!Jl9m-J&;r#{ADD$S6ArIH|>q9A0K}C@B>yR*=l;fhmNWX>WrbSS2F%#fy*1)yYYdJZ?hbL#* zmqp=|adKtt4Uh^YYu`X)t)u9k*;JZb*R#tnll1BfW6>jl$~r2@;=5YN+c$GMvN4C2 zoZIx@rkD@1cAhLz%zZpxTQP_C!EMG}e1{uznwPcE&3YVYjxhd`*bk>lUq5Xl9o+M* zl@`|{**RfBe!>?j~#7FSwm)_zuUsA&Rtwf>b2CoaH^!!Fq(eB;Ty~(zx$6 zfU@@Uvr%#rH8Ihm`!{mdW<-`eXL<6ak&q>&b=c`(m*8j(GbgxzA_nUc@k2h!zA*Z6 z(q)UYE?aL1yax>|8hCGzkMjgE?Q)48&ZlqJ*2eL@+t=~O*w^NeCgnSv`TEP^%I5-{ zMbOF2#X zE3MPy7i)X64kgun&vLYQw^?51pzjyvFYf`So5}dG@tnLYC-WTX-NI8YMmbKx*k~8$ zUz*0=Is9(-BenF$>g#EY{hF*u>qp+n2kT|y@sixFtDv1Kckh;6R4RVDq%qa`CsmBe zGJA>Nyx_D$Qj^R8@A_m^YptkEL3#I%6OvzF$nNA>uf6hs>7{iVr5mSITYtWo6yqo8 zQ#KRG{ibu=T46BBYVb^GT-g-9-}F_%tW9#*4)ye_s~!)XSmH64;FlT~wFQ;=`u zzO53D&lathGHW4%z4*is*^)-Fk-zw06U+6}s=`d&@hBOT*onVA(@2%@i_#@1~( z-L|W0w^^TC(aMadFlp#bLiVhm|#Q` zQF{vYj6}UF4pz4qWLLuij8cxTxL;yOK8$J8O3uonHyLM7tX|d`Z(6qRIL`2wC)I9` zZr*NZV59M9lFgEX_nlahTN)^&-Ml}v%lv#W8QKg0T!$l0qGhr6vDm0+&F6@oC+%2P z>J2&1-mRQB@22x*F{4p)WJLF`gz?@8E|X?ylJHp+tJzfpc|ssruZFX0_?c{svn3_^ z3P7K3R19xY3P{HE!JQDO#p5)UU$vxP^<7QyEMZB&AKbLE(Wn@e3n}tuav2yEAr<;r zS%HyM!{Z-jHtDYN0-tST>y*{aB$j9K2DE3z$Qtv; zBEFM{wa!V4UNPFV{`CAg4K6|-+Sj2|l_gtvl%*Ns@)h@e9$rsra+^=ytu@QpNa(dj zcJtf#4rqSrBkU9(2E8KTZxO9#@N{c9+`QRExIG0#!<1_xWmxQXjW31{h>B&4{`ACN zo|JgPXTEboe27f8k+*};k7F-#colkG_@?q|=|1Kaph8&LXG!}!yP>P6dH>vMXK8^E zgV_vE8)9yh-VmKP?$ThqF&Scv%|?4C+7MCUq@h@WJB!TiU05KUxz4fTJ%VL>(=s{ty&8P-FS9`@+wlLJZ92yD zlN)ulqxr3n*V?Vf`w5(Z+q2)&Na`(HZJKbUT@1%|UYT-F4IgsTCNf*GraZiD!z83; z>T~RnP_Q`yt=%AqmCkI(@3d|0M#_$|D6%6r){fJO;~)%XYKjK1KV)ap!0gNRdwVkK zH8?D74_d__LwGysiMP~#Uv5zKjY~J!I2>dr{OV-q`xZ7+w0Nd#ctb#lqW(CqbtIh~9KR?+ELGqBE7Vy>FjnC&GQVabFG;PjF!nC?anT4lyd zm33>pT7%C6%M6^tOP6LjZI^+qjDBmTkEa{LureQR6pzb!Ue1?5n6ThbUwtp%C(9!8 zu;}IT9rjdEE#ynSSB-a*E4gaN!fZgk3E}$wPC-QN-{lKIsd>}aflrerBI~roilt87d>Xk6#G#;;8ae$Yee_^*mN4Ffu#f4|Ld{?KA-SV{GQh4aAs1-O7T?9SV zavh)EDL-umO4%(Z)|T6HbYQoXPgjaAba+7}Vk#B}bP?Jvb)#QW}M1 z)Jg2=3tTGsj>QaOUy zC*%UE9=BY^-Hk((v4}`iimtk@Lv82_K<#0bqvaCu&`jtkTm-E+@@#GfbmCVG9bKd? zA25VrNYsm35z{&J0JH_5F9?=^(9nz!T?7~*!cYNc{Bgp4H)_Q~9wLbjBBG+FY=#4j zp^3TyK>z?lP&nYrD5a)iqB+3hk-~Tc7!DbvYM@+<+a>Xbqdm}^2or}9@UeKzi35m8 z9`gmU1+x!OCEx+Gqk(SN^8J|W`mVw-;X?n~cZr%rf?57lHGeV~K+2clCGKPbfq2A# zF;g)#EQvZT@T+oTAvM^-@TNVj?HL!PHb4xQ;}Ua)U(l#=0UnJ+~o2o zFch9G?f9+_T)0X%A{;Nkfjp#+LODiRE5WF0nCK+U#!-uh$75;$4iJ-x&b0L}U78u5 zO2MaI#E3Dg`wG9^nC7FG;uX4NhL9r69v-0_d2WSr7}q%aeuZdP1%# z`{^n7F$>9bFaptkO+czpRSgB8d^J2>TgK0p0pu4<1v9*tE(sv5=a46e8GE0R*Q&x@ zAQQlRmt;ijRXiuC2il+>(7dDSjtM9`GRyA#=!?4*g}ilJL?(wy1{e##4;5wj5Q7&5(Q87+4;Cjm|KC+4T*C_*BVQ&NX&6#u=$o}$ z5Zfq03{L|hqiK+e3?B#&knXr7RpqlYfDD*Ump1F$pbo=`LNpV@o?<^fNETr5OHpu2{e)K^UWK_H?4+fop| zQiA7<@-!%B?1Ua+UP5n>90QClW-#h&aFBMqo{#n{5|j}X zV62%!kZSzvv5p4POlvk-h$4X-To{gqNd}qkL3~3Z#xX>OUrl<<4w7+h0D%?LObDrj zN?8WlH0PDjge@%*B$(G>>)YKP-oL(jO z8{|Y!L^B5hiRY+?QaUthJ}}mwX$T9QK`aYN ztwGn2>FV=LVX4M*&4_hhqW+7WtB6dVNL+EHw?Ly3C7HwoRvmT=% zc415en&q;*n36&J6&V=$fG4A{7u{WmZ^IC6ATD{SAm8!H=t%15EY^fCJS|8 zG2dJ`?96DWX<-)TG?FwSEEK3{A)jl=Co{lk#h?dLktGjzM`N~tY@rKqY?ubYJgu}$ zawG0SA(ak=QZ(&+2$_^Yg^5bulgpY9tyoiPc@?$%S!!LdFeQWDSaZZLshA4PwZ1kE z5)-;27@P^T$G{UHS!CbR-nbrq}IebV{PyKF5LpM+1SVwzrZ+$ zsBm#x2SHUHf?Dn&s2SS<2Bw;ovnxG=aOxQ>bquC{L9oOxa4WySo%IWxYEC+;HbsqR z5H0ZxNae3|4JyxIsbg>pzrZzaL1f$lRhwfCw?GWzDdPqD*1kY|jW3{TU%+el0(eJ{ ztM}egFTe~uOAZ>Q<@Ue7%Kldk`(GRT->bc!=KK-`#((Lw0@@vV(RF z(pkr{b}K`PX4(~GV@ z&Z5LyB}(k6C0R=oxkf0#H=%^64kZNBP(o0L5>ypRh^Jon&7lN$8cNWOP=b47C?SYV zEFrF93Hth2f?5bAMDw8pzYZmY(@2895J~XYjwHlYBq3NDN${tU1h0-H=;e`w_?qCD zL=v1e!U)l_FoJG`5!^-?A-qc%!H=sj0?mbK96>kY2qqHMwOyKOt|Ar8QP5RVf91qc zpGgH@J8#?tEo_Xvj|hF=!Ln_HzP&2+J!9ZH%dA^>ZS&T*#WPwK^Zs4i98Dv)Vf|fi zt2EDj<(R2) z!GRs-Q39BiF9F9fo;ZN0@&mSsVkqq}mdK}#$Y8+H0LV7Pf*?ahjPqsCib%i0j0&`G zo`g^)0Fy*BPiaq~$Jk{jI+jM+V1cmqBHx;V^G~IyBpX}`M9{D)(&=g>2|b=Anq-hr z?1J&)OK!&!v|4e@ORYYnXNad>Bu>1={j>QE0C}BKvt3FT484pm8q$$1Pb0-41Vlny z&lPDd4KdwW*lK&)Kd9}pT|!a`z%qb+hbQL+WEpELGT}h*n9MB#T*G zx-b$;oAoJ4L^dX?=k5iEd`{LLRv!{thzJ}GmvC( zMZPtC4)|g>6urYJNP~Sv3ZMCyY)!WHeD2)EktiOfcXd#}bLO zVYYy{o>d>?0x_WI?$il#NCZ|CXJcz-RaHVP$bOH;vT0?UN(={Yy{nv9tMJ`x8o z;dzPGWlI^FRS1CJQf<&oLCl5CrA9~u3Qqhj*h#ZmBb3(BYs^MH_iw>0i7u5x=){KQ zXxjTS%Oak95ELrLg!O~iW|@cUu#2E>MCN-;l7Wp6$O$$-d8ETcQ32T$ChMhY2|>&= z8qf=27iJG%iy4#}?7=!R<6F0E(m4 zk?x6v52?XM(3u81_5!|EEJAo}MSl2PK|6fS0c*ku!Qq%kk|5C$`No_pGG2F)^GmQz`>L0EgtQo*(g z$mH=rZWTH$=rMAqNjDNExP*AetQ506bRuy;qY!2OIu@EKxA zZIb3t&G;UhEn~Q8#-68{WCHf=!_UMFmjtWl?RaBTNvMM3Yby2|QN;g%N$)mY;R~)O zzVNLU(;*TAzuNV1=Vf%Sv5fAD@2iaKTW{vLei2>W((?`O*YgeH+L2YPKQKT(4`PG} zUbUQlQ7?lnSx;YTBG)VFYKaT%8y{G&?*yymPOx5Zg57y1SednSz2;iF_`ptFod+=g z{lD3uKe+Xl`?5%pQKXj--YG2Gnm`>`U~%rpAu!+nI26rl0n*)qerllwc2#5baT%s-_7nf zCCSTA*V@Xu?Cc$@Q>2p;Te+wEyNQAp{nI@9A|i9um9^C_F+9yBL@_Ng661Zq0BIj| z$)M0q&?99O(WOtiiVOo{OTK8$YwSIUb&u2+sfB>%pZf6tYvv>p zu;!>qR5)I|PYfDi;wb%sq;MSlIK!(ewwO2RE9VN8=_tzv_9#o**~pe7fu8+um1Jys zGTrT1C?AyL?)sx=NHf3`VTCkn3wq5V#3G zip0Ex>Ps*sgv;XuQOr`EH(`)oB-4uNVo@oLGz( zk8!bqRUqhKF-aL@cpwm11G$5MBT0k|29(qs4`awt6+#UY9rgj^MhFF>iGdGu1$pZZ z2YploRWD8lja0<@XaKgL10REi2xgUnBM=B~k<0#p4tzm~88Bvyv|&I&NLVmrl!OW4 zL@DM$!&@f>^xKOp*a#4vTNW)yq5}=82c%j<0540U<(XeYB9SRh)V?6 zc%v02trsX>G4vy@;!0P5fule~Z43a@lWJjtg@`ysGJ1%bD8~b~9QO0E#D>x05-sDW z#6Thtj2H#(LkS9gKz^PMu{|;l7=FeDaer(K)QYH2@bk7hW^qD+$Pzn_Cy`?sF?Kb4 z4u1qL?>>SY0Xpm*@P-baS{ieLu(2u(LYQ`h0bBK$W`u#y!-x`O84@hR!s6f(NspFL?0YG`B{TsRZ^?y!D9C@R^~w8(89ipAZ3S(4=PG zBKQq1cuMnv;PjcFTwyI44vrQmg+Qccv)vB`8y@KZUaeU}!1ox!Su_?AGZqutW4;0k zz>N9IM_dLHqGmTD@&Rb zxP*&0Za^ua>X4s^H0gq$lZs7-?F3FD^ zW>#0aM?-9{GIBoFARFiJ$Hn_kXN^hwY>NF4f1UQ?-c9`J>2$;}9!{dX%toagzDkqq z-c7Tnv5#GrPS zyHr>ja)im}m=D_yG*2#ln$0oPQ>?gM*4mMeu62Nz9V`0SrF&Ag4sU~Yd5;D?;25wQ z@Vc=^Z3o>%U{->bw+B!WK+DRL3@RHyYYtVQLdZ)1p({!eP%EFe9cvEL`cOACaS+y} z4no9gHEcS->j(uPTOwBhUXofqFJHMp3PFaz@cpoS%#8Ac+5w(!J^*^8m1a#X=0WYSCI0nMiZh3?l9hhykFOn!*7F zK$pjf*S;W_9b;!OeAwL-nr0jxK2N9v;uCv#`~rI=_)8v%l-!5NJI&vp7Vke=-i;>X zu6golq#pMm4SrIw_t`yAr5}?a@tT;nYs1FIG7i>;g4gu8OV)K2X_%|$JVuqE<&os_ zUpQ!BMM-Q%cnq0ZmG0WOz%s;rMlnvh_^^L3Ee0RUO`}O4U-o|zK9aVXh0@<|bZqum zSd!*g2_h%M*I3>m4MJiW!W=7L4hd9}dk~9cE4~Mv?3l!;c)yYFA~< zFI-9&pEHvXh zd_3~j6+1nMh5FDvWcy&_2HcGR?m1ci?j?wU^LA-Mf^ojMDc?!b?5y~dCUeyTPyMO# z_?^cuV{8)zLk6*XiEqxL7ubUmUUHZz>|%+U<^pWL)PeOR`%rkPI}WjvC-TiTmYcDD z6T=6Wm2<36J6;kIpIiLLoFk1du&`2giS>He*w9%n>&17jxnS?zaRBgSu73Ar*gB=SRE;+Y4z5$o^Uw)1j{}AvD4+cI?Dh zyN+ZznYYow`tt+bg%_@a%Ls9TZLFroJgF5nIB9s;F1ZP~f=_{MA52p=%E&ZT@?S~+ zTh${fnmQ093PxcBU`UPomW&TfejVPYvVCs2`D5oDmaQgd*{7R!ri0(P%{F%oJFy&0 z{}O9V#FsGw;5y#I-g|6Kz+Zv}UJA);rM4aTKODS4FsAw~u~Q|6MkB@v!vtY4i~pdq z5*OCRmL=?9=1!`g5+BS`vABhxRg9et%6tkXUq^84obsza?uyyMB{zNWvwlI#hf61CeSB%!Df9O4T$xAt@;?-l zQ{tpWj+ZP>&Gq5A^Oshgd^Dej07)jWa9rmuc#4y+hST&G!z`Wj_>`#8q-PF%{rB5P zLt^`EoOa(n+WZ&Ymc?Y065njTebgpyLavLqj~*l6P&UG2Dv}@v6c@&ndDln&8MH@L zHvabZ(Ql9cjVU*l!gEFbB?pTAtu^cpxn0~P?S4K$)yK2PjaBFGExuWNQXNgU_-5SA z`dQgr^)qGu_Llzn<}aE7gm6naMi&xiD^Hco}xP zllQ2xQr=F}t?rD9;Xa{XJ?~eO`<+OOw3Oe@S=!6ne0g49_0xCh^fIe1%e40%*FQZL zp_KnpH98sh-^mnR!sh}4lG7cMl0U`CXFPXsXGvn#$MSx=SCrYb0N>7*K%IR#oSF(8 zEL5Jp<6C}NW=LMWq;GlAD6_Qxg;KLHoGfi*85~I8HGTdq-9Z$uOccjrYMAs7s-!vS zjP9*=HYVAX$)IpXpJtJ*fn@oyij?!iw_BFUCN&;iS;=`qY%Uy7Wj5LEpJna6HrI~A z)NRLc$4=c&$JSVAiv2cO&`#VT=iiMwsn5dHr1V*bDK8Vg`qxj_a0ks7lGZ=ajYEY< zg^y2Wp2+*1e3Y4FYy|5>^iy6QQ$sY3Hrb%!Cnw`PeLwqXyBLrqR)TEx({eIIBDL~p zl=n#*G~Nv5$y9vpXPxnN?~h?ME@^tN-^aQJw8Ipb>e(sj-V({q43?5R@Q_3hvIIQZ z987w>`Z1V&Z=Cg~&tWrHPnp)g%@lr~CF98`lQ+Ndh>x-jk=jD_$w+wd+Ju`~nG@m0 zu%S$vyA5!_b(-#0^3Sdyx=9S?ZZr_Mp<(#Ieu8~pt}PtR`q&x@tA9xHv2ZzxX2RP? z$b`Gk_bp&BgTQp_-_+1+n3BsNwhb8eSu$!K+_*d)WP|v~VtSd3vt^aIPur%+H5!wf zLR@At5=sWGseo^F6Jm2JH9b&uNjsfF*xqzoAE}Joo`;=I&Y}LfBtF*L zw-e9F4lVn@M_x0(>M_fdB zo>;}G*7eS@2AlBt)o-%1y#{xc?f0+dr1hN+u9l{bl5zJYCcl>xtI^$u6C6}6-sh({ zs+#+#F)zsmpCLaG`oO$HYEK74?6GPoL( zq!evfpSE2~s=7N70o2B*ez%{nUZ&^NSU)ezDLy;$Kro$*3t{=DWT!rNGBIP`K2nb9 ze#i#EPI4vgb7&dCvBp=-=hZWF*?ev!OWpW6xlH8oI<6(3 z*PW*u-+W_d|G0Yo^LY3B4?k}m9RKge-u~b)*Ts?EKYO+RCM?wVubi+GZ%lqT+1mSi z_v9q?4t2CU`fmHU^L{J$|M}h3k8j>P-?#H`UzMeMtiIdtgpNu+T=dRgZR~U|F24=W zht7u|dMAnckmlbXwh#9vm*bP(@aOM4&YSb+r%`bB{rH!IaMpVT+rk8Ma@fNiTyb@fYjg)>jxm;_%8Fcsn5t zs(a(nSDV(qtgmok{bc=S{dJLH=KHzMh#ivSl95Jb8K|f>nEI=e$aCUOg%aqL~1goRdBWJk*(~!OHA?cGZk#i8oRK2dGZvFSXKm4{6{ zk`dS^3;O@$ zeYl~?VElwP!O2)9u1ljb%o2RtF&-5r242U`SG^Y)p3UV#7z(=cErVVoiHk04c$A&9 zBR`Ywpjn5XkFs8NNzmgR(Mfcmfrbkt= obj.run.cutoff.date() for d in obj.cursor['realtime']['pending']) + # The same response cannot establish historical coverage through 2025. + with pytest.raises(ValueError, match='重复当前页面'): + list(obj.parse_listing(response(sample['url'], sample['body'], lane='history'))) + + +def test_undated_or_unordered_lists_do_not_stop_early(trading): + obj = spider(trading, 'treasury') + url = SOURCES[obj.name].entries[0] + body = '

News

Undated' + list(obj.parse_listing(response(url, body, lane='realtime'))) + assert obj.cursor['realtime']['next'].endswith('?page=2') + assert obj.cursor['realtime']['pending'][0]['url'].endswith('sb0002') + + +def test_failed_document_does_not_block_next_document_or_retry_immediately(trading): + obj = spider(trading, 'doj') + docs = [{'url': f'https://www.justice.gov/api/v1/press_releases/{uuid.uuid4()}.json', 'kind': 'doj'} for _ in range(2)] + obj.cursor['realtime']['pending'] = copy.deepcopy(docs) + request = Request(docs[0]['url'], meta={'document': docs[0], 'lane': 'realtime'}) + failed = Failure(HttpError(Response(request.url, status=503, request=request))) + failed.request = request + following, = obj.on_request_error(failed) + assert following.url == docs[1]['url'] + assert obj.run.errors and docs[0]['url'] not in obj.cursor.get('checked', {}) + gap = obj.cursor['document_retries'][docs[0]['url']] + assert gap['document'] == docs[0] and gap['attempts'] == 1 + assert gap['next_at'] > obj.run.started_at.timestamp() + + +@pytest.mark.parametrize('attempts,due,expected', [(1, False, False), (3, True, False), (1, True, True)]) +def test_document_retry_is_bounded_and_survives_rounds(trading, attempts, due, expected): + obj = spider(trading, 'ofac') + url = 'https://ofac.treasury.gov/recent-actions/20261104' + obj.cursor['document_retries'] = {url: {'document': {'url': url, 'kind': 'article'}, 'attempts': attempts, 'next_at': obj.run.started_at.timestamp() + (-60 if due else 3600), 'error': 'HTTP503'}} + listing = '

Actions

Sanctions' + requests = list(obj.parse_listing(response(SOURCES[obj.name].entries[0], listing, lane='realtime'))) + assert any(r.url == url for r in requests) is expected + assert url in obj.cursor['document_retries'] + + +def test_old_pending_cursor_cannot_bypass_exhausted_document_retry(trading): + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20261104', 'kind': 'article'} + obj.cursor['realtime']['pending'] = [doc] + obj.cursor['document_retries'] = {doc['url']: {'document': doc, 'attempts': 3, 'next_at': 0, 'error': 'unavailable'}} + requests = obj.advance('realtime') + assert all(r.url != doc['url'] for r in requests) + assert doc['url'] in obj.cursor['document_retries'] + + +def test_due_retry_in_old_pending_cursor_is_not_discarded_by_moving_window(trading): + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20250101', 'kind': 'article', 'listing_date': '2025-01-01'} + obj.cursor['realtime']['pending'] = [doc] + obj.cursor['document_retries'] = {doc['url']: {'document': copy.deepcopy(doc), 'attempts': 1, 'next_at': 0, 'error': 'unavailable'}} + request, = obj.first_requests() + assert request.meta['document']['document_retry'] + body = '

Crypto sanctions

New sanctions against a virtual currency exchange institution.

' + async def collect(): + return [item async for item in obj.parse_document(response(request.url, body, **request.meta))] + items = asyncio.run(collect()) + assert any(isinstance(item, dict) and item['url'] == doc['url'] for item in items) + assert not obj.cursor['document_retries'] + assert doc['url'] in obj.cursor['checked'] + + +@pytest.mark.parametrize('restored_pending', [False, True]) +def test_attachment_retry_outside_realtime_window_fetches_and_completes_pdf(trading, monkeypatch, restored_pending): + from alphapulse.crawler.spiders import us_official + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20250101', 'kind': 'article', 'listing_date': '2025-01-01'} + obj.cursor['attachment_retries'] = {doc['url']: {'document': doc, 'attempts': 1, 'next_at': 0}} + if restored_pending: + obj.cursor['realtime']['pending'] = [copy.deepcopy(doc)] + request, = obj.first_requests() + body = '

Crypto sanctions

New sanctions against a virtual currency exchange institution.

Order
' + async def collect(): + return [item async for item in obj.parse_document(response(request.url, body, **request.meta))] + attachment, = asyncio.run(collect()) + assert isinstance(attachment, Request) and attachment.url.endswith('/media/123/download') + assert obj.cursor['attachment_retries'][doc['url']]['attempts'] == 1 + assert doc['url'] not in obj.cursor.get('checked', {}) + monkeypatch.setattr(us_official, 'pdf_text', lambda body: 'The official sanctions order applies to a virtual currency exchange.') + items = list(obj.parse_attachment(Response(attachment.url, body=b'%PDF-test', request=attachment.replace(meta={**attachment.meta, 'raw_response_id': 13})))) + item = next(item for item in items if isinstance(item, dict)) + assert item['extra']['content_scope'] == 'article' + assert item['extra']['attachments'][0]['status'] == 'complete' + assert not obj.cursor['attachment_retries'] + assert doc['url'] in obj.cursor['checked'] + + +def test_due_attachment_is_queued_alongside_document_retry_without_duplicates(trading): + obj = spider(trading) + documents = [{'url': f'https://ofac.treasury.gov/recent-actions/2026110{day}', 'kind': 'article'} for day in (3, 4)] + obj.cursor['document_retries'] = {documents[0]['url']: {'document': documents[0], 'attempts': 1, 'next_at': 0, 'error': 'HTTP503'}} + obj.cursor['attachment_retries'] = {doc['url']: {'document': doc, 'attempts': 1, 'next_at': 0} for doc in documents} + request, = obj.first_requests() + pending = obj.cursor['realtime']['pending'] + assert [doc['url'] for doc in pending] == [doc['url'] for doc in documents] + assert request.meta['document']['document_retry'] and request.meta['document']['attachment_retry'] + requests = obj.complete('realtime') + assert requests[0].url == documents[1]['url'] and requests[0].meta['document']['attachment_retry'] + + +@pytest.mark.parametrize('first_lane', ['realtime', 'history']) +def test_successful_retry_is_not_requeued_by_other_lane(trading, first_lane): + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20261104', 'kind': 'article'} + obj.cursor['document_retries'] = {doc['url']: {'document': doc, 'attempts': 1, 'next_at': 0, 'error': 'HTTP503'}} + for lane in ('realtime', 'history'): + obj.cursor[lane]['pending'] = [copy.deepcopy(doc)] + request, = obj.advance(first_lane) + body = '

Crypto sanctions

New sanctions against a virtual currency exchange institution.

' + async def collect(): + return [item async for item in obj.parse_document(response(request.url, body, **request.meta))] + items = asyncio.run(collect()) + assert any(isinstance(item, dict) for item in items) + assert not obj.cursor['document_retries'] + other_lane = 'history' if first_lane == 'realtime' else 'realtime' + assert all(request.url != doc['url'] for request in obj.advance(other_lane)) + + +@pytest.mark.parametrize('first_lane', ['realtime', 'history']) +def test_document_retry_is_requested_once_across_both_lanes(trading, first_lane): + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20261104', 'kind': 'article'} + obj.cursor['document_retries'] = {doc['url']: {'document': doc, 'attempts': 1, 'next_at': 0, 'error': 'HTTP503'}} + for lane in ('realtime', 'history'): + obj.cursor[lane]['pending'] = [copy.deepcopy(doc)] + request, = obj.advance(first_lane) + other_lane = 'history' if first_lane == 'realtime' else 'realtime' + following = obj.advance(other_lane) + assert all(r.url != doc['url'] for r in following) + failure = Failure(HttpError(Response(request.url, status=503, request=request))) + failure.request = request + obj.on_request_error(failure) + assert obj.cursor['document_retries'][doc['url']]['attempts'] == 2 + assert doc['url'] not in obj.cursor.get('checked', {}) + # Completing a concurrent listing must not re-queue the same retry. + listing = '

Actions

Sanctions' + requests = list(obj.parse_listing(response(SOURCES[obj.name].entries[0], listing, lane=other_lane))) + assert all(r.url != doc['url'] for r in requests) + + +def test_concurrent_initial_document_failures_keep_one_attempt_and_all_errors(trading): + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20261104', 'kind': 'article'} + for lane in ('realtime', 'history'): + obj.cursor[lane]['pending'] = [copy.deepcopy(doc)] + requests = [obj.advance(lane)[0] for lane in ('realtime', 'history')] + for request in requests: + failure = Failure(HttpError(Response(request.url, status=503, request=request))) + failure.request = request + obj.on_request_error(failure) + assert obj.cursor['document_retries'][doc['url']]['attempts'] == 1 + assert len(obj.run.errors) == 2 + + +def test_document_retry_waits_one_hour_after_failure_in_long_round(trading): + obj = spider(trading) + obj.run.started_at = datetime.now(UTC) - timedelta(hours=2) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20261104', 'kind': 'article'} + obj.cursor['realtime']['pending'] = [doc] + request, = obj.advance('realtime') + failure = Failure(HttpError(Response(request.url, status=503, request=request))) + failure.request = request + before = datetime.now(UTC) + obj.on_request_error(failure) + after = datetime.now(UTC) + due_at = obj.cursor['document_retries'][doc['url']]['next_at'] + assert (before + timedelta(hours=1)).timestamp() <= due_at <= (after + timedelta(hours=1)).timestamp() + + +def test_pipeline_error_prevents_completed_read_checkpoint(trading): + obj = spider(trading) + obj.run.completed_documents_safe = True + obj._on_item_error(Failure(ValueError('test persistence failure'))) + assert not obj.run.completed_documents_safe + assert '落库失败' in obj.run.errors[0] + + +def test_completed_document_error_cannot_remove_next_pending_document(trading, monkeypatch): + obj = spider(trading) + doc = {'url': 'https://ofac.treasury.gov/recent-actions/20261104', 'kind': 'article'} + following = {'url': 'https://ofac.treasury.gov/recent-actions/20261103', 'kind': 'article'} + obj.cursor['realtime']['pending'] = [following] + async def scheduling_failed(res): + raise ValueError('next request scheduling failed') + yield + monkeypatch.setattr(obj, '_parse_document', scheduling_failed) + async def collect(): + return [item async for item in obj.parse_document(response(doc['url'], '', document=doc, lane='realtime'))] + with pytest.raises(ValueError, match='scheduling failed'): + asyncio.run(collect()) + assert obj.cursor['realtime']['pending'] == [following] + assert not obj.cursor.get('document_retries') + + +@pytest.mark.parametrize('safe', [False, True]) +def test_failed_round_saves_only_safe_completed_reads_and_explicit_gaps(trading, monkeypatch, safe): + from alphapulse.crawler.spiders import base + obj = spider(trading) + source = SimpleNamespace(history_target=obj.run.history_target, history_cursor={'history': {'entry': 0}, 'realtime': {'entry': 0}}, consecutive_failures=0) + class Session: + async def __aenter__(self): return self + async def __aexit__(self, *args): pass + async def execute(self, *args): pass + async def get(self, *args, **kwargs): return source + async def commit(self): pass + monkeypatch.setattr(base, 'SessionLocal', Session) + obj.run.errors = ['failed document'] + obj.run.completed_documents_safe = safe + obj.run.official_cursor = {'history': {'entry': 5}, 'realtime': {'entry': 8}, 'checked': {'good': 123}, 'document_retries': {'bad': {'attempts': 1}}} + asyncio.run(obj._finish_run(obj.run)) + assert source.history_cursor['history'] == {'entry': 0} + assert source.history_cursor['realtime'] == {'entry': 0} + assert ('checked' in source.history_cursor) is safe + assert ('document_retries' in source.history_cursor) is safe + if safe: + assert source.history_cursor['checked'] == {'good': 123} + assert 'bad' in source.history_cursor['document_retries'] + + +@pytest.mark.parametrize('status', [403, 429]) +def test_access_block_stops_round_and_honors_retry_after(trading, status): + obj = spider(trading, 'cftc') + doc = {'url': 'https://www.cftc.gov/PressRoom/PressReleases/9321-26', 'kind': 'article'} + obj.cursor['realtime']['pending'] = [doc] + request = Request(doc['url'], meta={'document': doc, 'lane': 'realtime'}) + failure = Failure(HttpError(Response(request.url, status=status, headers={'Retry-After': '3600'}, request=request))) + failure.request = request + obj.on_request_error(failure) + assert obj.run.blocked + assert obj.run.cooldown_until > datetime.now(UTC) + timedelta(minutes=59) + assert obj.advance('realtime') == [] + assert obj.cursor['realtime']['pending'] == [doc] + assert not obj.cursor.get('document_retries') + + +@pytest.mark.parametrize('later_block', ['403', '429', 'listing_challenge', 'document_challenge']) +def test_later_block_does_not_shorten_existing_retry_after(trading, later_block): + obj = spider(trading, 'cftc') + doc = {'url': 'https://www.cftc.gov/PressRoom/PressReleases/9321-26', 'kind': 'article'} + obj.cursor['realtime']['pending'] = [doc] + request = Request(doc['url'], meta={'document': doc, 'lane': 'realtime'}) + failure = Failure(HttpError(Response(request.url, status=429, headers={'Retry-After': '3600'}, request=request))) + failure.request = request + obj.on_request_error(failure) + until = obj.run.cooldown_until + if later_block in ('403', '429'): + failure = Failure(HttpError(Response(request.url, status=int(later_block), request=request))) + failure.request = request + obj.on_request_error(failure) + else: + body = '

Just a moment...

Enable JavaScript and cookies to continue.

' + if later_block == 'listing_challenge': + with pytest.raises(ValueError, match='验证页面'): + list(obj.parse_listing(response(SOURCES[obj.name].entries[0], body, lane='history'))) + else: + async def collect(): + return [item async for item in obj.parse_document(response(doc['url'], body, document=doc, lane='realtime'))] + assert asyncio.run(collect()) == [] + assert obj.run.blocked and obj.run.cooldown_until == until + + @pytest.mark.parametrize('source,title,text,expected', [ ('doj', 'Man sentenced for bitcoin theft', 'Personal bitcoin theft worth $300', False), ('doj', 'Exchange charged', 'Cryptocurrency exchange charged with money laundering', True), @@ -130,9 +440,9 @@ def test_source_api_keeps_attachment_gaps_visible_across_rounds(trading, monkeyp from alphapulse.models import Source source = Source(code='doj', name='DOJ', list_url=SOURCES['doj'].entries[0], enabled=False, interval_seconds=300, backfill_hours=72, - consecutive_failures=0, last_success_at=None, last_error=None, paused_until=None, history_cursor={'attachment_retries': {'a': {'attempts': 1}, 'b': {'attempts': 2}, 'c': {'attempts': 3}}}, + consecutive_failures=0, last_success_at=None, last_error=None, paused_until=None, history_cursor={'attachment_retries': {'a': {'attempts': 1}, 'b': {'attempts': 2}, 'c': {'attempts': 3}}, 'document_retries': {'d': {'attempts': 1}, 'e': {'attempts': 3}}}, history_target=None, history_position=None, history_done=False) - latest = SimpleNamespace(source_code='doj', stats={'discovered': 2, 'attachment_gaps': 999}) + latest = SimpleNamespace(source_code='doj', status='success', stats={'discovered': 2, 'attachment_gaps': 999}) class Session: async def scalars(self, statement): @@ -151,6 +461,10 @@ def test_source_api_keeps_attachment_gaps_visible_across_rounds(trading, monkeyp assert result.collection_stats['attachment_gaps'] == 3 assert result.collection_stats['attachment_retry_pending'] == 2 assert result.collection_stats['attachment_retry_exhausted'] == 1 + assert result.collection_stats['document_gaps'] == 2 + assert result.collection_stats['document_retry_pending'] == 1 + assert result.collection_stats['document_retry_exhausted'] == 1 + assert result.collection_stats['round_running'] == 0 source.history_cursor['attachment_retries'].pop('a') result = asyncio.run(crawler.list_sources(Session())).data.sources[0] assert result.collection_stats['attachment_gaps'] == 2 @@ -160,6 +474,10 @@ def test_source_api_keeps_attachment_gaps_visible_across_rounds(trading, monkeyp result = asyncio.run(crawler.list_sources(Session())).data.sources[0] assert result.collection_stats['attachment_gaps'] == 0 assert result.collection_stats['attachment_retry_exhausted'] == 0 + assert result.collection_stats['document_gaps'] == 0 + latest.status = 'running' + result = asyncio.run(crawler.list_sources(Session())).data.sources[0] + assert result.collection_stats['round_running'] == 1 def test_challenge_is_failure_not_empty_success(trading): @@ -367,10 +685,13 @@ def test_attachment_failure_is_visible_and_keeps_a_bounded_retry(trading): obj.cursor['history']['pending'] = [doc] parsed = {'url': url, 'title': 'Crypto sanctions', 'external_id': 'x', 'content_html': '

Virtual currency exchange sanctions.

', 'published_at': datetime(2025, 1, 1, tzinfo=UTC), 'raw_response_id': 12, 'retry_document': doc, 'extra': {'attachments': [{'url': 'https://ofac.treasury.gov/a.pdf', 'title': 'License', 'status': 'unavailable'}]}} + before = datetime.now(UTC) item, = obj.attach_or_finish(parsed, 'history') + after = datetime.now(UTC) assert item['extra']['content_scope'] == 'partial' assert obj.cursor['attachment_retries'][url]['attempts'] == 1 assert obj.cursor['attachment_retries'][url]['document'] == doc + assert (before + timedelta(hours=1)).timestamp() <= obj.cursor['attachment_retries'][url]['next_at'] <= (after + timedelta(hours=1)).timestamp() @pytest.mark.parametrize('canceled,failed', [(False, False), (True, False), (False, True)]) @@ -566,10 +887,13 @@ def test_missing_bill_material_is_a_retryable_gap(trading, monkeypatch): asyncio.run(parse({'bill': {'title': 'GENIUS Act'}})) asyncio.run(parse({'subjects': {}})) asyncio.run(parse({'summaries': []})) + before = datetime.now(UTC) asyncio.run(parse({'textVersions': []})) + after = datetime.now(UTC) assert obj.cursor['bill_retries'][base]['attempts'] == 1 assert obj.run.stats['unresolved_bills'] == 1 assert not obj.run.stats.get('excluded') + assert (before + timedelta(hours=1)).timestamp() <= obj.cursor['bill_retries'][base]['next_at'] <= (after + timedelta(hours=1)).timestamp() @pytest.mark.parametrize('attempts,version,expected_kind', [(3, 'v1', 'listing'), (1, 'v1', 'listing'), (3, 'v2', 'subjects')]) @@ -761,8 +1085,10 @@ def test_doj_body_identity_date_and_validation(trading, bad_body): async def collect(): return [v async for v in obj.parse_document(response(doc['url'], json.dumps({'metadata': {'responseInfo': {'status': 200}}, 'results': [record]}), lane='history', document=doc))] if bad_body: - with pytest.raises(ValueError): asyncio.run(collect()) - assert obj.cursor['history']['pending'] == [doc] + assert asyncio.run(collect()) == [] + assert obj.run.blocked and obj.run.errors + assert obj.cursor['document_retries'][doc['url']]['attempts'] == 1 + assert obj.cursor['history']['pending'] == [] else: item, = asyncio.run(collect()) assert item['url'] == 'https://www.justice.gov/opa/pr/test' diff --git a/deploy/README.md b/deploy/README.md index 3d2416d..e5c052a 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -263,9 +263,26 @@ list traversal does not prove complete bodies or attachments. Attachment failure remain visible on the news item, with up to three scheduled attempts one hour apart; exhausted gaps require explicit investigation or another traversal. +Article and DOJ document failures also remain explicit gaps, with at most three +scheduled attempts one hour apart, measured from the actual failure time. +Real-time and history lanes share the document retry quota; a document retry is +requested only once per round. Due body and attachment retries remain eligible +after their document leaves the real-time lookback window. +A failed round may reuse completed document +reads and retain retry evidence, but cannot advance its lane or history position; +a pipeline error prevents saving completed reads. HTTP 403/429 and detected +challenge pages stop the round for at least fifteen minutes; a longer valid +`Retry-After` is honored and subsequent blocks cannot shorten that deadline. +CFTC document requests are serialized with a three-second +delay. The console distinguishes active collection, scheduled waiting, failure +backoff, stopped processes and unresolved document gaps. + Real-time discovery follows pagination, including every Federal Register page within a day. Only verified descending feeds stop early at a fully dated page outside the lookback window; unknown ordering is traversed conservatively. +Treasury currently returns the first page again for subsequent page requests. +A dated real-time page can end discovery at the lookback boundary; a repeated +historical page still fails explicitly and cannot establish historical coverage. Congress screening reads subjects, CRS summaries, and available text before excluding a bill whose title alone is inconclusive. Missing bill evidence remains an unresolved gap, retried at most three times one hour apart per official update; diff --git a/scripts/smoke_us_news.py b/scripts/smoke_us_news.py index 82a1169..a708de6 100644 --- a/scripts/smoke_us_news.py +++ b/scripts/smoke_us_news.py @@ -178,13 +178,11 @@ async def main(): assert captured[0]['external_id'] == 'legacy-rss-guid' captured.clear() invalid = HtmlResponse(sample['url'], body=b'

Just a moment...

Enable JavaScript and cookies to continue.

', encoding='utf-8', request=request) - try: - async for item in obj.parse_document(invalid): - await pipeline.process_item(item) - except ValueError: - pass - else: - raise AssertionError('Challenge must fail before the pipeline') + obj.cursor['history']['pending'] = [doc] + assert not [item async for item in obj.parse_document(invalid)] + assert obj.run.errors and obj.run.blocked + assert obj.cursor['document_retries'][doc['url']]['attempts'] == 1 + assert not captured async with SessionLocal() as session: news = await session.scalar(select(NewsItem).where(NewsItem.external_id == 'legacy-rss-guid')) assert (news.content_hash, news.revision_no) == baseline @@ -216,6 +214,31 @@ async def main(): saved = await session.get(CrawlRun, original_id) assert saved.status == 'success' assert (await session.get(Source, 'ofac')).history_cursor['checkpoint_probe'] == 'committed' + # A failed document preserves safe reads and explicit gaps, not lane positions. + async with SessionLocal() as session: + before_cursor = copy.deepcopy((await session.get(Source, 'ofac')).history_cursor) + completed = {'https://ofac.treasury.gov/recent-actions/20261104': obj.run.started_at.timestamp()} + gap_url = 'https://ofac.treasury.gov/recent-actions/20261103' + gaps = {gap_url: {'document': {'url': gap_url, 'kind': 'article'}, 'attempts': 1, 'next_at': obj.run.started_at.timestamp()+3600, 'error': 'HTTP503'}} + failed_run = copy.deepcopy(obj.run) + failed_run.id = uuid.uuid4() + failed_run.errors = ['document unavailable'] + failed_run.completed_documents_safe = True + failed_run.official_cursor = {'checked': completed, 'document_retries': gaps, 'realtime': {'entry': 99}, 'history': {'entry': 99}} + await obj._finish_run(failed_run) + async with SessionLocal() as session: + cursor = (await session.get(Source, 'ofac')).history_cursor + assert cursor['checked'] == completed and cursor['document_retries'] == gaps + assert cursor.get('history') == before_cursor.get('history') + assert cursor.get('realtime') == before_cursor.get('realtime') + assert (await session.get(CrawlRun, failed_run.id)).status == 'failed' + unsafe_run = copy.deepcopy(failed_run) + unsafe_run.id = uuid.uuid4() + unsafe_run.completed_documents_safe = False + unsafe_run.official_cursor['checked'] = {'must-not-be-skipped': 1} + await obj._finish_run(unsafe_run) + async with SessionLocal() as session: + assert (await session.get(Source, 'ofac')).history_cursor['checked'] == completed # API/PDF packages use the real parser and database revision pipeline too. api_items = await public_api_items() + await congress_reference_items() + await congress_api_items() with patch.object(pipelines, 'redis', SimpleNamespace(publish=AsyncMock())): @@ -235,7 +258,7 @@ async def main(): async with SessionLocal() as session: assert await session.scalar(select(func.count()).select_from(NewsRevision)) == baseline_count + 1 await engine.dispose() - print('US source migration, HTML, API/PDF, GovInfo and Congress packages, API/PDF replay and revision, negative screening, daily XML, RSS identity, challenge rejection and checkpoint recovery passed') + print('US source migration, HTML, API/PDF, GovInfo and Congress packages, API/PDF replay and revision, negative screening, daily XML, RSS identity, challenge rejection, checkpoint recovery and failed-document persistence passed') if __name__ == '__main__': diff --git a/web/src/pages/crawler/components/SourcePanel.vue b/web/src/pages/crawler/components/SourcePanel.vue index 768b859..73b9109 100644 --- a/web/src/pages/crawler/components/SourcePanel.vue +++ b/web/src/pages/crawler/components/SourcePanel.vue @@ -7,6 +7,7 @@ import { useSessionScope } from "@@/composables/useSessionScope" import { formatTime } from "@@/utils/text" import { CircleCheck, RefreshRight } from "@element-plus/icons-vue" import dayjs from "dayjs" +import { sourceStatus } from "../sourceStatus" /** logs 只用来发现「本轮完成」以刷新最新入库;alert 为连续失败达到告警阈值 */ const props = defineProps<{ @@ -87,22 +88,7 @@ async function cancelHistory() { emit("changed") } -const statusTag = computed(() => { - if (props.source.configuration_error) - return { type: "warning", text: props.source.configuration_error } as const - const process = props.source.process - if (props.restarting || process?.state === "restarting") - return { type: "warning", text: "重启中" } as const - if (!process) - return { type: "danger", text: "管理进程未运行" } as const - if (process.state === "stopped") - return { type: "danger", text: "子进程未运行" } as const - if (!props.source.enabled) - return { type: "info", text: "已停用" } as const - if (props.source.paused_until && new Date(props.source.paused_until) > new Date()) - return { type: "warning", text: "失败退避中" } as const - return { type: "success", text: "运行中" } as const -}) +const statusTag = computed(() => sourceStatus(props.source, props.restarting)) onMounted(loadLatest) @@ -163,6 +149,9 @@ onMounted(loadLatest)
附件待核验文档 {{ source.collection_stats.attachment_gaps }};待重试 {{ source.collection_stats.attachment_retry_pending }};重试已耗尽 {{ source.collection_stats.attachment_retry_exhausted }},请核对原文
+
+ 正文待核验 {{ source.collection_stats.document_gaps }};待重试 {{ source.collection_stats.document_retry_pending }};重试已耗尽 {{ source.collection_stats.document_retry_exhausted }},请核对来源日志 +
本轮发现 {{ source.collection_stats.discovered }};筛除 {{ source.collection_stats.excluded ?? 0 }};不完整包 {{ source.collection_stats.partial ?? 0 }}
diff --git a/web/src/pages/crawler/index.vue b/web/src/pages/crawler/index.vue index 86d0f19..ec26d5e 100644 --- a/web/src/pages/crawler/index.vue +++ b/web/src/pages/crawler/index.vue @@ -1,5 +1,5 @@