From ac21a7c84a935ae1ad1d1ec31622bd02e78e9d71 Mon Sep 17 00:00:00 2001 From: "vladimir.buzalka" Date: Tue, 21 Apr 2026 12:12:08 +0200 Subject: [PATCH] z230 --- IWRS/create_accountability_report.py | 115 +++++++++++- IWRS/create_shipment_report.py | 163 ++++++++++++++++++ IWRS/download_ip_destruction.py | 102 +++++------ IWRS/download_reports.py | 64 +++---- IWRS/download_shipment_details.py | 121 +++++++------ IWRS/download_shipments_report.py | 38 ++-- ...4-10 42847922MDD3003 CZ IWRS overview.xlsx | Bin 74954 -> 0 bytes ...4-10 77242113UCO3001 CZ IWRS overview.xlsx | Bin 17875 -> 0 bytes ...4-21 42847922MDD3003 CZ IWRS overview.xlsx | Bin 0 -> 136125 bytes ...4-21 77242113UCO3001 CZ IWRS overview.xlsx | Bin 0 -> 20739 bytes ...26-04-21 77242113UCO3001 CZ Shipments.xlsx | Bin 0 -> 10441 bytes ...1 77242113UCO3001 CZ Shipments_100177.xlsx | Bin 0 -> 5873 bytes IWRS/run.py | 85 +++++++++ .../ip_destruction_basket_301.xlsx | Bin 0 -> 6422 bytes .../onsite_inventory_detail_S10-CZ10002.xlsx | Bin 6255 -> 6255 bytes .../onsite_inventory_detail_S10-CZ10004.xlsx | Bin 17523 -> 19795 bytes .../onsite_inventory_detail_S10-CZ10005.xlsx | Bin 6515 -> 6516 bytes .../onsite_inventory_detail_S10-CZ10008.xlsx | Bin 15083 -> 15392 bytes .../onsite_inventory_detail_S10-CZ10011.xlsx | Bin 17058 -> 17635 bytes .../onsite_inventory_detail_S10-CZ10012.xlsx | Bin 10611 -> 11171 bytes .../onsite_inventory_detail_DD5-CZ10001.xlsx | Bin 6016 -> 6011 bytes .../onsite_inventory_detail_DD5-CZ10003.xlsx | Bin 5045 -> 5042 bytes .../onsite_inventory_detail_DD5-CZ10006.xlsx | Bin 5764 -> 5760 bytes .../onsite_inventory_detail_DD5-CZ10009.xlsx | Bin 5501 -> 5505 bytes .../onsite_inventory_detail_DD5-CZ10010.xlsx | Bin 5048 -> 5044 bytes .../onsite_inventory_detail_DD5-CZ10012.xlsx | Bin 5690 -> 5687 bytes .../onsite_inventory_detail_DD5-CZ10013.xlsx | Bin 5774 -> 5773 bytes .../onsite_inventory_detail_DD5-CZ10015.xlsx | Bin 5040 -> 5040 bytes .../onsite_inventory_detail_DD5-CZ10016.xlsx | Bin 5041 -> 5045 bytes .../onsite_inventory_detail_DD5-CZ10020.xlsx | Bin 5397 -> 5396 bytes .../onsite_inventory_detail_DD5-CZ10021.xlsx | Bin 5683 -> 5683 bytes .../onsite_inventory_detail_DD5-CZ10022.xlsx | Bin 5931 -> 5931 bytes .../shipment_details_100873.xlsx | Bin 0 -> 5478 bytes .../shipment_details_100874.xlsx | Bin 0 -> 5478 bytes .../shipment_details_100880.xlsx | Bin 0 -> 5346 bytes .../shipment_details_100881.xlsx | Bin 0 -> 5345 bytes .../shipment_details_100895.xlsx | Bin 0 -> 5490 bytes .../shipment_details_100905.xlsx | Bin 0 -> 5361 bytes .../shipment_details_100946.xlsx | Bin 0 -> 5476 bytes .../shipment_details_100971.xlsx | Bin 0 -> 5479 bytes .../shipment_details_100980.xlsx | Bin 0 -> 5349 bytes .../shipment_details_100986.xlsx | Bin 0 -> 5477 bytes .../shipment_details_100994.xlsx | Bin 0 -> 5346 bytes .../shipment_details_101085.xlsx | Bin 0 -> 5477 bytes .../shipment_details_101092.xlsx | Bin 0 -> 5345 bytes .../shipment_details_101102.xlsx | Bin 0 -> 5479 bytes .../shipment_details_101110.xlsx | Bin 0 -> 5828 bytes .../shipment_details_101117.xlsx | Bin 0 -> 5473 bytes .../shipment_details_101118.xlsx | Bin 0 -> 5674 bytes .../shipment_details_101119.xlsx | Bin 0 -> 5745 bytes .../shipment_details_101139.xlsx | Bin 0 -> 5476 bytes .../shipment_details_101246.xlsx | Bin 0 -> 5966 bytes .../shipment_details_101270.xlsx | Bin 0 -> 5488 bytes .../shipment_details_101274.xlsx | Bin 0 -> 5637 bytes .../shipment_details_101293.xlsx | Bin 0 -> 5571 bytes .../shipment_details_101300.xlsx | Bin 0 -> 5898 bytes .../shipment_details_101322.xlsx | Bin 0 -> 5704 bytes .../shipment_details_101327.xlsx | Bin 0 -> 5758 bytes .../shipment_details_101357.xlsx | Bin 0 -> 6035 bytes .../shipment_details_101378.xlsx | Bin 0 -> 5743 bytes .../shipment_details_101385.xlsx | Bin 0 -> 5748 bytes .../shipment_details_101418.xlsx | Bin 0 -> 5906 bytes .../shipment_details_101444.xlsx | Bin 0 -> 5902 bytes .../shipment_details_101487.xlsx | Bin 0 -> 5570 bytes .../shipment_details_101508.xlsx | Bin 0 -> 5706 bytes .../shipment_details_101524.xlsx | Bin 0 -> 5710 bytes .../shipment_details_101530.xlsx | Bin 0 -> 6264 bytes .../shipment_details_101531.xlsx | Bin 0 -> 5478 bytes .../shipment_details_101555.xlsx | Bin 0 -> 5969 bytes .../shipment_details_101589.xlsx | Bin 0 -> 5679 bytes .../shipment_details_101662.xlsx | Bin 0 -> 6214 bytes .../shipment_details_101688.xlsx | Bin 0 -> 5710 bytes .../shipment_details_101700.xlsx | Bin 0 -> 5705 bytes .../shipment_details_101720.xlsx | Bin 0 -> 5486 bytes .../shipment_details_101732.xlsx | Bin 0 -> 5842 bytes .../shipment_details_101738.xlsx | Bin 0 -> 5349 bytes .../shipment_details_101750.xlsx | Bin 0 -> 5477 bytes .../shipment_details_101751.xlsx | Bin 0 -> 5848 bytes .../shipment_details_101784.xlsx | Bin 0 -> 5647 bytes .../shipment_details_101785.xlsx | Bin 0 -> 5780 bytes .../shipment_details_101827.xlsx | Bin 0 -> 6282 bytes .../shipment_details_101858.xlsx | Bin 0 -> 5480 bytes .../shipment_details_101910.xlsx | Bin 0 -> 6030 bytes .../shipment_details_101919.xlsx | Bin 0 -> 5704 bytes .../shipment_details_101925.xlsx | Bin 0 -> 5829 bytes .../shipment_details_101962.xlsx | Bin 0 -> 5682 bytes .../shipment_details_101963.xlsx | Bin 0 -> 5935 bytes .../shipment_details_101964.xlsx | Bin 0 -> 6216 bytes .../shipment_details_101965.xlsx | Bin 0 -> 6089 bytes .../shipment_details_101966.xlsx | Bin 0 -> 5766 bytes .../shipment_details_101967.xlsx | Bin 0 -> 5479 bytes .../shipment_details_102071.xlsx | Bin 0 -> 5903 bytes .../shipment_details_102075.xlsx | Bin 0 -> 5748 bytes .../shipment_details_102094.xlsx | Bin 0 -> 5548 bytes .../shipment_details_102108.xlsx | Bin 0 -> 5547 bytes .../shipment_details_102136.xlsx | Bin 0 -> 6395 bytes .../shipment_details_102137.xlsx | Bin 0 -> 6325 bytes .../shipment_details_102160.xlsx | Bin 0 -> 6090 bytes .../shipment_details_102193.xlsx | Bin 0 -> 5972 bytes .../shipment_details_102199.xlsx | Bin 0 -> 5964 bytes .../shipment_details_102247.xlsx | Bin 0 -> 5753 bytes .../shipment_details_102256.xlsx | Bin 0 -> 5751 bytes .../shipment_details_102275.xlsx | Bin 0 -> 5574 bytes .../shipment_details_102295.xlsx | Bin 0 -> 5620 bytes .../shipment_details_102322.xlsx | Bin 0 -> 6449 bytes .../shipment_details_102341.xlsx | Bin 0 -> 6095 bytes .../shipment_details_102403.xlsx | Bin 0 -> 5748 bytes .../shipment_details_102418.xlsx | Bin 0 -> 5754 bytes .../shipment_details_102439.xlsx | Bin 0 -> 7247 bytes .../shipment_details_102455.xlsx | Bin 0 -> 5488 bytes .../shipments_report_42847922MDD3003.xlsx | Bin 0 -> 187748 bytes 111 files changed, 505 insertions(+), 183 deletions(-) create mode 100644 IWRS/create_shipment_report.py delete mode 100644 IWRS/output/2026-04-10 42847922MDD3003 CZ IWRS overview.xlsx delete mode 100644 IWRS/output/2026-04-10 77242113UCO3001 CZ IWRS overview.xlsx create mode 100644 IWRS/output/2026-04-21 42847922MDD3003 CZ IWRS overview.xlsx create mode 100644 IWRS/output/2026-04-21 77242113UCO3001 CZ IWRS overview.xlsx create mode 100644 IWRS/output/2026-04-21 77242113UCO3001 CZ Shipments.xlsx create mode 100644 IWRS/output/2026-04-21 77242113UCO3001 CZ Shipments_100177.xlsx create mode 100644 IWRS/run.py create mode 100644 IWRS/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_301.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100873.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100874.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100880.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100881.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100895.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100905.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100946.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100971.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100980.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100986.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_100994.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101085.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101092.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101102.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101110.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101117.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101118.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101119.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101139.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101246.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101270.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101274.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101293.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101300.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101322.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101327.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101357.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101378.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101385.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101418.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101444.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101487.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101508.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101524.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101530.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101531.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101555.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101589.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101662.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101688.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101700.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101720.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101732.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101738.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101750.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101751.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101784.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101785.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101827.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101858.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101910.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101919.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101925.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101962.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101963.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101964.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101965.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101966.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_101967.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102071.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102075.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102094.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102108.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102136.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102137.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102160.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102193.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102199.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102247.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102256.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102275.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102295.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102322.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102341.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102403.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102418.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102439.xlsx create mode 100644 IWRS/xls_shipment_details_42847922MDD3003/shipment_details_102455.xlsx create mode 100644 IWRS/xls_shipments_42847922MDD3003/shipments_report_42847922MDD3003.xlsx diff --git a/IWRS/create_accountability_report.py b/IWRS/create_accountability_report.py index dfee950..6727f2c 100644 --- a/IWRS/create_accountability_report.py +++ b/IWRS/create_accountability_report.py @@ -5,14 +5,25 @@ from openpyxl import load_workbook from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter -# STUDY = "42847922MDD3003" -STUDY = "77242113UCO3001" +STUDY = "42847922MDD3003" +#STUDY = "77242113UCO3001" INVENTORY_DIR = Path(f"xls_reports_{STUDY}") DESTRUCTION_DIR = Path(f"xls_ip_destruction_{STUDY}") +SHIPMENTS_FILE = Path(f"xls_shipments_{STUDY}/shipments_report_{STUDY}.xlsx") +DETAILS_DIR = Path(f"xls_shipment_details_{STUDY}") OUTPUT_DIR = Path("output") OUTPUT_FILE = OUTPUT_DIR / f"{date.today().strftime('%Y-%m-%d')} {STUDY} CZ IWRS overview.xlsx" +SHIPMENT_DROP_COLS = { + "Location", "Shipped Date", "Delivered Date [UTC]", + "Delivery Recipient", "Delivery Details", "Cancelled Date", + "Tracking #", "Total Medication IDs", + "Shipping Category", "Study", "Destination Location", "Destination Site", + "Medication type", "Container ID", "Quantity of Medication IDs", + "Packaged Lot description", +} + # ── Shared constants ────────────────────────────────────────────────────────── COLUMN_RENAMES = { @@ -144,6 +155,85 @@ def format_sheet(ws, header_color, highlight_col=None, highlight_color=None): ws.freeze_panes = "A2" +# ── Shipment helpers ───────────────────────────────────────────────────────── + +def build_shipments(): + sh = pd.read_excel(SHIPMENTS_FILE, sheet_name=0, header=5) + sh.columns = sh.columns.str.strip() + sh = sh.dropna(how="all") + sh["Shipment ID"] = sh["Shipment ID"].astype(str).str.strip() + sh = sh.drop(columns=[c for c in SHIPMENT_DROP_COLS if c in sh.columns]) + shipment_cols = list(sh.columns) + + all_rows = [] + for _, s_row in sh.iterrows(): + sid = s_row["Shipment ID"] + path = DETAILS_DIR / f"shipment_details_{sid}.xlsx" + if not path.exists(): + continue + det = pd.read_excel(path, sheet_name=0, header=5) + det.columns = det.columns.str.strip() + det = det.dropna(how="all") + det["Shipment"] = det["Shipment"].astype(str).str.strip() + extra_cols = [c for c in det.columns if c not in shipment_cols and c != "Shipment" and c not in SHIPMENT_DROP_COLS] + for _, d_row in det.iterrows(): + all_rows.append({**s_row.to_dict(), **{c: d_row[c] for c in extra_cols}}) + + result = pd.DataFrame(all_rows) + all_cols = shipment_cols + [c for c in extra_cols if c in result.columns] + result = result[all_cols] + + for col in ["Request Date", "Received Date", "Expiration Date"]: + if col in result.columns: + result[col] = pd.to_datetime(result[col], errors="coerce") + + print(f" Shipments: {result['Shipment ID'].nunique()} shipments, {len(result)} kitu") + return result + + +def build_site_summary(result): + STATUS_COLS = ["Available", "Assigned", "Dispensed", "Returned by Subject"] + pivot = result.groupby("Ship To:")["Status"].value_counts().unstack(fill_value=0) + for s in STATUS_COLS: + if s not in pivot.columns: + pivot[s] = 0 + pivot = pivot[STATUS_COLS].reset_index().rename(columns={ + "Ship To:": "Site", "Returned by Subject": "Returned" + }) + pivot = pivot.sort_values("Site").reset_index(drop=True) + pivot["Total"] = pivot[["Available", "Assigned", "Dispensed", "Returned"]].sum(axis=1) + print(f" Site Summary: {len(pivot)} center") + return pivot + + +def format_shipment_sheet(ws, header_color_ship, header_color_detail, n_ship_cols): + thin = Side(style="thin", color="000000") + border = Border(left=thin, right=thin, top=thin, bottom=thin) + hfont = Font(bold=True, color="FFFFFF", name="Arial", size=10) + dfont = Font(name="Arial", size=10) + fill_ship = PatternFill("solid", start_color=header_color_ship) + fill_detail = PatternFill("solid", start_color=header_color_detail) + + for cell in ws[1]: + cell.fill = fill_ship if cell.column <= n_ship_cols else fill_detail + cell.font = hfont + cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) + cell.border = border + ws.column_dimensions[get_column_letter(cell.column)].width = min(len(str(cell.value or "")) + 4, 35) + ws.row_dimensions[1].height = 30 + + for row in ws.iter_rows(min_row=2, max_row=ws.max_row): + for cell in row: + cell.font = dfont + cell.border = border + cell.alignment = Alignment(horizontal="center", vertical="center") + if cell.value.__class__.__name__ in ("datetime", "date", "Timestamp"): + cell.number_format = "DD-MMM-YYYY" + + ws.auto_filter.ref = ws.dimensions + ws.freeze_panes = "A2" + + # ── Build DataFrames ────────────────────────────────────────────────────────── def build_main(lookup): @@ -234,13 +324,19 @@ def main(): not_returned_df = build_not_returned(df) destruction_df = build_kits_for_destruction(df) + shipments_df = build_shipments() + site_summary_df = build_site_summary(shipments_df) + n_ship_cols = shipments_df.columns.tolist().index("Investigator") # first detail col index (0-based) + # Write all sheets with pd.ExcelWriter(OUTPUT_FILE, engine="openpyxl") as writer: - df.to_excel( writer, index=False, sheet_name="CountryMedicationOverview") - expired_df.to_excel( writer, index=False, sheet_name=expired_sheet) - assigned_df.to_excel( writer, index=False, sheet_name="Assigned not dispensed") - not_returned_df.to_excel(writer, index=False, sheet_name="Not returned") - destruction_df.to_excel( writer, index=False, sheet_name="Kits for destruction") + df.to_excel( writer, index=False, sheet_name="CountryMedicationOverview") + expired_df.to_excel( writer, index=False, sheet_name=expired_sheet) + assigned_df.to_excel( writer, index=False, sheet_name="Assigned not dispensed") + not_returned_df.to_excel( writer, index=False, sheet_name="Not returned") + destruction_df.to_excel( writer, index=False, sheet_name="Kits for destruction") + shipments_df.to_excel( writer, index=False, sheet_name="Shipments") + site_summary_df.to_excel( writer, index=False, sheet_name="Site Summary") # Format all sheets wb = load_workbook(OUTPUT_FILE) @@ -261,9 +357,12 @@ def main(): format_sheet(wb["Assigned not dispensed"], header_color="833C00", highlight_col="Subject ID", highlight_color="FFF2CC") format_sheet(wb["Not returned"], header_color="375623", highlight_col="Max Visit Date", highlight_color="E2EFDA") format_sheet(wb["Kits for destruction"], header_color="595959") + format_shipment_sheet(wb["Shipments"], "1F4E79", "375623", n_ship_cols) + format_sheet(wb["Site Summary"], header_color="1F4E79") wb.save(OUTPUT_FILE) print(f"\nSaved: {OUTPUT_FILE} ({len(df)} rows on main sheet, {wb.sheetnames})") -main() +if __name__ == "__main__": + main() diff --git a/IWRS/create_shipment_report.py b/IWRS/create_shipment_report.py new file mode 100644 index 0000000..32599c6 --- /dev/null +++ b/IWRS/create_shipment_report.py @@ -0,0 +1,163 @@ +import pandas as pd +import openpyxl +from openpyxl.styles import Font, PatternFill, Alignment, Border, Side +from openpyxl.utils import get_column_letter +from datetime import date +import os + +STUDY = "77242113UCO3001" +SHIPMENTS_FILE = f"xls_shipments_{STUDY}/shipments_report_{STUDY}.xlsx" +DETAILS_DIR = f"xls_shipment_details_{STUDY}" +OUTPUT_DIR = "output" +TEST_SHIPMENT = None # None = vsechny shipments + +DROP_COLS = { + "Location", "Shipped Date", "Delivered Date [UTC]", + "Delivery Recipient", "Delivery Details", "Cancelled Date", + "Tracking #", "Total Medication IDs", + "Shipping Category", "Study", "Destination Location", "Destination Site", + "Medication type", "Container ID", "Quantity of Medication IDs", + "Packaged Lot description", +} + +os.makedirs(OUTPUT_DIR, exist_ok=True) + + +def read_shipments(): + df = pd.read_excel(SHIPMENTS_FILE, sheet_name=0, header=5) + df.columns = df.columns.str.strip() + df = df.dropna(how="all") + df["Shipment ID"] = df["Shipment ID"].astype(str).str.strip() + df = df.drop(columns=[c for c in DROP_COLS if c in df.columns]) + return df + + +def read_details(shipment_id): + path = os.path.join(DETAILS_DIR, f"shipment_details_{shipment_id}.xlsx") + if not os.path.exists(path): + return None + df = pd.read_excel(path, sheet_name=0, header=5) + df.columns = df.columns.str.strip() + df = df.dropna(how="all") + df["Shipment"] = df["Shipment"].astype(str).str.strip() + return df + + +def build_report(): + shipments = read_shipments() + if TEST_SHIPMENT: + shipments = shipments[shipments["Shipment ID"] == TEST_SHIPMENT] + + shipment_cols = list(shipments.columns) + all_rows = [] + + for _, s_row in shipments.iterrows(): + sid = s_row["Shipment ID"] + details = read_details(sid) + if details is None: + continue + extra_cols = [c for c in details.columns if c not in shipment_cols and c != "Shipment" and c not in DROP_COLS] + for _, d_row in details.iterrows(): + row = {**s_row.to_dict(), **{c: d_row[c] for c in extra_cols}} + all_rows.append(row) + print(f" [{sid}] {len(details)} kitu") + + result = pd.DataFrame(all_rows) + all_cols = shipment_cols + [c for c in extra_cols if c in result.columns] + result = result[all_cols] + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "Shipments" + + HEADER_FILL_SHIP = PatternFill("solid", fgColor="1F4E79") + HEADER_FILL_DETAIL = PatternFill("solid", fgColor="375623") + HEADER_FONT = Font(name="Arial", bold=True, color="FFFFFF", size=10) + DATA_FONT = Font(name="Arial", size=10) + BORDER = Border( + left=Side(style="thin", color="BFBFBF"), + right=Side(style="thin", color="BFBFBF"), + bottom=Side(style="thin", color="BFBFBF"), + ) + + n_ship = len(shipment_cols) + for ci, col in enumerate(all_cols, 1): + cell = ws.cell(row=1, column=ci, value=col) + cell.font = HEADER_FONT + cell.fill = HEADER_FILL_SHIP if ci <= n_ship else HEADER_FILL_DETAIL + cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) + cell.border = BORDER + ws.row_dimensions[1].height = 30 + + for ri, (_, row) in enumerate(result.iterrows(), 2): + for ci, col in enumerate(all_cols, 1): + val = row[col] + if pd.isna(val): + val = None + elif hasattr(val, "date"): + val = val.date() + cell = ws.cell(row=ri, column=ci, value=val) + cell.font = DATA_FONT + cell.border = BORDER + cell.alignment = Alignment(horizontal="center", vertical="center") + if isinstance(val, date): + cell.number_format = "DD-MMM-YYYY" + + ws.auto_filter.ref = ws.dimensions + ws.freeze_panes = "A2" + + for ci, col in enumerate(all_cols, 1): + vals = [col] + [str(result.iloc[r][col]) for r in range(len(result)) if pd.notna(result.iloc[r][col])] + ws.column_dimensions[get_column_letter(ci)].width = min(max((len(v) for v in vals), default=10) + 2, 35) + + # --- Sheet 2: Site Summary --- + STATUS_COLS = ["Available", "Assigned", "Dispensed", "Returned by Subject"] + pivot = result.groupby("Ship To:")["Status"].value_counts().unstack(fill_value=0) + for s in STATUS_COLS: + if s not in pivot.columns: + pivot[s] = 0 + pivot = pivot[STATUS_COLS].reset_index().rename(columns={"Ship To:": "Site", "Returned by Subject": "Returned"}) + pivot = pivot.sort_values("Site").reset_index(drop=True) + pivot["Total"] = pivot[["Available", "Assigned", "Dispensed", "Returned"]].sum(axis=1) + + ws2 = wb.create_sheet("Site Summary") + summary_cols = ["Site", "Available", "Assigned", "Dispensed", "Returned", "Total"] + HEADER_FILL_SUMM = PatternFill("solid", fgColor="1F4E79") + + for ci, col in enumerate(summary_cols, 1): + cell = ws2.cell(row=1, column=ci, value=col) + cell.font = HEADER_FONT + cell.fill = HEADER_FILL_SUMM + cell.alignment = Alignment(horizontal="center", vertical="center") + cell.border = BORDER + ws2.row_dimensions[1].height = 25 + + for ri, (_, row) in enumerate(pivot.iterrows(), 2): + for ci, col in enumerate(summary_cols, 1): + cell = ws2.cell(row=ri, column=ci, value=row[col]) + cell.font = DATA_FONT + cell.border = BORDER + cell.alignment = Alignment(horizontal="center", vertical="center") + + for ci, col in enumerate(summary_cols, 1): + vals = [col] + [str(pivot.iloc[r][col]) for r in range(len(pivot))] + ws2.column_dimensions[get_column_letter(ci)].width = min(max(len(v) for v in vals) + 4, 35) + + ws2.freeze_panes = "A2" + + suffix = f"_{TEST_SHIPMENT}" if TEST_SHIPMENT else "" + pattern = f"{STUDY} CZ Shipments{suffix}.xlsx" + for old in os.listdir(OUTPUT_DIR): + if old.endswith(pattern): + try: + os.remove(os.path.join(OUTPUT_DIR, old)) + print(f"Smazan -> {old}") + except OSError: + print(f"Preskakuji smazani (soubor otevren?) -> {old}") + + outfile = os.path.join(OUTPUT_DIR, f"{date.today()} {STUDY} CZ Shipments{suffix}.xlsx") + wb.save(outfile) + print(f"\nUlozeno -> {outfile}") + + +build_report() diff --git a/IWRS/download_ip_destruction.py b/IWRS/download_ip_destruction.py index f2f7288..d139bd2 100644 --- a/IWRS/download_ip_destruction.py +++ b/IWRS/download_ip_destruction.py @@ -13,78 +13,64 @@ STUDY = "77242113UCO3001" OUTPUT_DIR = f"xls_ip_destruction_{STUDY}" # ──────────────────────────────────────────────────────────────────────────── -os.makedirs(OUTPUT_DIR, exist_ok=True) +def run(page, study): + output_dir = f"xls_ip_destruction_{study}" + os.makedirs(output_dir, exist_ok=True) + + page.goto(f"{BASE_URL}/report/ip_destruction_form") + page.wait_for_load_state("networkidle", timeout=120000) + + page.locator('input[placeholder="search"], input[type="text"]').first.click() + page.wait_for_timeout(1000) + baskets = [b.strip() for b in page.locator('mat-option').all_inner_texts() + if b.strip() and b.strip() != "No results found"] + print(f" Nalezeno {len(baskets)} kosiku: {baskets}") + page.keyboard.press("Escape") + page.wait_for_timeout(500) + + if not baskets: + print(" Zadne destruction kosite — preskakuji.") + return + + for basket in baskets: + filename = os.path.join(output_dir, f"ip_destruction_basket_{basket}.xlsx") + if os.path.exists(filename): + print(f" [{basket}] Preskakuji — existuje.") + continue + print(f" [{basket}] Stahuji...") + input_field = page.locator('input[placeholder="search"], input[type="text"]').first + input_field.click() + input_field.fill(basket) + page.wait_for_timeout(500) + page.locator('mat-option').first.dispatch_event('click') + page.wait_for_load_state("networkidle", timeout=120000) + + with page.expect_download(timeout=120000) as dl: + page.get_by_role("button", name="Download XLS").click() + dl.value.save_as(filename) + print(f" [{basket}] OK") + + page.get_by_role("button", name="Clear").click() + page.wait_for_load_state("networkidle", timeout=120000) + + print(" Destruction hotovo.") -def download_ip_destruction(): +if __name__ == "__main__": + from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context(accept_downloads=True) page = context.new_page() - - # Přihlášení page.goto(BASE_URL) page.wait_for_load_state("networkidle") page.get_by_label("Email *").fill(EMAIL) page.get_by_label("Password *").fill(PASSWORD) page.locator('#login__submit').click() page.wait_for_load_state("networkidle") - - # Výběr studie page.get_by_label("Study *").click() page.get_by_role("option", name=STUDY).click() page.get_by_role("button", name="SELECT").click() page.wait_for_load_state("networkidle") - - # Naviguj na report - page.goto(f"{BASE_URL}/report/ip_destruction_form") - page.wait_for_load_state("networkidle", timeout=15000) - - # Přečti dostupné košíky - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.wait_for_timeout(1000) - baskets = [b.strip() for b in page.locator('mat-option').all_inner_texts() - if b.strip() != "No results found"] - print(f"Nalezeno {len(baskets)} košíků: {baskets}") - page.keyboard.press("Escape") - page.wait_for_timeout(500) - - if not baskets: - print("Žádné destruction košíky nenalezeny — přeskakuji.") - browser.close() - return - - for basket in baskets: - filename = os.path.join(OUTPUT_DIR, f"ip_destruction_basket_{basket}.xlsx") - if os.path.exists(filename): - print(f"[{basket}] Přeskakuji — soubor již existuje.") - continue - print(f"[{basket}] Stahuji...") - - # Otevři dropdown a vyber košík přes dispatch_event - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(basket) - page.wait_for_timeout(500) - page.locator('mat-option').first.dispatch_event('click') - - # Počkej na načtení dat - page.wait_for_load_state("networkidle", timeout=30000) - - # Stáhni XLS - with page.expect_download(timeout=30000) as dl: - page.get_by_role("button", name="Download XLS").click() - - download = dl.value - download.save_as(filename) - print(f"[{basket}] Uloženo → {filename}") - - # Reset pro další košík - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=15000) - + run(page, STUDY) browser.close() - print("\nHotovo!") - - -download_ip_destruction() diff --git a/IWRS/download_reports.py b/IWRS/download_reports.py index f8bf0fb..e67955a 100644 --- a/IWRS/download_reports.py +++ b/IWRS/download_reports.py @@ -38,58 +38,46 @@ SITES = { OUTPUT_DIR = f"xls_reports_{STUDY}" # ──────────────────────────────────────────────────────────────────────────── -os.makedirs(OUTPUT_DIR, exist_ok=True) +def run(page, study): + output_dir = f"xls_reports_{study}" + os.makedirs(output_dir, exist_ok=True) + + page.goto(f"{BASE_URL}/report/onsite_inventory_detail") + page.wait_for_load_state("networkidle", timeout=120000) + + for site_id in SITES[study]: + print(f" [{site_id}] Stahuji...") + page.locator('input[placeholder="search"], input[type="text"]').first.click() + page.get_by_role("option", name=site_id).click() + page.wait_for_load_state("networkidle", timeout=120000) + + with page.expect_download(timeout=120000) as dl: + page.get_by_role("button", name="Download XLS").click() + + dl.value.save_as(os.path.join(output_dir, f"onsite_inventory_detail_{site_id}.xlsx")) + print(f" [{site_id}] OK") + + page.get_by_role("button", name="Clear").click() + page.wait_for_load_state("networkidle", timeout=120000) + + print(" Inventory hotovo.") -def download_reports(): +if __name__ == "__main__": + from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context(accept_downloads=True) page = context.new_page() - - # Přihlášení page.goto(BASE_URL) page.wait_for_load_state("networkidle") page.get_by_label("Email *").fill(EMAIL) page.get_by_label("Password *").fill(PASSWORD) page.locator('#login__submit').click() page.wait_for_load_state("networkidle") - - # Výběr studie page.get_by_label("Study *").click() page.get_by_role("option", name=STUDY).click() page.get_by_role("button", name="SELECT").click() page.wait_for_load_state("networkidle") - - # Naviguj na report stránku - page.goto(f"{BASE_URL}/report/onsite_inventory_detail") - page.wait_for_load_state("networkidle", timeout=15000) - - for site_id in SITES[STUDY]: - print(f"[{site_id}] Stahuji...") - - # Otevři dropdown a vyber site - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.get_by_role("option", name=site_id).click() - - # Počkej na dokončení načítání dat (síť se uklidní) - page.wait_for_load_state("networkidle", timeout=30000) - - # Stáhni XLS - with page.expect_download(timeout=30000) as dl: - page.get_by_role("button", name="Download XLS").click() - - download = dl.value - filename = os.path.join(OUTPUT_DIR, f"onsite_inventory_detail_{site_id}.xlsx") - download.save_as(filename) - print(f"[{site_id}] Ulozeno: {filename}") - - # Zruš výběr site pro další iteraci - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=15000) - + run(page, STUDY) browser.close() - print("\nHotovo! Všechny reporty staženy.") - - -download_reports() diff --git a/IWRS/download_shipment_details.py b/IWRS/download_shipment_details.py index dc73308..d024bb5 100644 --- a/IWRS/download_shipment_details.py +++ b/IWRS/download_shipment_details.py @@ -1,5 +1,6 @@ from playwright.sync_api import sync_playwright import os +import pandas as pd # ── CONFIG ────────────────────────────────────────────────────────────────── BASE_URL = "https://janssen.4gclinical.com" @@ -7,80 +8,88 @@ BASE_URL = "https://janssen.4gclinical.com" EMAIL = "vbuzalka@its.jnj.com" PASSWORD = "Vlado123++-+" -# STUDY = "42847922MDD3003" -STUDY = "77242113UCO3001" +STUDY = "42847922MDD3003" +#STUDY = "77242113UCO3001" OUTPUT_DIR = f"xls_shipment_details_{STUDY}" # ──────────────────────────────────────────────────────────────────────────── -os.makedirs(OUTPUT_DIR, exist_ok=True) +def get_cz_shipment_ids(study): + path = f"xls_shipments_{study}/shipments_report_{study}.xlsx" + if not os.path.exists(path): + return None + df = pd.read_excel(path, header=5) + df.columns = df.columns.str.strip() + df = df.dropna(how="all") + df["Shipment ID"] = df["Shipment ID"].astype(str).str.strip() + cz = df[df["Location"].str.contains("Czech", na=False, case=False)] + return cz["Shipment ID"].tolist() -def download_shipment_details(): +def run(page, study): + output_dir = f"xls_shipment_details_{study}" + os.makedirs(output_dir, exist_ok=True) + + page.goto(f"{BASE_URL}/report/shipment_details_report") + page.wait_for_load_state("networkidle", timeout=120000) + + cz_ids = get_cz_shipment_ids(study) + if cz_ids is not None: + shipments = cz_ids + print(f" Filtrovano ze shipments reportu: {len(shipments)} CZ shipmentu") + else: + page.locator('input[placeholder="search"], input[type="text"]').first.click() + page.wait_for_timeout(1000) + shipments = [s.strip() for s in page.locator('mat-option').all_inner_texts() + if s.strip() and s.strip() != "No results found"] + print(f" Nalezeno {len(shipments)} shipmentu z dropdownu") + page.keyboard.press("Escape") + page.wait_for_timeout(500) + + if not shipments: + print(" Zadne shipments — preskakuji.") + return + + for shipment in shipments: + filename = os.path.join(output_dir, f"shipment_details_{shipment}.xlsx") + if os.path.exists(filename): + print(f" [{shipment}] Preskakuji — existuje.") + continue + print(f" [{shipment}] Stahuji...") + + input_field = page.locator('input[placeholder="search"], input[type="text"]').first + input_field.click() + input_field.fill(shipment) + page.wait_for_timeout(500) + page.locator('mat-option').first.dispatch_event('click') + page.wait_for_load_state("networkidle", timeout=120000) + + with page.expect_download(timeout=120000) as dl: + page.get_by_role("button", name="Download XLS").click() + dl.value.save_as(filename) + print(f" [{shipment}] OK") + + page.get_by_role("button", name="Clear").click() + page.wait_for_load_state("networkidle", timeout=120000) + + print(" Shipment details hotovo.") + + +if __name__ == "__main__": + from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context(accept_downloads=True) page = context.new_page() - - # Prihlaseni page.goto(BASE_URL) page.wait_for_load_state("networkidle") page.get_by_label("Email *").fill(EMAIL) page.get_by_label("Password *").fill(PASSWORD) page.locator('#login__submit').click() page.wait_for_load_state("networkidle") - - # Vyber studie page.get_by_label("Study *").click() page.get_by_role("option", name=STUDY).click() page.get_by_role("button", name="SELECT").click() page.wait_for_load_state("networkidle") - - # Naviguj na Shipment Details Report - page.goto(f"{BASE_URL}/report/shipment_details_report") - page.wait_for_load_state("networkidle", timeout=15000) - - # Precti dostupne shipments z dropdownu - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.wait_for_timeout(1000) - shipments = [s.strip() for s in page.locator('mat-option').all_inner_texts() - if s.strip() and s.strip() != "No results found"] - print(f"Nalezeno {len(shipments)} shipmentu: {shipments}") - page.keyboard.press("Escape") - page.wait_for_timeout(500) - - if not shipments: - print("Zadne shipments nenalezeny — konec.") - browser.close() - return - - for shipment in shipments: - safe_name = shipment.replace("/", "-").replace("\\", "-").replace(" ", "_") - filename = os.path.join(OUTPUT_DIR, f"shipment_details_{safe_name}.xlsx") - if os.path.exists(filename): - print(f"[{shipment}] Preskakuji — soubor jiz existuje.") - continue - print(f"[{shipment}] Stahuji...") - - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(shipment) - page.wait_for_timeout(500) - page.locator('mat-option').first.dispatch_event('click') - - page.wait_for_load_state("networkidle", timeout=30000) - - with page.expect_download(timeout=30000) as dl: - page.get_by_role("button", name="Download XLS").click() - - dl.value.save_as(filename) - print(f"[{shipment}] Ulozeno -> {filename}") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=15000) - + run(page, STUDY) browser.close() - print("\nHotovo!") - - -download_shipment_details() diff --git a/IWRS/download_shipments_report.py b/IWRS/download_shipments_report.py index 2138757..5644e97 100644 --- a/IWRS/download_shipments_report.py +++ b/IWRS/download_shipments_report.py @@ -13,43 +13,35 @@ STUDY = "77242113UCO3001" OUTPUT_DIR = f"xls_shipments_{STUDY}" # ──────────────────────────────────────────────────────────────────────────── -os.makedirs(OUTPUT_DIR, exist_ok=True) +def run(page, study): + output_dir = f"xls_shipments_{study}" + os.makedirs(output_dir, exist_ok=True) + + page.goto(f"{BASE_URL}/report/shipments_report") + page.wait_for_load_state("networkidle", timeout=120000) + + filename = os.path.join(output_dir, f"shipments_report_{study}.xlsx") + with page.expect_download(timeout=120000) as dl: + page.get_by_role("button", name="Download XLS").click() + dl.value.save_as(filename) + print(f" Shipments report OK -> {filename}") -def download_shipments(): +if __name__ == "__main__": + from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context(accept_downloads=True) page = context.new_page() - - # Prihlaseni page.goto(BASE_URL) page.wait_for_load_state("networkidle") page.get_by_label("Email *").fill(EMAIL) page.get_by_label("Password *").fill(PASSWORD) page.locator('#login__submit').click() page.wait_for_load_state("networkidle") - - # Vyber studie page.get_by_label("Study *").click() page.get_by_role("option", name=STUDY).click() page.get_by_role("button", name="SELECT").click() page.wait_for_load_state("networkidle") - - # Naviguj na Shipments Report - page.goto(f"{BASE_URL}/report/shipments_report") - page.wait_for_load_state("networkidle", timeout=15000) - - # Stahni report - filename = os.path.join(OUTPUT_DIR, f"shipments_report_{STUDY}.xlsx") - with page.expect_download(timeout=30000) as dl: - page.get_by_role("button", name="Download XLS").click() - - dl.value.save_as(filename) - print(f"Ulozeno -> {filename}") - + run(page, STUDY) browser.close() - print("Hotovo!") - - -download_shipments() diff --git a/IWRS/output/2026-04-10 42847922MDD3003 CZ IWRS overview.xlsx b/IWRS/output/2026-04-10 42847922MDD3003 CZ IWRS overview.xlsx deleted file mode 100644 index c609cbabf7bded2393640019caab2027b2547671..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74954 zcmbrmby$?`7Bx-{9YaeuNOvkQbcZP2B`ruxgLEjMh?IngC^<;i&>%=jgE*8RC7^W8 z?-}Gh=e+OneAo9~zdy#hWY50$UTf{O?`J?)109151qB5g<(754nR2g|Y8vpjLEr@j zUUu%bx}NSHUi{V`9(?|8u3E!dcpU=xmG)U8!+btbwVZB|r#ssi zvJW0T>Z2*(i(_W5eW|p^x8f!&)Jp)FWI2@?=+BSOUkNN1oDWZgQWF(2(4%)2xJ`vc z3F1q9TB@sDALmnc@ELv~?^h*thrM&jNJI+C!BFyr78kr0z$TMGsv()q4MHT`9U#B^ zYPH|}+ztf8YNXWt+YvBJYF_;WQs+iPK_ULnBiOoo+9S^}nA)!0DS#hs3HtU`YQ2ZG zfR7UVR!iQ7B-Zq~zp3Ig<+wXdb#Hl1u_CY*Zk!FT4X3)0zHuUzc_U`S9CK21q}BJi zOY|0r5>E0Uam&!RWNZa5ID3VYe-XN(+t&ls{G}iey_%^OwXzkl1M}S)(a)^o<3++> zGpQJ4^@Q6?QC&xD^Y9DE+QuaWU!4|3b6Z6cv**Ha9UYeLU69`L3CnpF?CbS@v$4ul zv%wk*wPJ4YSM`HMd3HK{LRi%^@A&%r=^yzM&SK_S?vhs+fGjB@b}-VKr((q4;m{4QM=ZRyn$*k5cdj}ka$B1rir6U0^^sjlWGGBU3_S3HU^`W- zG3z!4&-&SrlFHh5iATSF@Y#Hij(z^q-)e?`ZnCM4|LcOG5G%2q0M*-y-8S4enie%C zPAHuc!5>LUI=@6DU~Bl34NRIXm{RkzOm0-q%^O5Ix#r(cv9k)c;BkbE+`D7qZpm2v zjY0UBeBfS^{cBWGLpk<_*y^7ruHEOeUuEov*QMx$!XF6G7%_$pxxZb}Hi=!l*U{DH z>>z94(mVQDH7w#|@N-b3+TrUla%>@jTT(ejL%u{B%%HdSMb{espKAbI2DSDrDS7+=f7`~b1n2nocsx#)|H$V0yuBU9o zpP+S5C#JJJuBT+gd((-XkA)@S=lMEt65$_t+)D5bTtBqgiNd0VbP^S{qx7$rw!_UsWzOUrIeKw^fKjhKOgR^(W zVrm`3@!%GzrxdM&e9-XWVagjeq2Zkq+)eG<4h#k_D$R$7S6(H0vNX*f8U-Kfs61eq zde`=WdFr_`t5R+sZ{rHFM2#AO5UDJ7PV`G>U0Sq49Kjd)(Z|(2rNj%Q!F8?Jeek5% zqZ0Q_X~TRYwb{sHurl>hw%4lHsOuTl8|BeM{pK&P(~Zbc({I2<-iz{dPx_~|?UiNU zrXwX8e2#}H7oE<^PV!DNL`#+QlKaihP|w^hcxZ%i@yeo8t^Iq% z!2OH*kmw0vSDq(FD&v&uD@5#XW!Hs7uwG{x#dTOJXJiWr?0?a@%sPdJs=oq0UodqxN}g4ZDQ; zKJ=4^HvItpEr;8wbhUnYbfRa6=DsT|Zv^h9E7ViVx1zf>=6sQoc=a-+0e4I<*}d~* zz1yF9Npc3GA^4@C+uHQ#=-FEPoyylcw_nip#{Mi|`jH>~a=23==24*KONX=`T@f-j z*HTs<01e!_Mmeo7X)c0Dt-4@OEVh^TOk!3emS)YnJ=w3gsb(^UJ3Xp50Q_%zo5OjOwj~#*#*h zNkJ*JqWdE?8Iu%s@cBo@U9^yV!OPo6833rRUCuBE4*{=}SE-mb-Fny2hSNSYEUew&uy}>5xR(30m&VUCw zyc~!*i*e~{N@?X^uQL|tKg4)t1=M#al_HEL=}&~Z50Lhx%=;ltC@8lgu~BaP9w2`1o-SUF_V(Uh{8ztSO_9|%G#GW7gg=hz%WEA9T+Qy3pn%;A)5OYqSzKg3TR z%S(q}F`*}Kk3!G(mz#2PSBczZF1O$2ehoR^f#1G3zLW*tXQ^pB)Fb3*4S2tE+2aG) z`53=D;@tbu>3)pmD$y`vz5=FyvN*4!BO4lYbh#$Z@al3o7j_2kMw}{)`Q|=7W6hz*INx7|@x#uKe+)Rf%d{ZAEw0PT%u8P! zFMDp=2i}`@dKbu!<+JGqL>Pq7Fo}Bm=B;>zDe)-gfWXrX$%)_)f_2etPI-IpW74 zkq%<-&|Nk}3WgXC4Lof$8C(kp4L~gS@*{S#5a%(q#jwC%Z(+efzP}C@9I#|t&Q2H* zh&jY*WB(#*OdsmEDfIpeRQr{%jZM^v%P=ud$i+ceKq$nOaBvEp`XKCZ6LtGXn2=b^ zk4@AZDS5;}ScqR6c$CQUpi>*(d+8dpz2mFPOAfyrKZE&DotaY3F*FH;Zcj5*OkaM9 zItf%satqzHK~O#|Gk{)nCtT#-mPy-ox7b2_9}QjaJ~ZTCkqv}i%qLthw8mbt)yW1! z5pyakoOQAxPM62f(6(=fz)4cX5!kW(>p$n}a=%^<$Oii(a@}QuCS|A2&KX)2$J1AX z3Df*!L!WF6yak#%=QrFNe40Mcq@#n_-_#jSlR+H8F2|-`hb%K39Pf8#!4VX+erpUz zyO(2GV`rA(=%Jeo62bmQYvQo83d^szbx2Mwazg99R=zVlx*UVss)P(bIsZ16B^wlU zib8lH{!`^-jsXE*`$c521V8KL%e_1sL|h)P<+i6BU8cZfE8Hg$XuWh7%QE~ru6ym-|1KD=zmhImmROdQ&$F;sf7H zM2|(pW0@?caA|u#kDGk?&L%%z_~2o_=w#^4*||`@nAJmu4u;G84Q2$m-`8WdRcLKZB&F2!he!N>U8a*r!rGK}isFLBB zl)**uUg9nTrHO4>;o*Cd*^k9s_b0Bp5HR-zCBS?ZRj#r z!n4sxJGwt-r|n$9 zm)~*jTmT`+<`9K2rtc$8s1!}e74Be;hd}Mx)k@+cd&HbZ#=zq>n&bfjqza_r2?(#HI zs=2lO!IQ62rF@ybh}~rTBLR^&L14hTyh_NluoC0UyU|RGJLd> zO3L84q<^2!xPIkRi>~RUF^O$+ATJSaZpX$qJm8&{6{+#tz zOH>JGe5x>2v)#IW^Bb|76oyAw(K9=i=X9CYaBnKLr;cTL>k^yoY`qa<^v(S&>Dk8+ z(h4E3j~#3dKfZO8ltx7dlW~4gc?gn?XZ`sa@?Axr@=#M{=BeWtQz^Pg4l7MQzKFT< zC5s7=ly;cMN9Y1RcE!&=HV&DQu98n?RURKR0vmR@T=Lr)m2e64%pN8eXgfDji*6j?^QeitX^K8yAEnIg@Q{j``3um{mUrmkD82U00 zWoQgjcO0AG)bfi`jAvC}S^d8K>VZVqfJT3uViqA2W8OOJ47PpVx=lAF zWP9lx-i)}^pe1Op8z6}rd$@f#q5}116gnd z9CooB>J&Ol(cwZ zew^`_1E5Ve9^C~tYP?uIR4C&|WmW~~!rX*yI4QrG=P-a91^z$bMoAT>s9i6(-|ag| z?#+4cGsuquVjf@hj?1g#O{qrp2XG#mltyZjYP8fqU5Vcd%a|MwV%2KdMUy1S#RAF4 zq~$yMQj4c-=cMH;o+OiY>|$M{<#c`79{Kix9fMX5yx>>p+Vzpc4|VFlr44iH%V71$ z;~ZyKRczDoQT3qKYbcHzMVLNIQk~qN_|dBeCvEcNNtR-z7}@w?i|Xi`;~nWmzcCLh z##+*k1L<`nfd;>^mVVT9b%df|&i10!HY2{xv&H1pC}N}}MWuojLwL+^3iQ}wJ$O(e zifJ2rdeB&6m!M%U8Nhg{u8Klrc&+Br6z5Vr6m1)sMwQg0c#cDi6hRewJjZP3M#*N{ z9hslHqHo~&GLqtrR3$~>`%aPwYBN$`FR%22iB?uob6;cC-{2*e5EC+T_=U78aDuw#?rW zN~FZAq4<9)hPU=Jgi$47dVOPr(lk~Fn?6P$-N1=~k3VdhSMXx^f8<`o<= zJZc7LR?B`M#mtTT`)HIR!}Er%Jr#Jl4_6{ zA2KO0&uGI*cO6*C^{A*f$%rgTL!svy_$lxb%M#x(0Tvcbn;DGCdqw7C977f#BOdH} z>XGU*qcE?pFpgkGi#JB9Z4=ir!qkJ-JFx(3=xo1z+#g<&MqO@DwBS5JZdj7)QG>ha z>^4Q%1RJNL>WPa|@0<_;n#D*FYc5HlN=Jg{=yL=`5jitz^AdwXnz^75^?GPtLw2NS zMjQbzMH>WoN5rZt51>K@K-f3`4%@DvWISh-M`NKRi;6BOaz=1N4Nzj?=Yuj7LW|CY zL!uNTI>VtvhJFlq&)-WeNzNIm_D-nc$WpNW*1oHxE5DV`^vV;Om3{W8;Q|?!l-HZ2 zMV9De8*ZK!z;`kr(R(&>zcaph6_qdnRNZ)HRGE3FA4qZs!wUzUf#BmWK zrLp0e4%g&?3`E-uT04}D6j?J;^c7i$@L*UbXxMCXL1f6kc~nAoPla-;nEgsfz;TSea1|&o^Zex++RrE}75g3jk z$B$88WJ$`uWXvm&aLv_yIQYMG?25WA(!*8`Wz@VBHdZ-gc!rbZ>Q^O5iz$-c%BL1e zJQA=xlgI@ahu%WY2{#;H~E|axV5p{56b>7V;QtzoefT^`YV# zP~S9q6BaQ+PF9)_!&=N<_V$Fl z)J;|OV<_V_7X&FJZpQNNgr<3Z1dt`7Td9r;+T^}z#1S4L+E)9DpEBM${iUDX4Gs!t zRg_Gl5ujP&MvT7SjAADn4rS86czxWvZ*fe=@F(v@$LxPhXGzz&1K`TINh+?U1oh@$sBdL_6&7-Yn5*e5gOE} z5~6oU262lY(EmPxA(tv@6s-l(j4OmjxEnqN!3ZB|MgIrm8~ z@Yd!Ovk1k;bEEnyUmp?|Kd*fXyl!2;Rp)T1@19W@I421NNETImlx8X2yG|Q9tL?C! zu87D;=|2NMHj1{?ybS8=gX>~w`MXWTE{24I^u?_#$k77zy&f$_xAbD>6|qa{Mz&$pkP)2_Dv5 zRt~oTMqM&NgxsG1CGDDyB{Nbee7WI1@C?0GJ(+4^Kh2 z9Z2!IN=WbuBWduiVKibB&c8)vDh5sN9GZ1Jn-%2*UVy}@*=$p{&8EdTe?KJSrs`>s zcCQ|;bp8!an{8FPp8^bJ<#C{L$&MPqx)h?W(&u6+2n8W%m_Ri172tP4jhY?doV!F+3H=%auI7X)RbD1sG`&NuSx+R5YA?-)HIB37}d zFbjX{#yg~L;0$*~zr^*;>y#nSv(Li)h}R@W%|2ad6){Lo>Y6IhjwxqqZ#e4`WoR3b z6pe$-GBcm%@Le(vY2qY~N$!qwFI#WcqeTbbKN)xe_(XY%ACO(4`LZpf^aIb6d$b$n zw1U#(VyaIU|2j)@jG&yz3-Y0ceR6qp@O(tbUB>s`{-=kcfY4sX_!Wl4GM*qReRs zh+Xx-?T7K$wQ8|ecwuEfN7C*iOobAEO~NK*f=d9xqUS@l`tuP|#4kaNaBqymh>$T587WB9w@2ah`{o#GII?7adD3XK8;%UPBULTINCB8Q1iRh^UKMt>o3@5LWIUG# z#&cqUCgJ$Yw;f%HW$rVGAW6HmT)GoyE=_hWwIau2HXnS`Xz=m^-HiH|`%$=mntVI| zV$8pMiEbtOHiL-GfIf@}xnx!--6-4DKa?`bZ%IvZBbo8{+OIPzcKHR8LGEkRGk+N= zS3nQ~a@DUkEB^DlXcpg6G_-^A;tVqy>=sjnk31LkC&dae_jGV(ZwNZx+Kk7lRr9w> zuqrEXdw=oNyHCKv!O8y$R6YjCDlo~;KSu7B1xer|2eVk{c77t3je*oM`))WA1J8lI zYPdd#LB{n_s7V9h{cEWiri*sNx6_DRpB=YRjGD~n!x3Z6z}#!d+Y3j|z2|rt#}K`k zZxL-+%t;?Xeb5;*RV-v}DUr3kc_pq`7*P8xJV^jUXc1zR*o@!TI{t+zb5n!Wck?Bv z?FQ%=NUwL3QV12WWKD9ED1gfo|DJ67LHzrA*xyZFCHeudpS;*62q1)1Mup`ifzYr| zp&lTNIaWv|sNUd_e`|t(`|%CN8=)_WKFXd6SR!vUY_8B+8FPZh2=I!4VP)Etrw}~? zP!zns6vZ|iJp#G=(l%k&f{Cl45U`v$MI$%!G#gU{ojgXud#=N#N6&zv^<6{}iPe7% zEk9!9LKP;`gtxmqkuk0%uc@l*WWLEc`GfjCW_} z5pr*(!@$sD^~M12vn=9b`>KUmVWGC?zdLe2271Tp71}WY4>8F!WN?9%r&rT8Hm>c6u0JtX&&s;I{xM_$Ctp z^*DyeQj`Z-rFQ`_iAo9IwCMVaGE6%K{Uuy7Ku{>-ZaBo&2GzzD@GK{gzk3##P$KV~ zQQuzLA|XkH7zH-d_uh^)@K#VzTz5owar~O5Q$)S1l|+xTz@ zn-m8z=Bs998&lc%!14V>IoscZ zp#i}T)%8$lQhJ(GBhPb!+19h{#lbk5ukU-1Oxgk$aHM#)U4hKCbRvtqNlAubT~ zFOfC63cBzHaO>K()J_ zT|pBpSwZ7mT#W#7rm$xht2RSH-}XW^6>C76Ll-;STzRoW!z!o_Gff~$7P-B7CP5NF z0Y1iP{1sp_fz1Umh0OK8ZdeX) zy|Eo|YkK;7f_-UR0{3#I#L?bwZ|Vq|gW=qf$vP<^UGP zi5p{-O*~)8I*t zbXzD;o{@P&VFkc>8>d;4ON$dO_-Bxp!nahqV#58Nq`F2{DcA!;rV%hNeoxREV?|bT zShA~w*Io|s{T=<6?e|@0TD?BC^BpoZ-FOraUZ!wEF=OhM!O`0y7MzquFnF2oI;%pb z7?reu=S$dOn#s)XI?*6uWGOuuVO^i4!5{QcSRwI$&202Ah4|?zf-u@-8`)iySk!oS zlxXfG3D(hA^g`l+q1*h7of4=5jv~lHe!@h7a$}*q^CCd0raahL-1vcp;&>>){K~5i zQOmri9x|l(#Mjgkm{v$tHQ9U|epvRDH*0d(+JTO+`XJ`UV@Fd2E-j&2+hSN)XKm1B zMert!T7p(&0T_t7m?pBM9)KnEk~J|OJ4N$|OrB^Gk{+$19n%+u|1GbeOdCY&`PapScWT5ue6a3`^*@8w0 zLIbH!2nRGmU{qP$K_Lw9X*LBetYy(c87*c4fsZfhUc<>|(`yoJ!grVzV(vL@9P^|u zBvH7!EHnx?hPBuO5@~2yy?vRk23)Bgh!+0^r+YvVCMome^90|9NH`>r9a|W0ax6q< zwyPOE0sN|I?sDXGRq*SV=p3{X+7f*diyqxLF|64hD6WQg#<4!sQ2|eCg9$jsfjJUB zIUGrvDz)|AvJEG^r&=cZ>nHtCDJ|f>?}Gn&TY(}ZwPJbaN-k+d7TcUUZ+{6Nfa>^~*!XlZ+f}!;D{eg^@GEw4)Q6b64d<`m^AR5lH|@uIX?-pCUd%9A+z z2Oht`RWi=RLl;ZV!lib*xuIOD09*`6*hTe;>n||R-CHQIwOc(av!{P)%}YU2sWAGz z@KeBj?QtU%g$$#x+NO(~tDsRW64TxlfnK__{fxY+^<8tFb=Ad0N>CMoX~+W;#!I|6 zQH3;6!Z%9nQYqvGPE5OTy48qjbs%);AknAF7Zf#J8vQ+GXD1q_x!FXzBOWTWZn|$e)3_O+LUyLBJZE{G<{m2$k60 zp5ncUxGIG_yBJW4Ixt!ew$dR5+7Rs*`9jk8Qp`Be$M1zagP*dCp=~izDnX9}V`&SQ z47!J-ZoI|z%qQ?)_X+70 zi&%hwACUpCnr2q1FatEL{c#^MVw7yG=8P~@!duRSRW7kE#@$J z_(tMQT@yQbDr2Sqcc!*PRXfZ39dVD-GuCNtR#CI^u)cm)B_!-s5Dns>VX18&_(bcv zY|sshPG0X>8o_#(+$H{%zV*VGFnDfuinM~Awoc&@mt!K!Bwgl(N^ZtC%0l_iU*r5! zw75|2xY@{c{QQluSQP+azfTV?A?cBKq;ckx&+!>MCg2xN>bAzFmWgw2mJTxAt#WMB z>MUvK+1CBKOFa|oQ@kh)O)3SqIDx+_-<5dyozBZk!Tzt`Vb5 zX{bzh7EQ2F^4uP(2uF9Ikf8pPL?OjbZp z!5FP-M`Izs0MxN;@h(_l@Vbr!3?(FDL7XDD%RW4yhAsN|S7I-;GZWDBptKyETb6;) z`jEkP^R9#WUj=J>3#^?kLAWsWdXHM^xe zcreUP0nJy!$>-y8Ax}Sm_A>4C!$h|^IIWE>A*#89N+#RXIfQprq%?Xh&04rob*FKV(phV#FO+1Tpf+9a{hPF{r) z9)*HIAq6(VfLNtqVo;#;ah?9OI^t$TZ+ORu7M}T_OX@jpLl{IawkQG(11&`hb>z@C? zWz<~$ZF0q3q77ZH6*Pn@(8c=_AYV`$f? z)U+fP2v(vVGejwRehg2Kc{1F{W;f7`oY0zY+|7SPcVb$RH8!d~Bx4z^T}3fkHVW zcAJupMOZwGLa5WE^Q}N|=BQf60nikJ;X1y==~~xNLgeIl3d%X>M*mr%$%0UaSfnYr zKM4OZ4YA;P&V2F~KsP!R!m|m5;6jZc39dGBU?5^06oRh-g*YEW^MDkMhd;&Lz_YqZ ziZ@*gTzWj2EQ_bXdozUf0+qq2XfK>?=|lhf55wqvkgwZvoIkHR^6Spm9Y8F)BKonE zZR8fwQ~E~GJyzK}8Ibcx?-=qI%8{1zT^ka`FPNhLa;{ec+(_(f9jot{?pRoeT8o1Bx zO8nh6Ap#;E;#U)s3s@+2jCF9O+gC_Dm8PUu#T7Kc=Brxw;}&{~Sm+}7{Il&`$Rw6l z+r<2*1ZRQ}fSeLMD=W^h@Ma{b4W`=Lgiz2m815C6J;->sB;ajsud{s#c~HXg9k-?t zIUi~o-A_PeKPU^PembUTm7u3c*S*eo?eYj->HFZw<)4I{`iqdQlQc3Yh<=gXoz-1; zR*$hC;$F?R=nVw?5ur$ckMB0L$?C;g3~(-H~`3adyX%&K;_KOr&D5_e{)llrSp*LLcF$Z5o+i6Iaxr(L=M00tRm<{{_=WltT zlYb)e^jXzSK+S3cfN&dSBO6!Y45lPogckPuMYJmvvn{5?f~`$14zevcJp-=dZ8S(o z>wTZykbdA3HHOX{=-dnMsd;-?NJQ96M06{Y#O;}`1BFOJo+Mt#@2ODsp48VKGM(lW zX&)Vk$-cK_la(wvjT_bT4|2JBmUv9<5zr%_Yf^hF+yzgJ_xe!@Us%Vj^xo1z3_#S@!DfuvyswRb$aL)`VL_YIi7Z=XcZ zyvqCtv49ye?-wEIa-q$$d7+I94N*yOJ*A7)ge}L-g>Zzt3E>bu51G2g;q=ZZmmCZ} z{bc_Z5{h+j?wtE2Xqjc|uV=UvN%aTYU%@{@zP|Bi`|JuFxv?{3cpFKW{lM7=ejX`W zF!Yp@QSR)9lN%f8!lk0O-_&Z?g)mdDH2?kZGzM=+--juee0HqH{p_gqL8W;IIb-5Q zGOxyhD<5?dnN3CMB6C)>zHiB6VXTVMIO{U&yB;Ec5zxUARiy-jHfhROJPE!l^~3-q zVUia8S|1zNd7~Wa@O^68F)jAB6Vg<4u62y6G6h5%c!;o8IXLzOgbRB|&uqu`p=(SIu}>rVG7??Z4trC3Qv*FM1>kd8cXZm7CLRD2#`;3O z+Ikd755zoI>9|^K9SjIrk8>n}DIS5w=BJ4J#3Byu!%D?SHe0*2Sfk%8Re#*^fDmq> zLaskYqZ*07B36nW^*g`Shz=k>wK9mcsvZA3L05bJcfz{Xg_sS6FRsU6fEur^wRo*9 z0l%(w8YD_zirfHn&nQhfNJoN;qVW<4a^a z4g&c^cua1(I=^qA6zQG&C^z_6ckRwdb|mzlm_E~5YkD8H^ZxU3Ic~m+l0N+>3LKQ9*ZXD^-`lIvRNQ-9NE z`c(8jVX|@z2xqO-e-(~ejPjcj?r60BO@7uYf`t1|sKwA%$;*hvc)APznHh~iZ}kSs z*!wYoGU|syK9)p9)&Mm+Cz*@hAgMv1!Lvy~!%ODPHuVKBb9sA(*|55K=yjeXj;)q_ zj`0Xj#468m1Gdkk*7BignXR5rnA(KX%X>-KuZn(bQ{-QrQB(c;jO^sRWQQ~pzXRD^ zC~w}z&3q;>`A?w1)}P-s$Gmu|RZ`WxS3)AaEg|DIpefW}Imc{^;`#qBTaHwi z_lAh-Tg+tbQSz8s2>MkYRaLJ0SV#)=F$-ngV{p&c6B@yePS5ms*w)R)e1RD7*|-8n3weEgv{JZZ zj*6TYNl)fKO0*+Rm*Rw`Sfr+4M;+Ej)|pTBNg1GF1)Q2V1({NK2+NCu5SFOvr?*F# zv#|Ieb|EXM9E2e&a_8#9rm6%86?(vrA$NJ=&=uk`lKnsyMk%C^_!8pot@suPoWo8q z>V#5ow^pGAUi|Y3!OFJTWNhq{Ypqa>A+x!ap08AU@j&VRn$YcsO$$UCH9Wv#??@L5^yg~9kRhtaRgXZS0J zro4w)NXK-L1vsV)3hw_au1^HOKw-MiFtO)pTMtaf@XKbN10&!t3mf|iwyQBhNSKjt1`({iA4{ykcJn z%oJ&TD;G38LVn0${#&U30zdo;{B1{nfgc(0zs2w6+TwDCrz*r*sm~Zc=!ls zIWT+eH8cD*lG+z95_XuPrpsSArfmPZqiB+WRp_z0fa(;j<*!QcGwhy_6sHET=WL+s z-5USF{3FpV>!>!J2kF!q@T8*0F>dHRi)IoWZCnH6+ZgZEIp*iV z>sKvCXx%v|*TpUVIN%y zBL`J=;pr`oDTDU>;EY^j;1(m-m;|;p*D>1JX+SP^viJ|;zn5wsdm&d+M0}f<**`OX z^Ao==px`C(7*CM|0Gwb1KqeJ9lKcM0|HW-{~xGHwKxV?drdKFTbQh*NQRq2EFg zVmG|7WT4o^FZ~!VQI}gl{vUP?@T!rsVZ_e1!1oKh7d7O^G_7 zPOsJ+O(yEjBMg$ranDkme}JwuGt1QEk9FKTE1NvywThe-QT`6>4Ht*mKM&(i4uH27 zlD{(2?hQBc9rG*U7|O9OPwJi}9Avd&KO9uM;X<o zdCENp8;+C=2#k0j0H%Zoy6CwcP^z%KKM$xm_Z&jFH)_z>n|5)!1^HKq5>AaV_X>oj z0Y``Ub=wxQkZp?`wtILzG-aN3|M_${ndSJWyh(ZM;JQ2X0cV$hgFVbr9l4e|db+Y^n3R0sCoaZ|N))m<0ZLDIH(PrAef`%kv(G99O0p}RHmFLbAS{2AS?g?PuQd`bN4D3*GYKGsJ( z?7%ww|1ZwZliu)_V407{ByRy@u6S*BEJ=`Nrwz#Cx7j%+`LmP`rF)}VetxsZMz^5R ze>gJZ-U#eQA*3fH*X0gUyF#U9$93NpH2zQ3PVY(g4&&!+YAxM5**?f>@@BRyxE>q) zaY)`Gzx>KH;RNCQx&Lb#r(yi-W5=oVyKMHd_+{%?=x*st|9?hz%TW40aJIL5Y|_Bl z^49~rq}7nxzM^rh?R#v1cYq-?K@N#c@o@gdKO6fXHA}$Q_oVLwXN#$=8rHZnv2xuB8RH}r%pA4Y^~xaBT!2|mz1g(f|yEv1_?Xn z`Un}kDLHHG<;9*!z1Tc~$2AgfyfPgLf@c~5Q5Ww0J8Jmd{}Htm8MXCu$uTl&s~*L| zL;Oy|Y>LQw9LN8t9a>|@Z^?xk;o*MA{R6xgd2e(YR#V`|zOZ03VdjnX)#RL=4Uvh$ zU$KalM+2F~0;6{$Gv84Y@AL)YczG@tae8PN8j2VgdunNt(i{!0q$t1I*{OO0?zEZw zfr$h=-8W{L50=0qe2>+e7A$B{w)I5J$1*Kg(I2%)K%}}43yNs+2@jZ|=N%S_26zD} zzdID2VzJ^w5Bh^>%_b<|2aW~^GFoG%MaJzj&pN%KdX+W0&phwM_BG*n^L8yI+dQ^U zC1vmc|F#lp%;N8L#=4XeL>uj*-C^{PJGWhnL?%HK{ugrm@1JWHQjYTRxS7szhyuz? z$uxl$aOZQ9E>_@Z-T7w=GF_^FpB7^OKTgZ{`oIP1pxa98w@I*mkRlr9DH zVGISJVe{6A*Jl8W(hm#5K6_jtdq`k10Zsi+Hrk(`Jzqp2>@&WP3IAjs->{8{76@%%YXX9ay9|#Y|a0hB*;+zgCv1a?TzlRcT}HWW@dHPS(KhpXH#d&)yj@%?I_IY8|l%V?P>lQ#Q$QG#oM3O*3oN_9(`unPnT`*CmXfuIL^3mRbB{_AM zx#g9V{jPm@)qm8!|IfA8EO3nutD^dRGYeH$EXJXCA*W{(CLt1gtK(`=|2`kugU;VT z?lj`Ztta>skXv3x&f%jz5H4I4LvM2@#8SWols7A`#a za|>C2+{doZVoGYFLh-vxrX@OU5p;>S_jcmwwTzHknR9^Ful#B=Y}PAhI7t zx6K-|fM?(Nlh{U4a4v#i7%F{29nl)8)4AVY%g%O4~aGq zn&c>kWWJ|GN;XRN)Q2W(c($J_;~K~2T>civCXjSvQ3dWZ($;T8u+v}ZutG3t2op6a zc9F24wNg?Xj>yAbJnP{nw%{y?rYa!lR+`5zEPscO{e=uK#y-+@NAozO`{iyz^2_9G z-ERxSYJ$W zzxW_yXSP1j`geHXNsp${9eW!z?V!**=WXZlf(4*ZMN?HE}A zLR)JL;x!VS0!@g$QW$NEdM)^C)o zQI@f-KY&McsX#0A^5U|3_khQyFUdlPQ^``tfaO)P*PBA*b2}@@dtRTNGf3e)0?jr` zj6J9{xw{M86&X*iY=ib-Z6f`w!s`n({L9Oc+Fv9-EaUAjk)M@d2R`=^}Mh9<*31MtlrjMzx&Tnal36|&78N%JUVdV7euJ_ z3-r2v4qE5tTf{W~bs`Dc@CfUputfSuSv$_R$!fH>O^IafuwmAW5n)2q54Vfy*g9j$ zXkLt5`{yhSStV%c;IU+?(!6u+N?P;Tz$7i*??XfK zog_4+0dc6xT%~~#i^l5^_7jfj2;mutQrVyRx=ZN?ML1qNWk*Oppc5S-Ksp1lmV2HK z7lK&-Q$f70eb=dbKEHdaxc`22I1YR|VDrPPTMgSkKJ14pHv6{ezhOe@bJk@fy-)Gf zcBC2GX1{8lT9DFC9~XTgCi;Yc2mx=EhPAjG)lWvI*hf%pc}Gxr7b(IG$cCthhgeEJ z!auc0GzD##RvSn+t9ZrNGT?bh6g{tj=O4++iOpxSi+B;zX~U?gWk(~LqQ;tNisUVG z6sp>%fzEq1Ee(ngX1cm$EpQcS#IMDRm@I*JQeaBpv!BV!1kcrK^{(a%_oq9biLU5d zjPP_f!gks*qS8#cAEl_Z*fBb~*ac10=7>-&+{?2;%7~~+L$N{f4#kwF2_GzhhxQVo ze2q9C1%oWPX@qUFbJF3nbzEQ{HXYEA=8bp0W(06nj1!ZhMw@I(WNC+m*)ih92rbt$ zQ?zNrz6N~IcbFArz$Hj0v0Va44|>tWm@iM7e=2fh25f&>%^Ilt8gb8tA5Mpqi`PCE zLPUDqiIbdovPTp`azdi|nr3{?u$zGqop#s|7e@R_(|pb}V)VkxGM6pIcTM1W7oR|1 z3xR|(oEcdMOhmvUFfIvVCV_KtLsNKN_?`(U@KV8m4GHf_u{uG{+SPO{s7xu4yDdGG z)RR$D)Sjg!-Ile-6qWo*PyT0?e_(u21KXW*A#zf{S$FC8GEK(_L+CH}OUt+Ki12z^}^W!-fQl~XdGv&sU7$sBdUbpm>xNNom)i_(I+0e zBut}!{>~-)OlP2))FL8vv_a1j6K$r-C(zDJQQOHYLX=CV!!F9)UVhz3DJs73KVbE4 zD>Ce+R)ng3it4qy_=BboXfk+EA3v6T?(w7)k7uJp4HiMIh8Q+_i96Imi(i!wk@AGc z{p~{57sB;j3<#`Z1J9EJ-4ctbL#Wbqxwj26iVU0&Zkk2d0F&46;!m1Bzyg!Y^))V( zlc3Lhr_6m4+CGR?Y)1Zol*oV#Q9vBiWP2$3dRruQsP{$Xy(Xl{xh0)n;YH5c+d+J- zsadR9)ZmneYFm{S>Qr~&ny`OoOh@SZR|?H$M+AN8-crwZ5aTUan&C=|8B|)BK;>cr ziDzbljU|`7O3&0Z%hV1~x;=FM`nkLIeNy*{W9{A%oFKXSChhH2{phRue9zU;H}5)z zEcU*!_-UwSN|E7xS{b+IQ-^D&$;?MIvgniQA}eLM;?YDhT#!*U`1_)Nr7V@JI<02^ zId1uG?x89QjehaiZ^HPt=f7Psovytp|G_jWzX0ktI@Cml66tf@ROyH4B39UFBGG=9 z0bt<*`2`DqF7b1l<7@t9$US6lzxS9>JA${{<7(VN0~gxk5Rc!TOV!#tO-`03BjdtFvbtPORSXB zmWh4A2wx#n-Bcjr3b(uH_V#>FBz^uLU?ZrlgC6%aS!<*CLJp_-Ae&m%r9pR|jt>t8h~ zo-T6UP9P!8mbT86c;4=gx~p+usu1;+S+))g#eB{z1Go6GorfTRRpgX}n~{7PJlEd^ z8Bfj=AyWE7=2AYpWpgB39OKiVQRI{ST}Bh>CcF$*JTc6i|VVr9MZ9??gM@J7fkQx++k>$dRZe#;^~Wqc}%mQ608C+yej5heW99zVFjh3*ndA%P0B-%lXpY;*EoZ~DpD6`Jzan^XCY5v`#qv7YUF&r6cWo#L7NU)5|%w{mp$MqlV5 zha{68^4YIvzqXaDmJ78APB&Dbd@teKQ<=q@$%QJ%8Itv?<2XG1deiA?jw*(^TE}r%2Z!kY>9p_QM?~BXi+DFmhumMl#jZco zlid8)?|!-BQO+dy?>Hm?~a`=}n3Z^pW8eZcWx_w9YB&}dQ+~I{_?DbNF%O;>H z51xYJCdWB+8-k2vx;p6!fbBZ`61d%44;0C&MpS${R4S$p892ycBp+ble5zBA zzB-;xpz}hX9CNI;j5p7C(pb(J< zhIz~bL;uB7`S&`-<$^9vrz680bVg9CCcD*K-I0OUONSj^u)5!Tu2=K8^(fF7bRyUc zaZF8TekwbGzJ5V9+*3K|NKj`7(S>Z4D*=t#fX-tpmHx~FYl3>$p7NSV&ZBg1VRs%k zEAd1}2IiIy^S@-(Px4=XzuhUqYp$z5hEh8$ZqC7H&P6mP0TyiC7Ue@n$E+>s+lFY~ zISaUt>YvqOj(^4Pr`^vst-*qn$D{^hnxeKxT9Uk2wM@B^J(i+c&5at4J}6a7K6gNM z#$k8z5wYZ<_lZz!OtkN|T5zS!xAqM@CG7)=-FISdz#hFJ z_P7DDN2R>UhQPk9+4`rV279jgjO)2N^=xFxFHgi4HOTqx8Zcavm}s!nx}V4dFu@y# z2~a*LnHOr<&gX<;3<*HttVS<9*a_P9S2D{--uZ`-SY`CnEbi)Rm?aAwWkQ`Z-PPcU zj>ao09gcs=+8UsZA26i`)yz=33!G0<^A$Zg0p=K! z5&GIV?i6WO$Sa_lWz1j5OFKDDO~&JA8D` zRkNwmQI=lDn*>|$Wkqk5IEuoe%s9xQh1%YN@S!u66@q4B5yLxecwM@<3T<|Dq~jzTNS z>^rYO`%%+uwHi$;H9Klnux#@)paTdXSB7J&<7aeUe(kAyP@O%v@|gICc)d4`veqCI zXKaLFTLvRGKa+MO&XMG#IsPQF$+~+#gfl*hiaH< z0H1ju`b;F-6TuYu-Hz*Rnl^3IB++-fcZ4>QBEsFq?8P}{y}kzG@mqmaN}}vHTOeGh zI*IiIWC|&=r~FNU=vZYWxsbRYac1p%p$LmF3hl?0bN|slP^F*c7N$VE7x6;%Schr` zJstkz$$b4ZnQ*sT4|0T7!ZDzdX+?}Yz>z*x5vN4@%d-sOpy>u$fmTc8=jI#x02}6t zQK3^Kf1H64alZ7Ha4V?0S2@q6#p+(EtR*c3{+e>Z>o!)1-nthBw3{tn)fv)Xz18D` z-fD^nRYlF4`TfU-X~K0ObEAB{wA6nz2+$sXqh33^ymA>uGv%V{P^F2HPQ#mW8OrX3 zp*?N&6=#p6B;VYJrN!2^{#vmEpsD8>;MIWW@sNhVx9p#S)QyRxcld+pK^hR#CXWWH zdNN|bF0s1Vjt&hRRy~PlUgp;nS%F|=0i7i&=*+XRE?`Ncam??RitXOmQ3R5l$A!ST*1OSoKA@0vuGjc zaE%&Woe5~v%(eMTp@x;L6M+F%pZ;H~15&lA8a9$_*g=GC5u=>9m@NV4zLYvhHu z*y!33jj0Z5=qhaj$!WX+mtnkKMuJrB*;JXMpcUo?BnPSw)+nZI;I3pnJdj>uoCK-u z<;-*ffe-#*bk+o}Wn`DzWZJEj^ChOaPgGGZ^+?C|xrk+WJM?IiKUP zh)+ujSE-pStowKVAlHL3N<_6?{18{HfS}UYpjV-#bgf;;MkJ9V`3=*&8axf^_lo*) zP~~EdUcBGfu%<20tl*}mTBWTFoIdC>jOb-VrD|K{z(FI;g385l4J?*?kUMmVAq?X{ zfZdi8Fhi~hD8Jnzj>KXdq5AR*tHiQBcQutSN6p4Z6xyYMRAS3cy~0{Ni88EOB7bU1 z3_MRxGm7(kZD*)cf-=RZYP8F{wx|_Poe0`bx_&@oB7ch_9zTGS9RPs_aC7#jk%u zz>=^Agx}e){P@&AVt#U%7y66f39Q+K)^G2(ahbwF(r#r$(s zzJydLtbPfO(PY&?Y76X~@oga{*gD!?9sm+-TR-Pl53Q7!QUYI@dF_iZkQ6zAomfdw z2!Bo74$Qh-kC-Tp%9T9NfYX#N|H8dTn(#*jX<&57%zcoi9C5Q=lcE`U{9FZ8VYU1Z zTK#o`6HjzyA_d-mu;Uv_yF}YKN&F3%bh{q^+GTlPK@E6bMqJegXOfGHsS)I6r{0n_ zx1g2{x?w;8JO#2qun3Sst@+rYdcZ|^;>iu}_9S{1RM+G-H{a8jq#Au9vGXg4u1Wfg z{Q+j+CkpI5&QG14Y90y7D`b2ZY}i=Zex$kw^~Bu={pac=v4HZ>KeYs-Ua5(HDL6V> zHKB@5vri;)euW^RYTZzYzPfT3|qVz5ubo+rLxCKfn`YZ!Khc=V;GR@B%e*XMu<{Q&!E0q*-47*6QO*& zf@t5K+1s79g~(7Rg7ZL{iZMd~1LeZ>5rDE$lG>2BUc{PFfFIGS0Gl`+3D}Ip&j@~k zY7z(tM12*SY5G|88_J0g+904LFInCT*0uZ}{LgF1t;7)+$tehjrA&)V` z118@>%j^bocS)TD!O5Rvte)NNJhNWu`eHpvuo2*bxL#0 z_ptA05j1lv2{ltIgn?DMgtERw{S4a9iXK`(_RM^(B0Buz)Fp74cw?gc>sAj?@CTKZ z7!U4nb*P+Tka#e~tA>RWF zyd$|;MzUg{Po42f4GY<_`H22y>+-kwV)SYB;MF)Vy;jPbBNF$+Dl^si5IQPSjuEtp%6*D zOvqTOYu)P8{wB%D@fN2p%%>^SElnkMoj^?b*^fK@pH;zlS9s$mU%TyFO?y({Gw$ma z2Awe`k3^XdC%u@TJ}CJ6w8PI-1=z))O!CfRh1k-MG^0c8FN0ej0~qS()Fa+V$IS_+ zyKRTc7YrGFKY!Fcr`MY`qNnD^&CR(}8RX@t&`$3Qe?Z2fQzRaPCf2ne$3PEpE?Eq<`+2PxP#r=W&PIPv~Mc(p!Z8I>5Pj&CZcOIPcp-7SK#yA`>hi*FAu;4MoRiCiQ9GAHs~zQFUWO3N`y z$C)9#+WTKkFQr|9f#%RRRcS^-3pZlW_hhdv(_o8oc}@aZJ+Q!nK%`Ixt<96H6Pvs} zzuQG2@K*81;P2bjo;;4dIQO6YpV}w}Pb&tu7*n3OC`IHlicryR*DBQKm9%xsBu@;Q z(JFkdg?qfM1ui5->Gm$K>>s<}kVb~Kx&E`YYPR`2Xp}b=+wu`)Fd)YLqH$q_*Icv6 z=?qS>JFV8Ooqr{iMNW;+A)Wul3N%M4`bn3ZamITP+oI4|!%K?Uui$Un*tVm_Y2aKi z)!0Z1+3nYR=d!-@{7mAQ{K753otRiD==oNQQ8I#()Zyl>UQf+$5Tplc5VVEik+Ka1 zR^a1X*}j_M;07amfrucST{tPQOQ;;$hQiN5TcK?T33QGUsnV8w>%7f}&zX{*WRyZ@ zUzx4sf<`|>B5BDgl(<{MxCvZHNh>&G@!0V2jqhHe{B**+nQa332?o70Q^Ch3;&DB9 zK=Yr(;A8!1@%gATRl(&oAC17%AV;0wcUgjSipp!;sHZ-S%<# z#|gBKmP>eLGQx{%Y%Ac)I zXz4e-gEE+&|M)^A@%-!MldqpHPDr-NpZ4O%SZbhtx>C>n;JxG)P~#3=GbnNWgwTqg zS$x&71CCuL0avrkrJV}|;DQX?kKJD;F%Gf8{GlVS^^Or?y4V zX;y#_8%$>vxpn*eY)b?)^&v2!D5x}P*-Tl+GUSd^w{4l`bS@C3%hd*Se)BgrjsW8H zZ+5p%;I?63&f#Zp>bR_aon!uTxFBg5ckb8CwN(MrlZLyxa?nM+B|4rJ4uigz01wkT z*$O@c@v8p@h7Ni{W`!{=T<3TAztdjd_j!k-MsuikdhPE!G@H7V@b4_1-P}J(APr`* zBX*Vdd2!>R3qXu(-VR-gWEQTJrvZ;uX`ZD7(>-$gPRw_?86ih=78HX+MQT$uCOr@Q zr>Lw5XHI4(dFE6kb3#1$PC~Gy=y7WBjRaBTYjWaX2vLcH!L@X2_S*=oCBj{mzp^#3 zOOB1A=?xQ+3PCb|{crKmGSFR^G)z+Y>dDvCVayq%m>fzBCz%@fP(U8MSOvd~nK?@K z)a>UxnUBq{sNT0Cp*6S0JGz}Pj!<|*C#uRlbqhtRJEJEG1fK69$Ai@9GY)4u+M`3f z5%srUaSlLlM0e$3Y?y%0m;hZAxf}KDPUA7E_nD-YU<9~q^%x)Y@r?b<4=3|V5vwp$9T%9sX}j#C)C|i@yOrzu`}v*;q#du<$wE6#^f=6B$X5q4AzbCbG0l zH!VKg6R2-rJFXegh+b1;RXLCJE8;&}B?bts^NzC35=1GoKSaL2C*@ylGBEk8u%tV9 z|NRxzYMt_n;V3*^p`o>Eg13&b+YdeiQ>$_`x;Pq<*Oz0kH;*5EWRX#^e!diGoD(14ndu-lW35{)C(3;;B;5hJ2$F^-cfO#khRK;}ac|;3 zcu^FQisqvzRRr;1=O{+4nwLOd`s9Neg(g(oQx)F9?13BX*LBU{V6K|Tn(xAo$g_Wa zA18N&Sdf6eP(vjM9!S8T^6JiJ*Xpu#PaA<1USeD`;5`egA8*eD@=p_WR}nLq;hlBa!pWngxAGd+R; z=uXRe{qI$Sa&ZBN{1tHUzrIBQ0he7#;UhvxEC|+S*niAEa|T=uWYXy)aU+*?>HK=t zAeRWhufLck>+sP*2mNJsGj`VV1l$nDua^==aEFxGDLm)VV%>Ni`u_jVdc@H|XR_k6 z<Fl3A^Lk0(46pbrmtf>7Nw1EK2qD^xewAO5}M$e&-9XNs4#WsUUo z{Qe}=@P^0}6^VNbCx0)oJ(7{!_x8c_C}1e>?~ZF;yoPu{fld~u3=zO5HK?&2 zo>-q*-g+mXhB%m&72zYrtq8B}*NV{0e_s(*TN1GFAV`$Q67jOhiF%U-PH2^$NvFho z=s2VnS?GBT0KpLXtN+Dw;pbrNXHq(Hk^)r(cBvZa?AfD+JyGGf_ZnbpD$v$+ z=JcTV8m%V%WbA(%X#e7?v#3`bs8Xa#^?qHK2iCbMMi zbY;DNKTaBLI=U%;TKLx9wY?f%_*!wpQ}cS2 zE5i(X;Wg<8@QLIzHrAKr0&BV)skNQUZU9)OLf$2Q9|%pWEd;um_TaxEcQvQtw{Ry! zr4hOhAxZq2&rs$2HECDL2`r2A_1h{v8{s91w@o69bRBz$$s#;aZg5A5DxyJ0m;_Ni z%uaRZjTgA0qne8|pJd0${OoJTqUDf&xvOWXvWb7ln^~q28ryi{>9Ee@ZKtQi6AEP6 z4ZG;PtncaaH2v{&?Dh37Z|<(XhC9-i0>khKES#sr9yT`eFWd0q$5-rIq?T=qZfJEE zUz{2>+qZihQjFYh+4q*n9*fINQMnO!bDbSC_bkxKx}0RB^WCyrEXVU<7hhsU5?PfI z8w!0wmfSpcJ&M8Po^_dKwb&VUy`H$2`1xu1Itzh{;yw7y8p(v0gRfATV0-cF9I%8x z8n{XJ;7U~4V)m8C&wjaJJle4k$ZGl?{YG-yytN;{PfChqe1x6zs3=Lo{$gQT8B4Ex zUH0JEH%E<+9kZXe?lpZs$&*N^A)|TeWHnPF;g3#Hl0DR$MS@4I(j=vwdsK!ZJ}y0< z(ZSx}FSdPoF?&Vv=hm$s!*PlnupkQ*DS_momb6_YFQ<~_^aL`V>&as>Oq*j(BZh-0 zl0*r1Q3_FW0!~1D(d*vM<)#oS);gu^IerEr@XuvX5Ztn1CJMlclD5O9kzi<)$Z1fJ zbkAEK@tp^QM0@txSl@M^r6sC~ZB#*?W>nIX?X5Y#792=tsjVI;6_0lkPT)n+cmK1x zN+5CUj4o_P>*#J5-Ur?y=kW6mtKkm7_#6fAaneo}E#;A9q+Yc2R@Lh~?=HUJg!Ow9 z$r6+I?%zBnG%>l`+F9Oz+LvCpYL#J+ea`SgqP`<#{?Kg?wETVN(zL--t-KEN3xNSP z$-RI0VJ$@VGZntKaynv?9hlCtp3U>tq)Iw<^meRVqQ7a>cxAEl<1TzTk5?(|{ONYn zLf_?DH0yY|-m_}FJn}y9gn03fJ&S2-*c+_=3%Tq#s!F-QYl$OW>}#jP8r?W zYDP`3b8r@_Rxcyk4Atp*iLZ}dx|G&Wti*Mkgq9ng;+jx@ow;P3omAaU4neoG-sVFD;JKB#>WU_I;D*mwx5=8+E0( zdR8y9+YDh0yw9&czLz5%jv|Vk;Sbc~9M(R^mp4aID9TTJiMEqcS|5??cD~VJy<_8O zZ**>Uh@j13e7I{PrzW`LxnzPhF_())@ry;T7K_>%S_$UC6ERPB`ekI-Cy@^GF#LRI z)hAep?DayGu(O!#K_zsdGH2yDQG&nke%P{nDwCfysUVSDU`KjalkjtAQzN!CLXY`A za~gVsP%^#HNP|#;Jh%aiQ8N9^=nZZA!nSN|8ezPkO}*wv8>)0A2AEnKJJK)*#6$c_ zT4xYgOd(<(kM&AqCsp!H7Bl+3Vr(bccEvj))?9nPAvgPb6#m;VDHXeOpPSct4TplxYwWcwIgE;cE zQU}8W@v^lK@0-R+PI89Hj7btj8@r z7;A+{!iG8pUqP7YyJ65GI~$o}zLLk`7~1OiUb)0r(_Bpr1YWgX+}_Um^y%#}S6?zk zGTDpC;x(do9))Tm&AmoP8yhW{ABF1BYS`&<;}3x_O`M~s0b$CEZ$%zjD4~gDI@Tn^ zZ$};s2VVLO%CI1IQ$IJo9!B04M&3Tg8kUO_cOQj5L^gbMVqbBi4puj(s!m|3mTLhh zP;{7AksdhbbBdoQ@)_KWLMGocqapeypB!irM7gnK1oD-QDE`+|AfI@Zx(C~&Rmi8s zt1B1+&tJ0G=}14%Ea{PmMg^nD;z9VQ5ZPyg@E0W`y#UUZY(7_wHYqPuDH2IGkS;Nq z%##|zjgKbZ=tPm7d2)UF?03zU$u?#ZiDhv-PYn>)CMG1?8a4n3)L|e8- zGEvAeder(P`uJ3UH0c6y79|F39$kZ2=|0zR*XXgB1=|vK)joI#DhyeK>Bn3oYATLd zlV1$FIl4b!6x)vJK-J~VH|IP(Cid@!W!p_fXnM>D-(PXE%I6UWRvKtjf-IbBw@2oA( zRaGqaQ7BB^HZl%ZCcfvIf+EHZ1kEq;O3SkJWdf6qKRHpM%EuKyljFUZMqz%)8 zN-?M%7zxs6*&5%Lx$0VZ$y>TYeFonaXQzu&oFUQ=JWQ#WC!P zI&+O}h*gPf`Gbcm{fJ<7R|L8(9+bsIC3lz=Gd)>wAR{FESv!P}{TD*3>e&K}dYfus4!?&k+8Y8yy zlKGjASlGQJ*!mlQ_piiOkk*}0E^&hzrHP=ARA4Wpl-5M5E<6Q;h4-fXWJwozIH)kR z0rVBWu~aoEbMnWJ!kY)x=>_LAkzk6a;++)+@m2=0<%-L@j8YzXc*>BEGGZ_iNZ=r5 zVj^)6{5@QaOXr360P?9Rk=Yy|9~7rq(UAlj;Wvs{>4@JaS&{HRyKNA5NuA}oZIV?1 z|FaP@^G6kK`dl(_q%E}CE`HfyaS%rOXnqyW%Jaldo+Cy8T|^O`j%p)&HI}zLuyK3 zjYKba@*skhs6Q7+k=JsZRy}CKl8xBdMiJSm7EMk11K0({iT;n`q#m^M%zy)N2IqQu z?xVyk4ik5R!gP<%=4GFKwS3iJHdo!19zB3_y}molftfxMt@5~48Xi(8ZW?G=yX9b+ zM9h=TndZrRvMTAgosqu_V`s{0;y@J6B%}L4MK}6c8HMBu(I=y=dxDd@VKfd)HbF$B zYK%DML*9o+35pXVVn8p_g|YCJzNwxLD~tyt3}w6hUBF5z{Z309I{BI+ z?HO;7AMV?Is-oNBSy_zq3jH^ujdwy5>mcIA)Q{v-;+T|HV*v=+#r=McTU$T(`kVK5!JiiAP~6wFSE{YMv48J|_rBw& zcQHXVjS*R}blDa0w-rKMR40i8M*WAA!?|FwGm?SrIBGRKNAVWatOxj(kw#t5fHDpbqu3lB!lbjo|%F++_XYraK-(0>YTR1%~Ny_T& zIPk21R|0SVK}rnPl$A&=H;&9HSeum;EBDRp_|C=+2@q`a*2;QqafG;Vi{!;u)TF)A zQ$QsDb@?7g+}DS%HfddHzpilKAi~sx^53Y^78BE~n<~BI7KmrVi5W25XzM;33(&u9`X?{h9NjA_s1e;KBfR2E zqev+$J#If?W)arAL1127Y)HA;Hn)8G+s0ai!UI51AGESenCZOhAO{zMojh zN9(@%L5U{|lSiN9cnVxrLdK?NMz5oX5@S|uaA##W5U@XbinV;){?(L3#R1*bxMr0t zJ)s(eNa2r9TlYS;$x7VXFxQ4Goa4jCwQH)073742U1rQnMTv5qyCG24r@;sp7s6uF z*k0DwDGi+Unzry+7vpr8e}I3gG!6o8M|YSE6Fu2*pu4BC%`o~XKi^O`LTWcckjU|% z?-}G4rNomfC}4EdQy8dW`Z63?<*CxqM`^nin-}^(#PBb$m|h z&`ywe?^NlN62lJ(ATAJuCZ=~=p#78%vu)m7>7NzaA*=%IVh}n>3!=_igTi`Vgs4s844tsUOgnR2k zp*Lab@_O6t2F9^%yi~R&so)dj1EpwBlJ!&Qj4R{c&EKue#6R*>VL2(ok15)(PLm51 zcbNB~JPIy5aOm0E_dlX%y0o49lnJaLHi~BF{aFjo5;=!??<*lB^~U7Y zF8xTDnH#6RqimyK__CLVI);3KXX_q@_Q&Znzo||#N5xXgD&adEeQ>3ZN!MbN5WgbK zHLQ+LYE<+o@r@y1%=9gEuRs`{U?H}TcLbdXoHT`3RT?h&)iy7CPxVQ*DuNhScyP2{ zZ0qhf$gOeL*zoU4dkA0Ysi=NLah8pk-Ox*WYp47xgv9u#hGKEd$sKl=kv`H@s)Vve zAEXPM`-H8V6v=w!#a<;Wl42lVVlwr8%=^;f#8|@p?-$uf-s3P6A^Xn>5YQCYmsN3e zfn_4mvN)qb?y&UhCfqmVWp9X#fqHg*4ST9=9bZyrz!~Pnm~9%gK(*issSFOE zEHg<8t5#WOozpE?B?26U-`!L1JEI3uO4jayZ2fE{ubD1q@e8f$B<<#hOw>hU0bqxpUH5Ta26TdrI6;F} zA8^94BEZ_s;1Fx?HMbexx@A5!N<~Z6^|0^8wN_GRr zR$h|$DrQn@eA8#sT~f5RBkl~5@#$Zr`vs3j-uad#D+njlUzi!!kE9Gn*Oo5c`}|bC z=8})Rh_KT<>m|dZ z(bf&VTlB*~VIF(iyX2aqcplRic6zp>2qdAo6ae$T_x2=Z&rw60fDcP^JV z@2i(xq`M{r@}^+sV;se+k4$87FIE+6myU=g&VrxFw$_0d4!urGK_vYXY`iKVj*7jk8jHTr@+f zUQtxM-U20hBK5E%k{7|U$=i+M5Lf+0>4o}h;3Q2RgOgO}D7Fb8XOZ%VEt?Rxy_TO= zt;4cUWPD7J*8Em*RSk5KiGG_qyCKhNZb#4GzU?UUA9?vO`TE&=3ZBk1tDAdYEnZFW zc}4|{bb6IwB9ks+q2wH+b9hpyv1E*XstId9pE0^~^}G?w4I_b1#U(xU~Li1%f zT#8_ori}bhM3Fq>Q#rG?yORG#XoUIEl*K z!l=LQ%@QPYi%3%hCzkOwRZ;v~ss*>7Jw2PtEX%_}8MhdZ!SZHq-?)WPx0nE$pPSTa z*LW{hr9VpFsMYwiLDX<`42qhK_QuR010xpMjc;FHEsqvBTlB}pfL-6?k6k+frCvh- zOqPM3tncn!HkU?MwT1BrpTY9}w*herJTgAPv_Zpzzp1$Ew~r}L@9ARVz8=Ws><^2@ ztBVCyN(6~F4E-ghsM~2SfV=n*4AeK}gV^aqmtatXHxz(qOy8cn+*)kWoP0A5rf#aY zMz12fsKwa$k0|kC{B-JFoPr`eH)nh&=%Nuzt&xCD;g7vjmlX;8wv#LO?_XuCuzp#s zW82CyXROEFAdH;5i^7y~*~YE7jKOjq`Supn&+e(G;89VPi9W}Dqt zPEa%`Pg zA=4E%3fOwWll|ZqZi8^V{*ZVpQdcwV9*^= z{Nja3el%bihVR8-(9-S6>AlbROw{@C0*6 zih31zc%bZEB@*1_A}U{Dumr*`@+j3Cgd?zN{tA?*4A9G8 zQ<5&&-XO%Fl$f(M?JK3aS-MT*qUW#T#kbTR94mK4Uwq5d=?BW?d~Y^#K3cG>fMXqB zCi6LhBQ;y6!_2p$Lv9eZ!!DLz#BS7^Rqrc^CNnjz?(z^6K-KZ^ny6{DJ7sk%=#OrO zxwJc?6?8NDqSxOoJFXunahHJ-_s{?+abiF!p4}u91NQ`Q$w>Rz&3*4T(*a}>oxl1I zd69${N(vi;(*Y(pi9?Ro#9qVlQW()#RCn=iPL27`9Y<%WQ&#{*eJF8DOHq|erCf!3 z?Vh5+doK(TTwmOLJpAG0lYI%Qi{w#6=BE?;Y@F{(+^ssw)YVAU=M=pR2{0#4|M*C; zUD6%jd?Dz3?|o24sR6-UUMrQx;pRTY{BeF-pA6(JWt+ybIHxLe#dn1@*&1_Rk{`5c z?BuNFmak%Gmgn-H9G>Qb^1kGz`j`(T45p)!8MVvB209YT zBL{kpXpuL`f75d$&)W}}U4r$F!p)r0#xhOVfh_cZU+}#YRAm94JAC@z@I2=+Z}3>E zxWhyJCB1Q~{r98JN?iTUl&4A#l7<{44TzcX3Xfoma;wlz_7AyC`#VePTMp*eDcR`1 zlv_>yY8hUriI*#C`_FAaTQw_}Nk8K(F8EL&Z-_MrW@I!yRh|{BUN-*5)DefqJ1mA% zpePK?l~$WT+`M(y$?U)Mf(91a8Qu?LcASWO4WuqzN-;oTfT6iVLsKjx=k>V#EO