From e74b9d5678f84dc9e9892af0cc27bd0729f5603d Mon Sep 17 00:00:00 2001 From: SR Date: Sat, 16 May 2026 20:20:36 -0600 Subject: [PATCH 1/5] Add enterprise compute quota governance --- enterprise-compute-quota-governance/README.md | 44 ++ enterprise-compute-quota-governance/demo.js | 29 ++ .../docs/demo.mp4 | Bin 0 -> 58148 bytes .../docs/demo.svg | 38 ++ enterprise-compute-quota-governance/index.js | 432 ++++++++++++++++++ .../package.json | 15 + .../requirements-map.md | 19 + .../sample-data.json | 108 +++++ enterprise-compute-quota-governance/test.js | 71 +++ 9 files changed, 756 insertions(+) create mode 100644 enterprise-compute-quota-governance/README.md create mode 100644 enterprise-compute-quota-governance/demo.js create mode 100644 enterprise-compute-quota-governance/docs/demo.mp4 create mode 100644 enterprise-compute-quota-governance/docs/demo.svg create mode 100644 enterprise-compute-quota-governance/index.js create mode 100644 enterprise-compute-quota-governance/package.json create mode 100644 enterprise-compute-quota-governance/requirements-map.md create mode 100644 enterprise-compute-quota-governance/sample-data.json create mode 100644 enterprise-compute-quota-governance/test.js diff --git a/enterprise-compute-quota-governance/README.md b/enterprise-compute-quota-governance/README.md new file mode 100644 index 0000000..7e9b935 --- /dev/null +++ b/enterprise-compute-quota-governance/README.md @@ -0,0 +1,44 @@ +# Enterprise Compute Quota Governance + +This module adds a focused Enterprise Tooling slice for institutional compute and +storage governance. It helps admins see which labs, departments, and projects +are approaching or exceeding GPU and storage allocations, then turns those +signals into approval queue items, dashboard metrics, webhook events, and +export-ready evidence. + +## Why this fits Issue #19 + +The issue calls for admin dashboards, usage stats, custom flags, API/webhook +integration, and export pipelines. This slice covers that surface without +duplicating the existing open PRs for broad dashboards, export packaging, +webhook replay, trust center, compliance packets, identity drift, retention, +grant compliance, data residency, SLA monitoring, lab inventory, or secret +rotation. + +## What is included + +- Portfolio dashboard metrics for GPU hours, storage, forecast cost, risk bands, + departments, cost centers, and top at-risk projects. +- Deterministic quota evaluation for warning, critical, and blocked states. +- Admin approval queue with requested decisions and action recommendations. +- Custom tag preservation for grant, doctoral, restricted-data, ELN sync, + open-science, and reproducibility initiatives. +- Export manifest for institutional dashboards, finance chargeback ledgers, + compliance archives, and workflow webhooks. +- HMAC-signed webhook payloads using synthetic sample data only. + +## Local verification + +```sh +cd enterprise-compute-quota-governance +npm test +npm run demo +``` + +The implementation uses only Node.js built-ins and has no install step. + +## Demo output + +`npm run demo` prints the portfolio summary, review queue, and signed webhook +event IDs. A static visual preview is available in `docs/demo.svg`; the PR also +includes `docs/demo.mp4` as a short reviewer demo artifact. diff --git a/enterprise-compute-quota-governance/demo.js b/enterprise-compute-quota-governance/demo.js new file mode 100644 index 0000000..f577e16 --- /dev/null +++ b/enterprise-compute-quota-governance/demo.js @@ -0,0 +1,29 @@ +"use strict"; + +const sampleData = require("./sample-data.json"); +const { evaluateQuotaGovernance } = require("./index"); + +const result = evaluateQuotaGovernance(sampleData); + +console.log("Enterprise Compute Quota Governance Demo"); +console.log(`Institution: ${result.institution}`); +console.log(`Period: ${result.period}`); +console.log(""); +console.log("Portfolio"); +console.log(`- Projects: ${result.dashboard.portfolio.projectCount}`); +console.log(`- Forecast GPU hours: ${result.dashboard.portfolio.forecastGpuHours}`); +console.log(`- Projected storage GB: ${result.dashboard.portfolio.projectedStorageGb}`); +console.log(`- Forecast cost USD: ${result.dashboard.portfolio.forecastCostUsd}`); +console.log(`- Risk counts: ${JSON.stringify(result.dashboard.portfolio.riskCounts)}`); +console.log(""); +console.log("Top review queue"); +for (const item of result.approvalQueue) { + console.log( + `- ${item.severity.toUpperCase()} ${item.projectId}: ${item.requestedDecision} (${item.reasons.join("; ")})` + ); +} +console.log(""); +console.log("Webhook events"); +for (const event of result.webhookEvents) { + console.log(`- ${event.id} ${event.signature.slice(0, 23)}...`); +} diff --git a/enterprise-compute-quota-governance/docs/demo.mp4 b/enterprise-compute-quota-governance/docs/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..aa0c02e161193a9e586ed0295aaede4da9e673bc GIT binary patch literal 58148 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 literal 0 HcmV?d00001 diff --git a/enterprise-compute-quota-governance/docs/demo.svg b/enterprise-compute-quota-governance/docs/demo.svg new file mode 100644 index 0000000..b02bf50 --- /dev/null +++ b/enterprise-compute-quota-governance/docs/demo.svg @@ -0,0 +1,38 @@ + + Enterprise compute quota governance demo preview + Dashboard preview showing institutional compute and storage quota risk, approval queue, and webhook events. + + + Enterprise Compute Quota Governance + Northbridge Research University · 2026-Q2 + + Forecast GPU hours + 3,321 + + Projected storage + 15,010 GB + + Forecast cost + $12,257 + + At-risk projects + 4 + Admin approval queue + + BLOCKED + microscopy-foundation-model · block and escalate + + CRITICAL + climate-preprint-replication · approve extension + + WARNING + grant-tracked work receives review before billing + Webhook and exports + + HMAC signed review events + + Finance chargeback manifest + + Compliance evidence archive + Synthetic data only · node test.js · node demo.js + diff --git a/enterprise-compute-quota-governance/index.js b/enterprise-compute-quota-governance/index.js new file mode 100644 index 0000000..42d5e88 --- /dev/null +++ b/enterprise-compute-quota-governance/index.js @@ -0,0 +1,432 @@ +"use strict"; + +const crypto = require("node:crypto"); + +const DEFAULT_POLICY = Object.freeze({ + warningRatio: 0.8, + criticalRatio: 1, + blockRatio: 1.15, + reviewWindowDays: 7, + webhookSecret: "synthetic-quota-demo-secret" +}); + +const RISK_RANK = Object.freeze({ + normal: 0, + warning: 1, + critical: 2, + blocked: 3 +}); + +function evaluateQuotaGovernance(input, policyOverrides = {}) { + if (!input || typeof input !== "object") { + throw new TypeError("evaluateQuotaGovernance requires an input object"); + } + + const policy = normalizePolicy(input.policy, policyOverrides); + const labs = Array.isArray(input.labs) ? input.labs : []; + const projectEvaluations = labs.flatMap((lab) => + (Array.isArray(lab.projects) ? lab.projects : []).map((project) => + evaluateProject(lab, project, policy) + ) + ); + + const dashboard = buildDashboard(input, projectEvaluations); + const approvalQueue = buildApprovalQueue(projectEvaluations, policy); + const exportManifest = buildExportManifest(input, projectEvaluations, dashboard, approvalQueue); + const complianceEvidence = buildComplianceEvidence(input, projectEvaluations, dashboard); + + const result = { + institution: input.institution || "Unknown institution", + period: input.period || "unspecified", + generatedAt: input.generatedAt || new Date(0).toISOString(), + policy: redactPolicy(policy), + dashboard, + approvalQueue, + exportManifest, + complianceEvidence + }; + + result.webhookEvents = buildWebhookEvents(result, policy.webhookSecret); + return result; +} + +function normalizePolicy(...sources) { + const merged = Object.assign({}, DEFAULT_POLICY, ...sources.filter(Boolean)); + for (const key of ["warningRatio", "criticalRatio", "blockRatio"]) { + if (!Number.isFinite(merged[key]) || merged[key] <= 0) { + throw new TypeError(`policy.${key} must be a positive number`); + } + } + if (!(merged.warningRatio < merged.criticalRatio && merged.criticalRatio < merged.blockRatio)) { + throw new TypeError("policy ratios must be ordered warning < critical < block"); + } + if (!Number.isInteger(merged.reviewWindowDays) || merged.reviewWindowDays < 1) { + throw new TypeError("policy.reviewWindowDays must be a positive integer"); + } + if (!merged.webhookSecret || typeof merged.webhookSecret !== "string") { + throw new TypeError("policy.webhookSecret must be a non-empty string"); + } + return merged; +} + +function evaluateProject(lab, project, policy) { + const compute = project.compute || {}; + const storage = project.storage || {}; + const computeForecast = toNumber(compute.usedGpuHours) + toNumber(compute.pendingGpuHours); + const storageForecast = toNumber(storage.projectedGb, storage.usedGb); + const computeRatio = quotaRatio(computeForecast, compute.allocatedGpuHours); + const storageRatio = quotaRatio(storageForecast, storage.allocatedGb); + const computeRisk = riskFromRatio(computeRatio, policy); + const storageRisk = riskFromRatio(storageRatio, policy); + const risk = maxRisk(computeRisk, storageRisk); + const computeOverage = Math.max(0, computeForecast - toNumber(compute.allocatedGpuHours)); + const storageOverage = Math.max(0, storageForecast - toNumber(storage.allocatedGb)); + const forecastCostUsd = + computeForecast * toNumber(compute.unitCostUsd) + storageForecast * toNumber(storage.unitCostUsd); + + return { + projectId: project.id, + projectTitle: project.title, + labId: lab.id, + labName: lab.name, + department: lab.department, + costCenter: lab.costCenter, + principalInvestigator: project.principalInvestigator, + funder: project.funder, + tags: Array.isArray(project.tags) ? [...project.tags].sort() : [], + compute: { + allocatedGpuHours: toNumber(compute.allocatedGpuHours), + usedGpuHours: toNumber(compute.usedGpuHours), + pendingGpuHours: toNumber(compute.pendingGpuHours), + forecastGpuHours: computeForecast, + forecastRatio: roundRatio(computeRatio), + overageGpuHours: round(computeOverage) + }, + storage: { + allocatedGb: toNumber(storage.allocatedGb), + usedGb: toNumber(storage.usedGb), + projectedGb: storageForecast, + forecastRatio: roundRatio(storageRatio), + overageGb: round(storageOverage) + }, + forecastCostUsd: round(forecastCostUsd), + risk, + riskDrivers: riskDrivers(computeRisk, storageRisk, computeOverage, storageOverage), + recommendedActions: recommendedActions(computeRisk, storageRisk, project) + }; +} + +function buildDashboard(input, projectEvaluations) { + const riskCounts = { normal: 0, warning: 0, critical: 0, blocked: 0 }; + const portfolio = { + projectCount: projectEvaluations.length, + allocatedGpuHours: 0, + forecastGpuHours: 0, + allocatedStorageGb: 0, + projectedStorageGb: 0, + forecastCostUsd: 0, + riskCounts + }; + const departments = new Map(); + + for (const project of projectEvaluations) { + riskCounts[project.risk] += 1; + portfolio.allocatedGpuHours += project.compute.allocatedGpuHours; + portfolio.forecastGpuHours += project.compute.forecastGpuHours; + portfolio.allocatedStorageGb += project.storage.allocatedGb; + portfolio.projectedStorageGb += project.storage.projectedGb; + portfolio.forecastCostUsd += project.forecastCostUsd; + + const department = departments.get(project.department) || { + department: project.department, + projectCount: 0, + atRiskProjects: 0, + forecastCostUsd: 0, + costCenters: [] + }; + department.projectCount += 1; + department.forecastCostUsd += project.forecastCostUsd; + if (project.risk !== "normal") { + department.atRiskProjects += 1; + } + if (project.costCenter && !department.costCenters.includes(project.costCenter)) { + department.costCenters.push(project.costCenter); + } + departments.set(project.department, department); + } + + return { + institution: input.institution || "Unknown institution", + period: input.period || "unspecified", + portfolio: roundDashboard(portfolio), + departments: [...departments.values()] + .map((department) => ({ + ...department, + forecastCostUsd: round(department.forecastCostUsd), + costCenters: department.costCenters.sort() + })) + .sort((a, b) => b.atRiskProjects - a.atRiskProjects || a.department.localeCompare(b.department)), + topAtRiskProjects: [...projectEvaluations] + .filter((project) => project.risk !== "normal") + .sort(compareRisk) + .slice(0, 5) + .map((project) => ({ + projectId: project.projectId, + projectTitle: project.projectTitle, + labName: project.labName, + risk: project.risk, + computeForecastRatio: project.compute.forecastRatio, + storageForecastRatio: project.storage.forecastRatio, + recommendedActions: project.recommendedActions + })) + }; +} + +function buildApprovalQueue(projectEvaluations, policy) { + return [...projectEvaluations] + .filter((project) => project.risk !== "normal") + .sort(compareRisk) + .map((project, index) => ({ + queueId: `quota-review-${String(index + 1).padStart(3, "0")}`, + projectId: project.projectId, + projectTitle: project.projectTitle, + labName: project.labName, + department: project.department, + owner: project.principalInvestigator, + severity: project.risk, + dueInDays: project.risk === "blocked" ? 1 : policy.reviewWindowDays, + reasons: project.riskDrivers, + requestedDecision: decisionForRisk(project.risk), + recommendedActions: project.recommendedActions + })); +} + +function buildExportManifest(input, projectEvaluations, dashboard, approvalQueue) { + const atRiskProjects = projectEvaluations.filter((project) => project.risk !== "normal"); + return { + manifestId: slug([input.institution || "institution", input.period || "period", "quota-governance"]), + generatedAt: input.generatedAt || new Date(0).toISOString(), + formats: ["json", "csv-ready"], + targets: [ + { + system: "institutional-admin-dashboard", + payload: "portfolio quota summary, department chargeback, top risk queue", + recordCount: dashboard.portfolio.projectCount + }, + { + system: "finance-chargeback-ledger", + payload: "cost center usage forecast with compute and storage units", + recordCount: dashboard.departments.length + }, + { + system: "compliance-evidence-archive", + payload: "review decisions, custom tags, funder-linked quota actions", + recordCount: atRiskProjects.length + }, + { + system: "workflow-webhooks", + payload: "quota review required events for restricted or over-quota projects", + recordCount: approvalQueue.length + } + ] + }; +} + +function buildComplianceEvidence(input, projectEvaluations, dashboard) { + const flaggedTags = [...new Set(projectEvaluations.flatMap((project) => project.tags))].sort(); + return { + requirementMap: [ + "Organization-wide admin dashboard for projects, departments, cost centers, and risk queue", + "Usage stats for compute, storage, pending jobs, forecast overage, and chargeback", + "Custom tags retained for grant, doctoral, restricted-data, and open-science initiatives", + "Webhook-ready review events with deterministic HMAC signatures", + "Export manifest for institutional dashboards, finance ledgers, and compliance archives" + ], + customTags: flaggedTags, + fundersRepresented: [...new Set(projectEvaluations.map((project) => project.funder).filter(Boolean))].sort(), + reviewSummary: { + generatedAt: input.generatedAt || new Date(0).toISOString(), + riskCounts: dashboard.portfolio.riskCounts, + atRiskProjectCount: projectEvaluations.filter((project) => project.risk !== "normal").length + } + }; +} + +function buildWebhookEvents(result, secret) { + return result.approvalQueue.map((item) => { + const payload = { + eventType: "enterprise.quota_review_required", + institution: result.institution, + period: result.period, + queueId: item.queueId, + projectId: item.projectId, + severity: item.severity, + requestedDecision: item.requestedDecision, + dueInDays: item.dueInDays + }; + return { + id: `${payload.eventType}.${item.queueId}`, + topic: payload.eventType, + destination: "institutional-admin-api", + payload, + signature: `sha256=${signPayload(payload, secret)}` + }; + }); +} + +function signPayload(payload, secret) { + return crypto.createHmac("sha256", secret).update(stableStringify(payload)).digest("hex"); +} + +function stableStringify(value) { + if (Array.isArray(value)) { + return `[${value.map(stableStringify).join(",")}]`; + } + if (value && typeof value === "object") { + return `{${Object.keys(value) + .sort() + .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`) + .join(",")}}`; + } + return JSON.stringify(value); +} + +function quotaRatio(forecast, allocated) { + const allocatedNumber = toNumber(allocated); + if (allocatedNumber <= 0) { + return forecast > 0 ? Number.POSITIVE_INFINITY : 0; + } + return forecast / allocatedNumber; +} + +function riskFromRatio(value, policy) { + if (value >= policy.blockRatio) { + return "blocked"; + } + if (value >= policy.criticalRatio) { + return "critical"; + } + if (value >= policy.warningRatio) { + return "warning"; + } + return "normal"; +} + +function maxRisk(...risks) { + return risks.sort((a, b) => RISK_RANK[b] - RISK_RANK[a])[0]; +} + +function riskDrivers(computeRisk, storageRisk, computeOverage, storageOverage) { + const drivers = []; + if (computeRisk !== "normal") { + drivers.push( + computeOverage > 0 + ? `compute forecast exceeds allocation by ${round(computeOverage)} GPU hours` + : `compute forecast is in ${computeRisk} band` + ); + } + if (storageRisk !== "normal") { + drivers.push( + storageOverage > 0 + ? `storage forecast exceeds allocation by ${round(storageOverage)} GB` + : `storage forecast is in ${storageRisk} band` + ); + } + return drivers; +} + +function recommendedActions(computeRisk, storageRisk, project) { + const actions = new Set(); + const projectTags = Array.isArray(project.tags) ? project.tags : []; + const overallRisk = maxRisk(computeRisk, storageRisk); + + if (overallRisk === "blocked") { + actions.add("pause-new-workloads-until-admin-review"); + actions.add("route-to-department-head"); + } else if (overallRisk === "critical") { + actions.add("require-quota-extension-approval"); + } else if (overallRisk === "warning") { + actions.add("notify-lab-owner-before-next-billing-cycle"); + } + + if (storageRisk !== "normal") { + actions.add("review-cold-storage-and-export-archive-options"); + } + if (computeRisk !== "normal") { + actions.add("cap-pending-gpu-jobs-until-approval"); + } + if (projectTags.includes("RESTRICTED-DATA")) { + actions.add("attach-restricted-data-compliance-evidence"); + } + if (projectTags.includes("GRANT-TRACKED")) { + actions.add("preserve-funder-mandate-audit-trail"); + } + + return [...actions].sort(); +} + +function decisionForRisk(risk) { + if (risk === "blocked") { + return "block-and-escalate"; + } + if (risk === "critical") { + return "approve-extension-or-reduce-forecast"; + } + return "review-before-next-allocation-cycle"; +} + +function compareRisk(a, b) { + return ( + RISK_RANK[b.risk] - RISK_RANK[a.risk] || + b.forecastCostUsd - a.forecastCostUsd || + a.projectId.localeCompare(b.projectId) + ); +} + +function roundDashboard(portfolio) { + return { + ...portfolio, + allocatedGpuHours: round(portfolio.allocatedGpuHours), + forecastGpuHours: round(portfolio.forecastGpuHours), + allocatedStorageGb: round(portfolio.allocatedStorageGb), + projectedStorageGb: round(portfolio.projectedStorageGb), + forecastCostUsd: round(portfolio.forecastCostUsd) + }; +} + +function redactPolicy(policy) { + const copy = { ...policy }; + copy.webhookSecret = ""; + return copy; +} + +function toNumber(value, fallback = 0) { + const candidate = value ?? fallback; + return Number.isFinite(Number(candidate)) ? Number(candidate) : 0; +} + +function round(value) { + return Math.round(value * 100) / 100; +} + +function roundRatio(value) { + if (!Number.isFinite(value)) { + return value; + } + return Math.round(value * 1000) / 1000; +} + +function slug(parts) { + return parts + .join("-") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)/g, ""); +} + +module.exports = { + DEFAULT_POLICY, + evaluateQuotaGovernance, + signPayload, + stableStringify +}; diff --git a/enterprise-compute-quota-governance/package.json b/enterprise-compute-quota-governance/package.json new file mode 100644 index 0000000..2a56b9e --- /dev/null +++ b/enterprise-compute-quota-governance/package.json @@ -0,0 +1,15 @@ +{ + "name": "enterprise-compute-quota-governance", + "version": "1.0.0", + "private": true, + "description": "Institutional compute and storage quota governance for SCIBASE Enterprise Tooling.", + "main": "index.js", + "scripts": { + "demo": "node demo.js", + "test": "node test.js" + }, + "engines": { + "node": ">=18" + }, + "license": "MIT" +} diff --git a/enterprise-compute-quota-governance/requirements-map.md b/enterprise-compute-quota-governance/requirements-map.md new file mode 100644 index 0000000..932c2db --- /dev/null +++ b/enterprise-compute-quota-governance/requirements-map.md @@ -0,0 +1,19 @@ +# Issue #19 Requirement Map + +| Issue #19 capability | Compute quota governance coverage | +| --- | --- | +| Organization-wide admin dashboards | `dashboard.portfolio`, `dashboard.departments`, and `dashboard.topAtRiskProjects` summarize institutional usage, cost centers, department risk, and review queues. | +| Usage stats | The evaluator reports allocated GPU hours, forecast GPU hours, allocated storage, projected storage, pending jobs, overages, forecast cost, and risk bands. | +| Productivity and compliance metrics | Custom project tags, funders, restricted-data flags, grant-tracked work, and risk counts are preserved in `complianceEvidence`. | +| Custom tags or flags | Sample projects include `GRANT-TRACKED`, `DOCTORAL-WORK`, `RESTRICTED-DATA`, `ELN-SYNC`, `OPEN-SCIENCE`, and `REPRODUCIBILITY`; the evidence packet exports the distinct tag set. | +| API and webhooks | `webhookEvents` are deterministic `enterprise.quota_review_required` payloads with HMAC signatures generated by `signPayload`. | +| Export pipelines | `exportManifest.targets` covers admin dashboards, finance chargeback ledgers, compliance evidence archives, and workflow webhook delivery. | +| Institutional use case | The sample data models labs, departments, PIs, funders, cost centers, compute allocations, storage allocations, and governance actions for a research university. | + +## Differentiation from reviewed open PRs + +This is intentionally scoped to compute and storage quota governance. It does +not reimplement broad enterprise dashboards, export pipelines, compliance +packet generation, webhook replay, identity provisioning drift, retention/legal +hold controls, grant portfolio compliance, data residency, SLA uptime, lab +inventory sync, or API secret rotation. diff --git a/enterprise-compute-quota-governance/sample-data.json b/enterprise-compute-quota-governance/sample-data.json new file mode 100644 index 0000000..3329c9b --- /dev/null +++ b/enterprise-compute-quota-governance/sample-data.json @@ -0,0 +1,108 @@ +{ + "institution": "Northbridge Research University", + "period": "2026-Q2", + "generatedAt": "2026-05-17T00:00:00.000Z", + "policy": { + "warningRatio": 0.8, + "criticalRatio": 1, + "blockRatio": 1.15, + "reviewWindowDays": 7, + "webhookSecret": "synthetic-quota-demo-secret" + }, + "labs": [ + { + "id": "lab-neuro", + "name": "Neural Systems Lab", + "department": "Neuroscience", + "costCenter": "NRU-NEURO-410", + "owners": ["Dr. Mina Patel", "research-ops@nru.example"], + "projects": [ + { + "id": "neuro-open-atlas", + "title": "Open Neural Atlas", + "principalInvestigator": "Dr. Mina Patel", + "funder": "NIH", + "tags": ["GRANT-TRACKED", "OPEN-SCIENCE"], + "compute": { + "allocatedGpuHours": 1200, + "usedGpuHours": 910, + "pendingGpuHours": 240, + "unitCostUsd": 3.4 + }, + "storage": { + "allocatedGb": 8000, + "usedGb": 6200, + "projectedGb": 6900, + "unitCostUsd": 0.03 + } + }, + { + "id": "microscopy-foundation-model", + "title": "Microscopy Foundation Model", + "principalInvestigator": "Dr. Leon Watts", + "funder": "NSF", + "tags": ["DOCTORAL-WORK", "RESTRICTED-DATA"], + "compute": { + "allocatedGpuHours": 900, + "usedGpuHours": 980, + "pendingGpuHours": 180, + "unitCostUsd": 4.1 + }, + "storage": { + "allocatedGb": 5000, + "usedGb": 5100, + "projectedGb": 5600, + "unitCostUsd": 0.04 + } + } + ] + }, + { + "id": "lab-climate", + "name": "Climate Informatics Group", + "department": "Earth Systems", + "costCenter": "NRU-EARTH-225", + "owners": ["Dr. Rowan Ellis", "earth-admin@nru.example"], + "projects": [ + { + "id": "river-sensor-elns", + "title": "River Sensor ELN Sync", + "principalInvestigator": "Dr. Rowan Ellis", + "funder": "Horizon EU", + "tags": ["ELN-SYNC", "GRANT-TRACKED"], + "compute": { + "allocatedGpuHours": 300, + "usedGpuHours": 121, + "pendingGpuHours": 40, + "unitCostUsd": 2.9 + }, + "storage": { + "allocatedGb": 1000, + "usedGb": 870, + "projectedGb": 930, + "unitCostUsd": 0.02 + } + }, + { + "id": "climate-preprint-replication", + "title": "Climate Preprint Replication", + "principalInvestigator": "Dr. Sabine Klein", + "funder": "UKRI", + "tags": ["OPEN-SCIENCE", "REPRODUCIBILITY"], + "compute": { + "allocatedGpuHours": 800, + "usedGpuHours": 770, + "pendingGpuHours": 80, + "unitCostUsd": 3.1 + }, + "storage": { + "allocatedGb": 2200, + "usedGb": 1450, + "projectedGb": 1580, + "unitCostUsd": 0.025 + } + } + ] + } + ] +} diff --git a/enterprise-compute-quota-governance/test.js b/enterprise-compute-quota-governance/test.js new file mode 100644 index 0000000..00889db --- /dev/null +++ b/enterprise-compute-quota-governance/test.js @@ -0,0 +1,71 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const sampleData = require("./sample-data.json"); +const { evaluateQuotaGovernance, signPayload, stableStringify } = require("./index"); + +const result = evaluateQuotaGovernance(sampleData); + +assert.equal(result.institution, "Northbridge Research University"); +assert.equal(result.period, "2026-Q2"); +assert.deepEqual(result.policy.webhookSecret, ""); + +assert.equal(result.dashboard.portfolio.projectCount, 4); +assert.deepEqual(result.dashboard.portfolio.riskCounts, { + normal: 0, + warning: 2, + critical: 1, + blocked: 1 +}); + +assert.equal(result.approvalQueue.length, 4); +assert.equal(result.approvalQueue[0].projectId, "microscopy-foundation-model"); +assert.equal(result.approvalQueue[0].severity, "blocked"); +assert.equal(result.approvalQueue[0].requestedDecision, "block-and-escalate"); +assert.ok( + result.approvalQueue[0].recommendedActions.includes("attach-restricted-data-compliance-evidence") +); + +const climateReview = result.approvalQueue.find( + (item) => item.projectId === "climate-preprint-replication" +); +assert.equal(climateReview.severity, "critical"); +assert.equal(climateReview.requestedDecision, "approve-extension-or-reduce-forecast"); + +assert.deepEqual( + result.exportManifest.targets.map((target) => target.system), + [ + "institutional-admin-dashboard", + "finance-chargeback-ledger", + "compliance-evidence-archive", + "workflow-webhooks" + ] +); + +assert.ok(result.complianceEvidence.customTags.includes("GRANT-TRACKED")); +assert.ok(result.complianceEvidence.customTags.includes("RESTRICTED-DATA")); +assert.ok( + result.complianceEvidence.requirementMap.some((line) => + line.includes("Webhook-ready review events") + ) +); + +const firstEvent = result.webhookEvents[0]; +assert.equal(firstEvent.topic, "enterprise.quota_review_required"); +assert.equal(firstEvent.destination, "institutional-admin-api"); +assert.equal( + firstEvent.signature, + `sha256=${signPayload(firstEvent.payload, sampleData.policy.webhookSecret)}` +); + +assert.equal( + stableStringify({ b: 2, a: [3, { c: "x" }] }), + '{"a":[3,{"c":"x"}],"b":2}' +); + +assert.throws( + () => evaluateQuotaGovernance({ policy: { warningRatio: 1, criticalRatio: 0.8, blockRatio: 1.1 } }), + /warning < critical < block/ +); + +console.log("enterprise-compute-quota-governance tests passed"); From 9c9023f77cd9f1f03a3a563b3639527e14b89335 Mon Sep 17 00:00:00 2001 From: SR Date: Sat, 16 May 2026 21:16:30 -0600 Subject: [PATCH 2/5] Add compute quota syntax check --- enterprise-compute-quota-governance/README.md | 1 + enterprise-compute-quota-governance/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/enterprise-compute-quota-governance/README.md b/enterprise-compute-quota-governance/README.md index 7e9b935..8175290 100644 --- a/enterprise-compute-quota-governance/README.md +++ b/enterprise-compute-quota-governance/README.md @@ -31,6 +31,7 @@ rotation. ```sh cd enterprise-compute-quota-governance +npm run check npm test npm run demo ``` diff --git a/enterprise-compute-quota-governance/package.json b/enterprise-compute-quota-governance/package.json index 2a56b9e..7f7e5fc 100644 --- a/enterprise-compute-quota-governance/package.json +++ b/enterprise-compute-quota-governance/package.json @@ -5,6 +5,7 @@ "description": "Institutional compute and storage quota governance for SCIBASE Enterprise Tooling.", "main": "index.js", "scripts": { + "check": "node --check index.js && node --check demo.js && node --check test.js", "demo": "node demo.js", "test": "node test.js" }, From ac7052204bcfb8fb9f129226e601faa2b9ee1e62 Mon Sep 17 00:00:00 2001 From: SR Date: Sat, 16 May 2026 22:04:27 -0600 Subject: [PATCH 3/5] Add compute quota API catalog --- enterprise-compute-quota-governance/README.md | 10 +- enterprise-compute-quota-governance/demo.js | 5 + .../docs/demo.mp4 | Bin 58148 -> 57708 bytes .../docs/demo.svg | 10 +- enterprise-compute-quota-governance/index.js | 96 +++++++++++++++++- .../requirements-map.md | 4 +- enterprise-compute-quota-governance/test.js | 37 ++++++- 7 files changed, 148 insertions(+), 14 deletions(-) diff --git a/enterprise-compute-quota-governance/README.md b/enterprise-compute-quota-governance/README.md index 8175290..256dabd 100644 --- a/enterprise-compute-quota-governance/README.md +++ b/enterprise-compute-quota-governance/README.md @@ -21,10 +21,12 @@ rotation. departments, cost centers, and top at-risk projects. - Deterministic quota evaluation for warning, critical, and blocked states. - Admin approval queue with requested decisions and action recommendations. +- REST API catalog for dashboard, review queue, project detail, decision, and + export manifest routes, including service scopes and integration clients. - Custom tag preservation for grant, doctoral, restricted-data, ELN sync, open-science, and reproducibility initiatives. - Export manifest for institutional dashboards, finance chargeback ledgers, - compliance archives, and workflow webhooks. + compliance archives, workflow webhooks, and the REST API catalog. - HMAC-signed webhook payloads using synthetic sample data only. ## Local verification @@ -40,6 +42,6 @@ The implementation uses only Node.js built-ins and has no install step. ## Demo output -`npm run demo` prints the portfolio summary, review queue, and signed webhook -event IDs. A static visual preview is available in `docs/demo.svg`; the PR also -includes `docs/demo.mp4` as a short reviewer demo artifact. +`npm run demo` prints the portfolio summary, review queue, REST API routes, and +signed webhook event IDs. A static visual preview is available in `docs/demo.svg`; +the PR also includes `docs/demo.mp4` as a short reviewer demo artifact. diff --git a/enterprise-compute-quota-governance/demo.js b/enterprise-compute-quota-governance/demo.js index f577e16..0f6ee25 100644 --- a/enterprise-compute-quota-governance/demo.js +++ b/enterprise-compute-quota-governance/demo.js @@ -23,6 +23,11 @@ for (const item of result.approvalQueue) { ); } console.log(""); +console.log("API catalog"); +for (const endpoint of result.apiCatalog.endpoints) { + console.log(`- ${endpoint.method} ${endpoint.path} [${endpoint.scope}]`); +} +console.log(""); console.log("Webhook events"); for (const event of result.webhookEvents) { console.log(`- ${event.id} ${event.signature.slice(0, 23)}...`); diff --git a/enterprise-compute-quota-governance/docs/demo.mp4 b/enterprise-compute-quota-governance/docs/demo.mp4 index aa0c02e161193a9e586ed0295aaede4da9e673bc..ad45722696984e8dc213a7ebd23ab790eeb2db4d 100644 GIT binary patch literal 57708 zcmeFYbx>UWvM4&ZC%6U}T!K3UclY29gS)#EoCJ4wcM0xJ&;Y^R-66;wzP-=A=bgHL zzN%OCYIm((?fvsvy=H0v007X`*~8w#$<77V$1VH?@ z2_P^7(~3WWiZHE~kkHWn000yKe8Yo_!X1MEz?vU#kCAr2JUoEI0GLM3j{lz4x#o8T z7;v4O|C#c4{=J98?^_NGnBbATFU32&>%j;sY5aeDhyJC5{!{PSG> z@*nv>^YQ<}eqjA?`{{j8F@RA5jPHB#{|lKO+^*Qa+XmNa#&%%-9l&P@xe-i_Tuht| z!Booz^bdWvk^J|P7+gj>8rs{xSFQgW=q>(LcT#$bbpoxO)O$kZ9g!o5m^<58gMIAmoh|Haop^x8hDL_Q ze9S;_+0MrbGyxe|+ZkK&G4n9-FaZs14Xr(#KzvN@tUOHa%*?Dn8xWs4$Q|h9Vg%+m zf%Z-w;85^g-_eARnSlu$1ik@nEZjjR`tKQ;!4dk7hPGxPK4uP}vALt2jiEj`lo{yk z2(q@eZ~}8&?p!9u&S1va!G@0s+yz4uPdi%>9}6=bGc(ZC(8*cf-pR_s{@vqW2^{S8 z?MzLbK+b&hEI?;-M{oo<5ev}T&d$ov94zVomy!kOWNl##?#zEFn1Hs9|1@H3VPok0 z-VqC1XON?{A=nA5H?nqdH1yCnwzIJ}bOy`D;5h=n(plJoQ-B>E4c~oC9SvV)ABK^>k%ghtdm|Q(pnnGD2C^_ScQyjs?Ce3d`et_aVC%n> z_TW$}kOw$79}7Fvzgd0oixyY}IvIm(LB=l5d~8hb-E=g3pHoMWlQ}rt(OCa~5BJ^f zXv}BqXbQ9ezrBFohXoe+SXddDfDZ42;bUUp0JHY*iT|O7?tC2FV1<)2$exc4Xkiat zCGd^_FCsXXp#yjWyf5ebOHx)KC@dK8i;?T+&=q8o6ThK9tvssGthh3-_5gLwXflGQx4t$H5-Prkil*jB>l6| z`Q_Q5|HhrQY3c|HfqyCA)27_g%?1S!YfYh9wN*SMcQN(kD$tAa-ib|i0e%^luQqr% zTO?bPlI%=2rPCgN8OOScBr%8u%X9`ErrF~Yhr}EdzBz*c-7fa`;sxiCO_wNscgO<^ z<{)qA8m8g+pZtQEB}CrqCk;3;;q&QpIRJp1gN|kRB(8;&I$qZeiZb5pIWJY5LPlZ#YT4!JxH#- zgXetQ-40ei&ul?47v9{K;C%|Bxby<`cXi($p9S8Mps`=rg%f6s?ACR<0RXlGvisa9 zYL+hx6kYI$Cj&2GD&c16tx2V=PDPyI0>-f1q;7Wz(0?}rd!RQOorY=fh5CK3KIaN$ zK{zdmJ)W#oL|QbXy(rqtW>%ZHqLS3ty1P_qE-D})koLZgD*y|^)rHF)2ekmjW_9vFptCFkl zr7X>g;vvy8nHI02xEV7YNU>R&7&LX8FsW2ukAQApAwYI(|uoGOw zrfZ3I$kKl)td-8PWk0bq%W3I7l{yhsTw)9Fx($H^{Gc}yGx2uO1ff_@c-0wj^ z-=)N%zhNI6A;h}bZ{x_Ea(O5pe`MSoUn=@4ma{J4zxSpX&2@+(yRDn^n-#v>)fDc6 z0$FIzBV4^@R8jtFCK+M8_uN}V_w=jTvswC0jkcGpaHU#0ig5w-vP+%)_X6HG*PUqo zzLF6pBm6rez02 z&xq;JZly^UsAJq@B!}KF-rp>V9Gf+poh0mwPx#~#pk}z*~OV0~jQ#tISC?=@LDn#be z)l?nqb7~E1!*`{7RdAt~Lt?f!$;BRns9q|T@0NE0VTzVAs}e-hqO{8X4q~SOQu?wj zmXNtw!=Mu3l1zv&Q_+v`qVH*}cBJbVjJfn)5MFhudwti@3rY>3S@)cSintKTek&ey zqiZ=I%oW)^EQ?0}jH6nBN^H{k!5y{vG+N_=K*aRI&mJ?eiu|b`OFxiKnj@ah@6Gcj zzbXCij|V1VjIg!4#kW<{!-sOO@icn^ns%$@%iV#PubovN~e>R@iT`j4&#uU!6tn_z2r;v zOYAk{Y%(R;MS6@T)eML7=Y1${jGO~ z5IxaYRwLNu7l`)+3M8w;^-J{hE207@Fe6PwdMcO>fveyM;lSPTiK;8R-%xjRZi!p&}8?j+HKIl&!d6v zZ@uRVZU8dnu@@}tG0u1|>-3pDej#jO%0)bW2`PJgQKh;`4HHIUcVG23#mSp8=4lVX zX4lDB0oAGHLQB-OlYo;@9xMdsd=$ddLUN}^P4ZKf-S3WVuZSNANq?`4>b#NZ2GNtY zg>)iO@=r`dt31q0TyoAp!7~dstPTy$7o-gBOxJrkRAkW%&ksDZQ#UNufAVOcr?EIJ z_zIaf)8$M_V+-SD`mjRx^|Sa!cC~#*)0}%x&4xU00F{IWl%2tsNl|=%H{BNiTxuzfxjr z0P5i%{uY-NJe|i0O5snZ&B;=}(KFY=oUeH~>bLn@idb>Qzq%w$t#5okhj2;xT2YmW zNpkP)-EpL48}($9CU9##*B9n7_I%xo=JXfMO*>}_dF)Tg1HBpA-ESw}kw*ph+Svmo z1|QnMTT|Lxwbei31}wv0&KD(+mR_h!vJ$O^ygMG6>>&YpIVbBXmDL_CRY~2q-rr$- zJfx?5uqtQPEydZmtS+Hnyw1J9bVTL3W-^0F_-|=j@(Ra}gHdm?o&Rc^WTtv&qZM8c zob)CAQK@#-+Ruo1lWuC8hyv6GwL_+R3kheq?05DmskH2MwTNjSxyxAO zZ(PV|F$|eTgtIAi`{CmmY5&Bsww7gc{!=fn*#}h_6VNfyI{eo!aIfa64SJ241I2F_ zR9m|Lt2l}Q>aK{j(=*w?gz>J7M$tkpW0dvH>PW$*EaUj53T-9rrB)jj@f0~j?{UiS0JHaG8Q*N&Z#vXKMy7*^2;&(u?_|ok-kp%@`%r zw0%o;XP|q@7F{d0=czBA%1x4|7aqcS72YTW@!b)|T{W4v61El`q4Ht?ngbfc8zqL~ zdCfu7$dBpoE#YKCWLx8r(g}P)Y}MB*6mlS#eC5>&`9WNWSx9m@&MR-5b(;iqw{P`eVf-~xEd++=*DK9{<#5=J{U?SpD`9-rWZcj%`9!gxaK43e9OSyIsUx$OT037XFjOSf~@U`(ADU&+u&uI{&ZuYwHT1+!XF?& zw=~UysWhpnW88vySsW+1udrIXgKWwVD<2t60L?PQklg(_EQYm|J1eBeeajK{W|9y~ ze+mav-HJP4e#;Hyx}#c%lE^k5%&gQK^J{Er_pqs@SLc&BM+Y$D6CDCyn|MBpjPZea zeEyyKJs^N^QRDBVE9-!JIu71Tow42z&S?NIOwSz$qEX7*uO@l~JpNY^hC0Doi^L$f zB$|7cSXuA-D}5Q;2;vJ!=FFP%+`+u)R{;Z!yC3j&kGQbL*wi|d5gzIFhK6_EKb;h+ zYUm`5cTyA)9qA`_pXF^pLp8|)wq$y}=D>OCh^LVsi5-FmqI|J0_I##V%Q0rtbdDBI zT#;on$s~xJV`3={<6iP2H4W+T@x#+>qt&&}709@{pC>qN=`-iU5h2(<>VFD~W5r9x zvu1t5)}lBg9u-JoaoTzPJ|Sh&>+EpkI)+~4i80&=32Uldiv(Oz?KL#s0YGdSIV%l@ z-D!W(Bi%iPk-W)99?!zLYpyGGHFM?(YMREDXpocL9T)A`rpP^5yL+h2jPemJY|Be` z2*kZZVKc=9+(qMlxeOVm7L{)KGgO}6-AWjE9Wk7azZiO)jrH^@F&OTr0Xo6%D z&BIEbc5JMgCn<6l)kfNTcSJKn7=3%dP?g4}b+T@NE-5&-X`N`}TQ_}UqaPCW1N;4u zY#Mtu-;g_A9rFBdbhg#u^IKIrmgU5%sr`p|@xFsN6;cYwl*+~H^{vFIx2H4-0iuD9QS17}D8|Qf#<`mZw&d}^ zO{cG#5Ps}2tV4wT4L@nnw_V7FlcmU;yVWFDDp#zHcn}yZF(wM&go}c-2DGZ; zr~jn)(fap3bV+aPY1E!i19-@B;a&AHa)Kv952tLXRn1zFiIG!l48%j9yJXD6bj#dY z0Q_TvP?$6pi2z+03*E3)6)F4bmKMf4vSQKpzx=eEG`nXR(foX#T>|+LupWFX$*N?_ z6FH9`?pc{(`PRf)0~5PO^tbGGT+T?6_tzXDt83^McdwXIyKF!b5^`bBp>ZT{l((C= z1k9W_U1XxxYp?Ahnl8##%yX;hKx_DbyZA1p-5eC1s4Y=v0kJ-O?%%<5jI;W07>yy`BrMwPLgU`&b_q~7^WdUOGjU!JYJhfIEmDv_wLHZ>Q;a`J6KY7rZi65TMBXwN&vm!Ps&A1V){g|Bcdt2~VYD!Or z8DX_^mKn*(e93u5U)|1UApjbVX(ZW2GAWjE$iKVLvz^A4HutN1eNDrEcRehoA#bmv zV6!5;K{VU<2UTgI*}3i?u0`J^t`bRKtXr=#OJa?C=_(?D=0d?f2(CDaoZ*c7uZUY$f9*4d>Go1?nS>n zwa4ASFohaOoYy(dz&32fIcTJIo?lAD28dzE<`h~)y!k%igXe7PLgRoFpk|kQn^+-| z-Wk$A^cxwzg@A0<;_sZ*yp$z^67%Jj6Bz+2$rycvt87aoBoADLU1opes=hd%O&{&E zfTorzDiraYWQ>Nb%oN)d0^O6;uP%IQbvU$GOegWTOIAjc@YZbB8JujK>Eo9k|1Xjs z37xz2ezL&UGYOF3Ch1kAV#-u0Np%ubEObaILk6%gq0CM{*0CTH{JH-$7SH~bUM29i zjl{7iF-s3Xk?=3WM{5GbBE`F3IDB~7Pl07!%bZ0;u=ayi!pisy2fwJt9)c<%x3<|Y z*)m=ku39ph@@ua4)Qv1|B{XqpP`+3M5Sz+!Io|+kPf}oEJd^hqqCeRYF(vU8&8nkhsFrP-1$RqclRBLTyQSiYl6b$@Fg8>h1Nsx)i=S-Sjk{qf>PVTg=P zScHAEWi*kv{9Dr)tAg-0%gT^sSB;3N+K-P$Ch$815d{mvm*a^0b z_ay?pDN1md!+3OS@|4DCy{TpXt8$c6?(_aJ@^RGL^Z4$pT;#HpKo^rMhl1HWJzh*n{T|o6o$IOY#9vSWFv1V4CV!6l5kTNB z>k&%vHgt%W!?!0;7O`aCzK`aPbs@o>!qmyUePk7|z84&RFSa=$Mo8I{Y4C7aY(_S( z5G&|Yh(^C=5Hi?Ri7<>=!pptObBcv3vAAtgU(}sfA+&UCnsqFpyw|2Y9;73O*NvD7EGH1$Oa`Wk~f?gcSjJD}w4te-21T1+Tj104A zIElc+<#GBrXbi{d=SLiM1=|Sz{@N?@JCx+y+wk@Rc64_J!2QuBVb549G+oW5=gH#j zDFQ^MKl*v`SK%<@t^SXo_WgX5Xr1Ze zx)tMFU(*QQEPi}5fQ*l+j;6Sv{pnim^r7P=UHEPa+jVq2Du8};LzK1F_3tQ|%4}tq z-%XPVo~9#lu!O^94IdYrGwW@hcIEh>3bg{USlVN>hH7t11qi zHc424b$5ETx>ss1e@>eZhRmcY?7tdmh5_~tCwP%ogI5)90=;ST>7T{n=vv{$3)kxn zA;gMwGQRqa=DGSyNA+s3A4t3BO>W8`R?sxHAD{5Jr|W$!Ec!8>$OX|=JtMjq6A$CC zRepS|R{AxCY+j2t(_B=btSDQF806-&-H)LGC5*9__hq$ARIqcB)UO;aWVucY0?T^S zWl>(M?uW8$l;|?~B}p8o<1m!z*4oJrhy4d>a{jbATIQTq(g3TDyFK?*E3U?J7XwDI z3RW>)cc!oYcXNfF)@H*JO~}w&u3yn>IPcrS-)29v;362H!8n`Vu@w>p3rcKrDXh6Y zhGYM674^A#+mO%^V9sjgAIoIVj;zF?qOi?Wwe|05n8LMS@i*r&;5g$LL?f%?jQ11& zBqIY4TPkcGh1`arC_e-HN#DnwUZY;XY87Qx^j`jHBl&NXXCutU+UL^Z&)TzGeXD6n zcRxFff6_kSrD3X+Vlzv zdu;2Rs0XgkyX=Vm-LEvn>fP~OifUYF&sl7T;Q z@497PvTlJLBMP^SJUi2yPYSA4vnDCbeK7CHT0NyIXFntXRyQ}O??-FcV)|H#tuOQc zpjKB4W-SkcHebiugVCj_+ibGN&`zFmsQYH_yy|g8ueVvh;~g(J4bW2c()USXJK@=D zEwj=b7FlqI59LHL0BI`!_OK3h3OTFxK>w8KKudlJP(&+{D0w}u>b&e%Z0ih;mp%Em zg;cwuWH27z%sguYiL1^wOKro#aI{q3F;Lf$0U_0(&AVv@s`-@)!Ox%}IhNd|kzS4D>N z+0V~%qkT6(#Iu3S06tw{pki(yf%>>3>ATN$L2`XSzJ-jp1+GoyxywypU~|S3 z7rMXvD9teHC20VX(zU*Y1VSWlOu(fA{xEv?ELG8tZ{D`2y9XvNC>K>dG zl1ANKcd{m|IN~_U6F%Iz{YKk(rH%gmWO6n4uww`+qb>*fsxmfeM@#)-m8m%umIPjU z%@=wrC*1v8D7=2bUTJo3f1fWTTVfZ4dV(WoMbMV_1*8(Q&~>7J0*^c?5#sej^?u)o zx%BavN5kxgbA8vT`s1Mk9Q?*dr>f*1gCb!8c`Wc%-0mcWtV#)f}sc5$mQ2{Q)HlLCH{?L zU#4`~ly3^SvJQ!SbupvL%N~LE>LG#ZaQwjah}{XRjUH_x4Vj0}hf&O)dR_#RHqd0ED*$h+9AJIMxW&SNkI3Xtre(0Qm+V!zR=1{x#^kA3hPdVNr$}a?FtMKP#Z&VK zp(-NpybE_^wR!ESmK*KLi~i;I+Y|}(%wv-ll$xJlzWELX=%;~ZTG`;TJCa;QEE=VY zpu*z7z;=gjHHwGRS{>b7ERb&~i=eyCkzCU2@8JC(x8nVeA8#sYr4AlE8L>NKc; z&eI1SMRG|SwxwV@6Ncx6`b-%_?iW{DRxatImv)Rx7k4HA^hJQ6f3$CHW#2W{q-E~8 z@cBpCl>{?ZT%*YCO3OVfwji|qFq<~(kts6sSqahl4k9Ff%!B$d*lI{QZ&CH8l(dtU zdU)I{=73QgVZN-0_q;Qyv# z9Os_7jVYM^2`#vX)%)bDN^KnXp#wYna(otvD4co^sGqMDeXzE2X_Qq%lmXfH<0ds~ zDuSOjnr&H!{~2y~g8J!x0$aYJ2*&4cB*-yQE5~L4Fk-TY2+=;N1Jr4u-55)?RPx+GX3#ZVc$Ob3jmJ)*zkZh$ zQlgQl@lOpv{_bnnSTP?{Yukz=$Np9J?(wocC%d1`bDUP*d`Na8XI5lnVuisrO0%D| zJdj-Rw@ty$++T*A?6Vo@y`l#<&HFQY>*gv%Lqb(`c5N^E;?HZ#Sl^dY_Ux(b$DkSo z3F(VY@xzqMwp7&dYiPP@1K@@c>1^4Z$H z`2^7&;I%T(8zoFS^r?vNK7GTu*qtc6kIyezP9@quo^Uck+=Lz&-WlP2 z-SFb=2wGmVp13UkWh2b_IT{ow(%>pNCY-?L?h%jzz;Es^X?7H+$P6J9D@?2wlB6mM zuBe%s3K}69xy;OkqLsA4ADl?`;tawW|HKEy-f5$mt1)^hzT3v zSFXMsdh!-0LgBgWQcZP5BR}6bvxr+b^jL_kmiwBOp|E)c=Y_tn{}Je*v2oEwD&HumhB-?Xp{bL4kkXo_(YQET%7doh&D}`NpIb%1Y1-aK&hj z3AB==9bqTOIDP5uw8DH1&h}!6*+eEqE*CV<;3w>f%Jl|t$Dc8su6Ym%unPt|8Co4MaDpY$z6 z&1%|LYB$qA%<(9)J!bS$A&E0gShgEh-o3Y{oXxe~2|2)QZ!BCaOx2OtF~>_Me=Ny=P6h{4XpO=GC*$`Kt$dH17v=*n_ESl^L|HYf5^3*QuskMKt0M_IoiXzFB`=ZaC zp8aQ()w?yQczA+Bp>$~lZ_`G-kVu}4qOyqUC8j3M8~ zSj`=yCrhh-T$R*B25)(`ZPN6#)f{C0bJ;5<+3nkq4^mfp6y?V0UKj;3ie&iV(x+D@ zXJct~+v*-$^`Oq$R5n4q*FyNzKf9O-$1**=Xs>1H4(%1ZYs0-YDSzt|({`6<{ zFvcspjlOo|)Q*eJBhvu zLD8!8`FA!!IF5uKIVN9c`B20B4^k6%%cUHW#1K2&JEXMCmxox<3U78|wd_sgI)Qn6 ze}T7=)dk&fGcpP9YxD=`4v~V?c3I5+IESm!VXUmNCccjw$MCGLI!(B|^T${Re|$A9 zsA-uF%SuD16^? z)LGV-egI5hJn4Xu!XNTaH$mW^FedecowYIB!Ho3Q73#qI7KdoQfjFo_~%L$uCDXnq7ZY5S-Lp2^ENzI}Ok`yxb*JySJq5#6kpHOMl=XcAqPxNw<; znXz(9if^@s)P@$34$b}n=ERF}xKSTdEfi&cpodF6<+B1ies8{!QZ}?V0jX~jzR&Cd z(~eJ4hEz{QgFsGt27mV{`ndD1J;`veU3`0*rv4 zFXNR34~8j2Kh~tjo#%SRCYdgIlAFiLvod#?jM7``zJ}!fo|^D_NQ%+x1*B9|AL{tJ zHZM(VsENj6l?=lgCZA008i$XSf-d-!1FXax7)fP9iFWZxK9Q9$Ts^vzkcP!+hLkMNsRC2T`8J+yt`;{dDXMuGM@?x zq7VDf*|SWlI@znPPx>)tg}P*nu-06s%7fK!dEG2vzUgR~FGd`qtZ;;0T`bcx)P5XR zg8|p!Dyn(uEyrVOHjn|iGKhBR#h}L%pnYBV#SefRoRvCQd}b{FBF=d!S-(DEcIK7Uk&s)!v*NrwMhao!IMR&# zCq$9gAj7m;=JZi*=y5dast!SrUQGAgH$i2lf<~N4mi>YJ0sCq4{9a zPrQ4pe@=JLr2=0MZF@zybtpr6kEL^`gy*+W@!iq9CoA_gI(F7~yEIG5ji8U`$?6;@a_x7`R~Nb7&4Tyn*4YNs~*=8rqDTYS0G z$ILyuL*cSi;DM3cN6s@pKjGg&Ibhi#&7QKJP**XMksG@ub~sS17oNZt-JTz7Rv|iS z1**3x_#@b4GIB;|0$O#_hlur+O#s>H2zEq?!;lY?W3OX7ezgdoYj+Dfw3N;JSnZSm zP^mt-D!Y0AhY#%zcRP|Aqr|)7ZaP8{WYa|t__n=Jo&tgP0tFsrH*{kV9UgMVitA=Z zCS1jmB&caWEiA$}@R!_gU8>3=ft-s%O@A7!BoDQd=1J{zGe6w!ORZ~ zDG&%uRFg}MqGU__)&Fg}+AfL2HcM1B%(EpMKHK~~kKE8u>!zGJPnL(*sn>*}c=gfZ z`vBVWw^kAdNkwG4c=WHBGV$}yu}RCHazeg*gHui=!pq(Xu9Bf9g6uOgUMbmj1rcT8@#OjGvu9I+I zZ;I7nfbUxShjA{mzWq*S2}Dfayjk1JgYyIw)uQx&Cl(!IW0C%5j;&uv<|4aGNV4XdS!*_WrKvlw z-Y|-Ai3~UD{rTHY6)uv=$B(u0Ejcy_)^fFFu$XP^oQND|pa0DRzBtR{<&FH@t-5yb z=g6G^>|n(4YZSA>uX|guGjnKQsOJ}TV&l_x8iA=~@q-u-_Qq>g>69O#^`hbdD4=Xf9f`W}xuKKb0 zqi0F?-NynexS*stp{bx4Xt;4CS8fYHMfm76KnYw74~pQ&gQjLylWaDTwM|ZqH1#q9 zR!21j&Pc!T%%fqrFLVfY?oJH)Xr6`F_m_`=1o)51%?rF*#r?ampHF6ngvGQzSYYbhoJ2SvB}^D5N~xC!en1LDH4@%+cabi4S@$^sA6$ zU#*t@R?db=^m2`AN>*uAr-zj#QFFtAA(?QEg@;6SpO~WXQq>B$_o7MI={snPi%a4> zmq55CP}JA2cxW~7FubI`q4vPG+;+98#cx|(oEDCyY1d&H+0fqrCY5u|9#!KtG2R)p4*j!ESRBN#?Tz*`x(wycS~-5Os%?#tqk$9CuZ^PZ-j@z$FJ0@>8^_spNp3#jv1(yOCZ{TXu~+OXCg*Dz zF{ZMyljM38P-gf*zT6!|>_bFp7DWIf3B*D#(G;pFT3=D|?TWVQ?BPUSkdoT>&tBK4 zL6WPZ83d}g*@i-q-}3kvn0k7AIVAL-v;#!op942AD>Vd&cFEV7RdY+p_Hv%_q9!Un zJa!EuxA=|6B>gh;n0wPEb>Ylm)$!{1P!&46$IM#hrEJ z6S^^ceGo|%+0S~}nD9;K*uKLc8f(EW2vVIQk%R>%J;JmOmEJ4-^xtQzcDn0dZefw8 zS2do1)jK;X(#>jX)58cBwnrZRUI-M8lR`j^;cdsei+WHuF?Uy0QxywrW^HMa7KNjr z9ckDZA6)hCN?3;Zqibs9#h*L&h{B7Z#6T9<9Id_wJg@5Du?4;1w0m-uPFq|ye4Gz9 zB>iEtgivKtRH19a*QHvHr^Uu@dM9@(yE}N>w#`eK0`JcWkilj1b%)NI+aUT4+x|QV zw;A*#xg=U-l`;M(fPg`@((n90@FJyR3^-xQEw}#-yX2MDt%*1^G)D(Txd0tD?B9RW zc<1$eb6(Ls>X)96etO^+umxK?nm4R+8_oRXOV9VoMtV=)PXmM`(2ohpu@QW|`y(y> zpZcGm1Kf&g7KAKigFHa( z4A&6X#g(v`T5q!S(EtF7olZX1@)y5uNsRR~rk`}lVHJ&9IP`ANyHy;D1-9mi_ODJ( zKDt1%E=8?MDYJ>Om~R>$bgZD?kBonjw>bElRH)71C8f95_b{p0ssFW}9c9p&>hR~p z_^Q1!V?u*UQV05vExY+`0r$jHp2Ku122&- zfT!QFqs_wY19GC^a;khX9oCkSJ}m4v`01wWA=vN1zXzPKBb;J6sZAg zNv8^PEk~TAAQp>{R3yxlJbvM(_)w0r`Vbz7qWVkyUw=3zRhnXaNJ2abEs@5_c-WCL zbP@3ZKo-1uIvY{&Z7`kUcU%4Q%_E~Ssdz#3B9%oLq0w; z7HaG_;EMjzXmyTHYv5<8H@S61z|nQ(K$;Ul_bs&GVp?b+FqXBcAankhEW?{7GovcW zJpXu(qaqiXKTIGm%+3B#O?3Iq?JWb3tP`u(@&_~BGqiGFM5Fl$Iq}odO~d;2Kvdwu zC+Hd5p8>+sKkMlRsc=>t($Ew43RUcTb)U@94MPWx=9-NR@H?$>CdGN?16)`0d-|dsl4yQU<}--4(vatRAjaH18_HbD zBD`j?sXl@A zZ1(hAX1jUYYPEz_O+cqRretsD5Cd2WBBFq-j_;$8-ofWyRH+TlcvAGq!AGj?JJkU} z-{dJahicOatrCE2_O&mo37PCz+~PZ1M$8)i*DM7V7;L zXz}?kma>gH)|O1-vzcEz-5!=0b^l)XIlf&)aAWPQFV?l{C6N>wGY#Z4W{GK2PAh+n z^d8~JRyBr$`!iX(kzp-oolHBQehqAlo98AwbCZ%vAHRe?b(2+@wm{@hC_>iufV%As ztSi}s@v<7$#i7ByFv39M3v!4Z0$eL~;~4OH7eoQkRCZ>3f<{ZRVQBvF+(HETW1d=C z6Ypqyeag>|u<|2hsY603xmzSU^KWGPEQoXVvSCsycE5v74)8Zb%b$^M%cTlU0qXS^ z!_C)!(4MuWgb~1c9j124amw}7n$YOs^UQN&DB=rJoQ-NKQ|JG7uI8wt@)Y(vCv_Fq zUc&_zjV;AO=>~|l=NGXkwTmeS8ntj&48DyQ0W3B5_)ktcFjBT4R3~xD{5NYXiXXiMQ`^O9>ifsF)8z- zHkWfkCz&$>fvW9RS&hWaa3fq@u7t3g!9SZUe&(!%=2p*qqSVY=gIiNOiPLZ{p-oO< zi7B@axwjS6@4YD6r*YL3@*V!Z2*rJ4KFX1cr6O|SdBTKC7wWH+?c${`SB6>F0GasL zmF!Rc+=hxAk9r>cfRnE`-IJr{b}Ar1NPX1mKQVaKjo$zx*w(vM#!-$ zR#c!9Faw8Zx|JL89$G{_%?@ue! zzWmC)RaI;AGM4~ud|L`5yXNP01#?x30wb5@vt;MdSI@*^%)YOGD=9}o>1j6`WliY;pfyhHSgT#L4qFuFhh#BDtI&s7jyWB>>q zd6V_Swv`F9Dmj#@*hyKB<|X24jFK<8Rq|n&7QMSGGtLwlbud0$sZyQH=Bf)ld5YaSWjXA?T0BC{vx29zWU5TNzb)ewLFo?~Gg|6*FnZb8cqnrELjs zOuWsgaa9DI=U(ci(fB>|^Lk+*d3X4k*8~+Yge2DW3v}ya2N8cE|4v=vUo}4^>yN`* z-?KG|8CM{iH?QV#P;7_VXdLz}sgj29VNcj9P8)9g%TMtYt=+fRmiD!upM5rdfy}Qp zwwJako32id2zr=PFvOuDz1ezg?S0O!gHQ>DyBl4KaHV}BR@QcFDV63dsqmDSalKfy zG>DUTSbT@jjTUM3(}{U89Fw0e>rql9By0FSiwC(xkDA%=5Qu!W@n-uL5JBxxWH6$6 z$fmzjoc6oXbap`EBu4>1-&uDP{MW$`aHW)@VHYeRDj6R! zszs+*mLMME=5L1+*vI($P7xP6&jhKtL{6xxMrD3cMGOasc+;k_C@fY0v)iBMRuRNP z{f48WygZ!I2|fwn16IYdjz5Rpz~n~G_tyl^?t`Ov`M>Z@gP5_IJ+wDif? z%Yx!|a8W%M%zkmJYXs~dGcYsHCQLWGGkbkhC*EBR(9_rP78xprYdmS5w<41LTLEcq zVym!Ce&7BVN;MaFbdkGFb|zNzbV;rub9J3LNe+NNVGEk0TK`47o)WSgD^AE>Y={d# z3GvA-G0RX=HeJggMmMrFv(#6tf8Klhshs#gwM!Hd0lTceDv(weJK%6y${zqB$G zLzTxp&1okNf2_=K;OG`$lZmb8w66h8_0$np!%bpx3|+}_l{i#Rf>sqyjUeG-v*C4u zCuNghW$eLS-zN2uWOU+sshNgnD#xeuYlr|{#7GZK-32pb^kVcKeVfC!_WDDbem;(a z`b|qSm0N0-2o7AiB;@%g0XD3Wc_Y011y~uufGwQ5?@m$)_qD%-14)5Secx zmFJB69X|jSz(;a7xy|Jt@{th<+2Xc^-XIDY;2$BQ!9P_@ZFJ>$mbAl?Q|J?I@`1F=}zr% zB{M{bJ!2XPV>6D*ju!d0PcI{9?Ix87?1oh2_0!FTYmCdc5H|O@fJ_SP zhaxApSu(`r?qP~Js+T8`E|NJx8^q3%&JWmL_6|BQ@=Lpu(8j6~DUlZ|@;;HGjB&woD)PglRFT8djXT!q&Ef7XQ++5~?s&xQO-eNJTkTuZ> z-V}fSJWr?>e_XVj@}?4?klwH)-`-K2R8?A?((0>7 zE}h%!>|2wKR-W=@=&lhXiA5*pc&~DQgWg(&VWM76|Aa;SttW4bk8`BSr=;{kH6APGqu$y{WHnHH7-10!FP0O_#{|9egZ zszjQa;uvJwT6Ah}_)s3lL%T2ai=k_tABz*YYfV1y@072caFeOgSjmOU4t{t^l#o1W z!Hzs*R3CBtM7^1?_B6#-_`#I+hU{Diseo|lW{9Of11PX=4}EaEC~ z5%c|Y$p;#rcX5oT<(6G~G!JqQ+6{{=rlz`qUWKav6Q182sP zE&Qp8^=Bi~YEncxkG90@8(;S6)*41cMW3oE_kgi@nx{zRYhB=nMk1PNbND$CqG5ag zwk*}3Ou2#-lBj-RadGq>z@Ok9EZhE0Okkuy6XHmbdf*lCYC%7xzs0;fM`9gW#)^tuQ2&qLNxC2QuP-64c)ODJc^Tz<)%q`f z5*{>F-94!W3SRMVGOi(o3+9Mh9fNs&anm%11zy_F^$9gjkowYsWz=imT3*jub_pm8 zr{hgQH(o#P!*Gv@`3%)Yp5`H-pmQ{Xyx7`5Acbvyk$PI*!xY?25;?LqRtd}s^gD20X@Y5cEM<=R8*3;7$$8vaDFD~!_iuBd$vH2nwOB$yG-igtGlK$> zTU!MjbDQFWq<$9?cTh_S?oU!A%P0q&7Fr4J(Ln- znI})vFdM^fn?8x~`a>Ar*@>@Js+N7nVt2v0rP7sBmPLKv>uMa^kwgrrG$xJUEaNg! z=*Ot^Y)cExdL@?L4Fyd*wc8IMsDl(?7YyaL_;S@Bu*LE3lNva$qY6;bN`^;osG=Ox z{p(R*@`hWfGy#&nc{6rPComgKbTxG-4+eB$Po;=fA<9?t0(}{#^)cxpm+Nm-=_Bmt zHz4sZ0Khz94piJpQOL$O6W1JsNc51lYIhVwT7?K7Zih?H)ALOo3sO#-f$XWralw`6 zlmYXjthv~wZFbuhwryQKEnF#pRj=2TpaNAB`sU7!mp8NM=8JQkJC867x1!ZqO-q2_ zP(g0(hmGc)(f$a6#%@+&Los!f#_+xO(F%A#+b=oePQ9RO21x5O9FMx>*BYWtLt-#m&v(jWs|2g`V< zo5_@qNS;I$Ze@g(M_WdJB|E>nY#7FpTKioveA} zCjeUOe%orItt^yoP-#_JA*+A5wmm@ku;iT69Lkb^ur=qvC`%n;3{Q?I#R%HNjNIJY zkxFT_wz(rtai&Rd2AhvX~yxux~rUVG;+|eZSN&5^(H3~ z{49P+msNO}qsHoo>ucFEZtvjdtYFDGb*t;njE!jmFDPU|s$EFun`72ZAmmms&JFq_ z`W)QL4s31!YLr$CV@E}G;Qck3~-`2;#=-s@#w8HRg}wpBiJ=jdJx@P#TxEEhB~w+ ziqL7ObV>PW!U&nz(H! z@~npR^&C&#C_B98nQQv8Ww+<691^!W@*T(DtDBR31oZ!c$~FI~gw!ZnQ@^eyKhD5I zWm$AioV7faJgl~KSbniN=kf+>L^Ix^xcF^@N&XH*RhYNcxW<=uK=GLf#+3M+9s@j~N{_KU*wZ zRDCa^cn`YHa!h@q_6NZ(PruN96@JbP0@BFOLTEr38TH1qp6;7E ztGB?l@a(m(p}7De2Fqe2kFS@1WhDaRF_*P%aE8orJagusj|doQ#ry7>;AeXP4c!0m z`Wp~9Xe<;Qfo53ythr@^18;b~Om=fm;b1$q)jH3d4o*W4Y79G@ktGp0YsR$oXCEEZ z(2eMn;2Y0id#sjs!Q4kpUeGdL5h{tJY6v@xusMT8Om2vB!s)44hH%y7B!s2U=idF1 zUDkqU$Y$+8y0KLbXnLoQqbOahVUJ2>EbWDy+9Y?EBb(Tk0x+nOszD#SAfor9$2U;& zvS4=67B@3DhFAIg34^sg%s;T54V+8fV}KV`Ug*3k=zd^V;;?3bP65jDGddX*75{(& zzjs>byiTVh5Sc9)Gx8Sz$e{QdhO59o`ffKowF4yj#JZN&@OOEYM?Vil7!Ht*IC_0_ z^6MI-KJ$v-iB>$x$O}!fb zhQTc=soILdOL>E7I+5MN*t6|WoZeQvWtb~RM^Ygwnfv;W)PUAL#cTSPxWun-9v0(x zFd|@PgwZQPw%5Qr<@zAiQkd>OZMdzE>YSUkFhnAnl7|J9zL1$!tC{-1aH5+1 zU7HSh!fTt5yx2el7Z&eFjs^AG`!&z;`C-!0>$E3u{ZeocmmtOCiNenz)z=(X8%8HT zYN@5gKRxpNEQS87o@!d`Nt_+F5xaJX$E-g<__hpHffT12RYV#`4Y)O~89VN*h8!p1 z->aD-Zwo1e`6{4nOBn&dgB7?#8Neo}w#0Pv;gNgip zJYy+a*kYS&B(1jwJPl9w%|%G~np2Fas<3am#c__az3=rVRz|VomwQDz; zjz>F1YianK1Fv!XBevdF0ETP26T~yjrYr@2X_yrS$$ESqLF>7sd|7$a|%1 zS4SFgCmxu9ES7XP+EDOHhJ^f*>u!Bkjnnsylo7Yh=)XrY^-gZ{j=-|y?$?w}w;-SM zwl#5ycI8xz9^3MrfzP zg*_-X!$n0ZfEecZ4{0vB9#p)&_iu|s@OdwF`?6Jg$})&GQaFnXNUwYaF6VkfZ{p6o z+z9qy5$yij$eKt}c3aeHe4aZFIylOW=nP?r3v32_MpC^aSqvO1ucD#or?=o~V;X?&wiq~#ekXcsOUx|!E`TNs_w+Lhh zkW^!B16wdy@w7Jd{aq6&&R_ZHbQ`+I@Y6NmUA~9G_FI&eJ>7}cRmaN^`wRS{-y@=5 z(1KY~gIMW0)VNzxPH7PHOAo#O`0ulImTT+H(l2zu&y`KiMJ30K;v8N%o$UJKcFy|w zHO(xboFMmJ`_F+G8Wm#au2sCNW_kh12l~L=KdKySvy3{mR$JhVtNaj?s z5_V|U5y0|KH^BfYuARgfdO2gxKaD2HP6W>N1+i);BamJ~b(2O7v$c5;y^&IL#>x9jGOV<&`3 zO7`4Jp-Z=DcZ^l$e}CGyu?NRPxRnHdy!Lmi9FjaoFUL^$#&U}c(DKEMilPpmA<@QJ z9wW9Bg(sWL0tNjxfOS{4WY#&kTwnk*IRjw{{{%6M>PvHbvG4YjB z7J5Pqqf~zO(NFxSO+9JTQ2f?9))bK@tsBYwQegRjy@U{DPHx9GVBSP5)t{OnbJjJ( z64IhdombgM<{JiWnsjNKb6@As4B28v~V zcLCZ<%GR0+Jzd;AWmR)FhFTa%Lykuug`4-TFYb|k^sh^Z_4_4?X@|F28^rC&cvl1e z8K-aklK1B-^eM?FWMr16^O(58l|TZ>+yxmen%Wz#g()mE?VIgP5{RJqFsm_kz;|0`&(1#jPx3D@lrgOSX|5Z2o+8vM2>{i71GVOoLjJ zrD!ZO;B|=N2P43d3v{p=8u^BxOnO0JEx0(mUfWH$aNSm~{)X`^{Y@St z!fXA6eEf>K$ZGew--o~Ew3XMaSgeIo$ls0-+jP&Da3qUkt-pLNidp^EwvA_=r&=kc zQF7*CP-6$8ZAR-%b%kEVim0irfafrnOf;XnPpI0;UlZ$jMB-?uq2P zLW7a7!REcPKO0v!l}7L#JW!Q9SLo0tR>_y`mOV!Fg^S6GCQEE&u4|8kuHBzn6hS#x z+ore$c#A`3ih^riY%=M%T+@|VM86-boA5gYy5sWj_8w(tJUiT38E$KjM)t|U40?#khvr&J`c%o`+&QYUV&v{6DR=na z6lk0S;ZU!A$zaGoRmwYwt%3n`296}joGY1l1ZI^;kh+8o3$>p`~# z2T!5w%#@}#>F~9!FWcPy zbIkW3@pOf-z(M)$(h2$NZRygab#g{-H^0hbKIr2pWRKt6+Run+h5pw1j`e@)O#WJgb&L|$q+O?HptY8X~U)-$mb0_W?Z6S$Y{4o3` zybyjZYjSjB2;mNb|E-_lkfsJ8D29~!hnM?Er@HqMxSj&h-^AcZR!pxN4?(t4<7mmno;Xoo0VD7CkliSTiWP$+CYE3H3t~S4iW3 zJa#V#KbddonHKcIi-nRTIAL9M zR&cwms?ZMmwGGYU8UqY||1a=O@sll+JPk?Q0Y{Go?VRQ->BLMN5jum^j-^%TrMPnD3gZ=|<%lh-Wz1r~=F74U zU_@d`@+Zz>0~}L7l?8w;KH0oLOJN;*1RwC0Ib3%+@uZw2nK5gKvT)3=^`E82y{u7M zvQEo!f+s08Bl?msTY%X3IB*uE7o0fW}+B6 zm@v|u0n!3U9VMzS*gzP|N+X4v`Vl%unY>yf>0Zb_TRthBqr*uH6Xj9o0pyyA17Wq< zTA2fQpS(9~!m73#-nlw;%{t=XQwduyCIyQ%R=jLto&kqMXH$!-J`#6oC*rn&lU+qx zxsrk(QB4xb3~5VU5!ZWnIr?`eU2;)YPgw3x@v8#e5E})hP=_sbpgD0=D??uSJCK7i z#}qZ&`CBsD-D7eeYQ*6VZR>>Crz$zPP|JoY#;Unpi!H?b4#!kF3*rS{znmL5zGW>( zvllAA1+pk6HE{Y6RQBS9HGcf=;D6u*6cbT(2Hb?=bb!U*hta zN9$F+X`t)D`r!m^KFzgheIq$eA|Hjy^p&WFiZzG|z*+Dd5dIFh*8yAFT`_rgj=~J< z#IdlGi#@z?x4wioy^z_JAhVN!6;sD*+8;yDQc>lnWooi>*I;Bb=fu>i9Rke7t%Tiu zsWx#9*nw@qdUw14Q*>+Y$G#9$? z(BvN=Eh8f2$tTthgbfMw9}I6r;1pWN zhrEhZedIVG37|~&)Uj`wGQ8VcTeYX5T;$U4Rl?4wPL7u>W+Jg|E(cp zbW+w(Bp5KcrZGy#(?cmEfAi2*6py-^2OXgx_gdc^4AM=2AD88`(7de^ya_jXg-odW zmu@`(KBcrnT@G3Tr`S-=P7wb4#i5{$SI&5P_Rq@uHPoc8+xpw*Li{DOGnXF>tDb@n z`xF;F0i}*a2;T{c08k56Bg+it2K0ISi*ZZd^p-o5FN(aR^pM2W-Z^_s7evz}+?m5M zn4>`%K0$_6FOUqPzd9NAFI98DVtA;sulsq+HWTu0DA<|hhsx#H!@%+Swve8i0I$4# zVbRiz1#_RK*zS27hS!